Created
September 24, 2021 11:58
-
-
Save bluzky/a0e33dfacca18046acf266f0bb3bcc4b to your computer and use it in GitHub Desktop.
Elixir Redis Lock
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
defmodule Toolkit.RedisLock do | |
@moduledoc """ | |
Provide mechanism to lock and check locking of a given key. | |
When set lock on a key, must set expiration time to make sure no lock live forever | |
""" | |
@redix_conn :octosells_cache | |
@doc """ | |
Lock given key for `lock_duration` in second. Default lock duration is 5 minute | |
This function make sure only one process can acquire lock. We use `INCR` command to guarantee this. | |
INCR command is atomic so only one `INCR` command rund at a time and it return the value immediately. We do as follows: | |
- Increase a key by 1 | |
- If result is 1 -> it's the first one who acquire lock | |
- Return :ok | |
- If result is not 1 -> some others lock this key | |
- Return error | |
- If some other error happens | |
- Get lock TTL | |
- If lock TTL not set | |
+ Clear lock | |
- Else | |
+ do nothing | |
""" | |
def acquire(key, duration \\ 300) | |
def acquire(key, lock_duration) when is_binary(key) and lock_duration > 0 do | |
with {:ok, 1} <- command(["INCR", key]), | |
{:ok, _} <- command(["EXPIRE", key, lock_duration]) do | |
:ok | |
else | |
{:ok, _val} -> | |
{:error, "lock for #{key} has been acquired"} | |
error -> | |
# If error happens | |
# GET TTL of key | |
case command(["TTL", key]) do | |
# if no TTL set for lockey then remove the key to avoid locking forever | |
{:ok, -1} -> command(["DEL", key]) | |
# do nothing | |
_ -> nil | |
end | |
error | |
end | |
end | |
def acquire(key, lock_duration) do | |
{:error, "lock_key or lock_duration is invalid::#{inspect([key, lock_duration])}}"} | |
end | |
@doc """ | |
Clear lock key | |
""" | |
def release(key) do | |
case command(["DEL", key]) do | |
{:error, _} = error -> error | |
_ -> :ok | |
end | |
end | |
# shorten command | |
defp command(args) do | |
Redix.command(@redix_conn, args) | |
end | |
end |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment