RedisMutex is a library for creating a Redis lock for a single Redis instance.
The package can be installed by adding redis_mutex
to your list of dependencies in mix.exs
:
def deps do
[
{:redis_mutex, "~> 1.0"}
]
end
Version 1.0.0 of RedisMutex
changes how RedisMutex
is started and how with_lock
is used. Key changes include:
RedisMutex
no longer runs as its own application.- If you need or want to set up a Redix connection specifically for
RedisMutex
, it must be added to your application's supervision tree. - If you want to re-use an existing Redis connection via Redix, it does not need adding to your application's supervision tree.
- If you need or want to set up a Redix connection specifically for
- Using
RedisMutex
'swith_lock
is no longer done viause RedisMutex
. Instead, your application must call the functionRedisMutex.with_lock/3
. - The code you want to execute in
RedisMutex.with_lock/3
is passed in a zero-arity function instead of in ado
block. - Timeout and expiry options for
RedisMutex.with_lock/3
are optionally provided in a keyword list as the last argument toRedisMutex.with_lock/3
. - Callbacks are defined for
RedisMutex
's functions to allow for doubles to be used in testing.
In order to upgrade to version 1.X, you will need to:
- Add
RedisMutex
to your application's supervision tree unless you are using an existing Redis connection via Redix. - Remove use of
use RedisMutex
in favor ofRedisMutex.with_lock/3
. - Replace the
do
block with a zero-arity function in your calls toRedisMutex.with_lock/3
. - Move any timeout or expiry arguments into a keyword list as the final argument to
RedisMutex.with_lock/3
. - If you are not running Redis when you run your unit tests, update your test suite to use a double
that handles
RedisMutex
's updated functions.
Here's a quick example of the changes that need to be made to how you use with_lock
.
defmodule PossumLodge do
use RedisMutex
def get_oauth do
with_lock("my_key") do
"Quando omni flunkus moritati"
end
end
end
defmodule PossumLodge do
def get_oauth do
RedisMutex.with_lock("my_key", fn ->
"Quando omni flunkus moritati"
end)
end
end
Please see the Usage section for more details and examples.
RedisMutex
offers the user flexibility in how it is used.
If you already have a named connection to Redis and want to re-use that, using RedisMutex
is dead simple.
If you need to start a named connection to Redis for a mutex, you can do so via RedisMutex
.RedisMutex
offers
a default connection name when starting your connection to Redis. This is the simplest way to use RedisMutex
,
and it is the default.
If you want to customize the name used for that connection, you can specify a name to use for the connection.
In order to use an existing connection, you can simply pass the name of that connection as an option to
RedisMutex.with_lock/3
defmodule PossumLodge do
@redis_connection_opts [name: :my_existing_redis_connection]
def get_oauth do
RedisMutex.with_lock(
"my_key",
fn -> "Quando omni flunkus moritati" end,
@redis_connection_opts
)
end
end
If you don't have an existing connection that you want to re-use, and you want to start a connection for RedisMutex
,
you need to set options in your configuration and add RedisMutex
to your application's supervision tree.
If you have a named connection to Redis that you want to re-use, you do not need to add RedisMutex
to your application's supervision tree.
By default, RedisMutex
will use :redis_mutex_connection
as the name for setting up a connection to Redis.
Set the options
in your for RedisMutex
in your supervision tree. The options can be a redis_url
or a set of
options for Redis. See RedisMutex.start_options
for details.
By default, RedisMutex
will use :redis_mutex_connection
as the name for setting up a connection to Redis.
@impl Application
def start(_type, _args) do
children = other_children() ++ [{RedisMutex, redis_url: System.get_env("REDIS_URL")}]
Supervisor.start_link(children, strategy: :one_for_one, name: MyApp.Supervisor)
end
@impl Application
def start(_type, _args) do
children = other_children() ++ [{RedisMutex, host: System.get_env("REDIS_URL"), port: System.get_env("REDIS_PORT")}]
Supervisor.start_link(children, strategy: :one_for_one, name: MyApp.Supervisor)
end
If you want to start a connection with a name other than RedisMutex
, you should specify the name you
want to use when adding RedisMutex
to your application's supervision tree. You will also need to provide this
name as an option to the lock function when using RedisMutex
.
In order to specify the connection name, include it as an option when adding RedisMutex
to your
application's supervision tree.
@impl Application
def start(_type, _args) do
children = other_children() ++ [
{RedisMutex,
name: MyApp.Mutex,
redis_url: System.get_env("REDIS_URL", "redis://localhost:6379")
}
]
Supervisor.start_link(children, strategy: :one_for_one, name: MyApp.Supervisor)
end
@impl Application
def start(_type, _args) do
children = other_children() ++ [
{RedisMutex,
name: MyApp.RedisMutex,
host: System.get_env("REDIS_HOST", "localhost"),
port: System.get_env("REDIS_PORT", 6379)
}
]
Supervisor.start_link(children, strategy: :one_for_one, name: MyApp.Supervisor)
end
If you are using a custom connection name and want to simplify the use of RedisMutex
, you can
write a wrapper module for RedisMutex
and add that module to your application's supervision tree.
defmodule MyApp.Mutex do
@redis_mutex Application.compile_env(:my_app, :redis_mutex, RedisMutex)
def child_spec(opts) do
child_spec_opts = Keyword.merge(opts, name: MyApp.Mutex)
@redis_mutex.child_spec(child_spec_opts)
end
def start_link(start_options) do
@redis_mutex.start_link(start_options)
end
def with_lock(key, opts, fun) do
lock_options = Keyword.merge(opts, name: MyApp.Mutex)
@redis_mutex.with_lock(key, fun, lock_options)
end
end
@impl Application
def start(_type, _args) do
children = other_children() ++ [
{MyApp.Mutex,
redis_url: System.get_env("REDIS_URL")
}
]
Supervisor.start_link(children, strategy: :one_for_one, name: MyApp.Supervisor)
end
Call RedisMutex
's with_lock/3
function to lock critical parts of your code. with_lock/3
must be
provided with a key
argument and a zero-arity function argument to call. This function will be called
if and when the lock is acquired.
defmodule PossumLodge do
@redis_mutex Application.compile_env(:my_app, :redis_mutex, RedisMutex)
def get_oauth do
@redis_mutex.with_lock("my_key", fn ->
"Quando omni flunkus moritati"
end)
end
end
with_lock/3
also allows setting options, including a name for the connection, a timeout and an
expiry, both in milliseconds. If you have specified a custom connection name or are re-using an
existing named connection to redis, the name of that connection must be included in the options
when calling with_lock/3
.
defmodule PossumLodge do
@redis_mutex Application.compile_env(:my_app, :redis_mutex, RedisMutex)
@mutex_options [name: MyApp.Mutex, timeout: 500, expiry: 1_000]
def get_oauth do
@redis_mutex.with_lock(
"my_key",
fn -> "Quando omni flunkus moritati" end,
@mutex_options
)
end
end
If you are running Redis when you are running your test suite, simply having the redis_mutex
config set and
running the default command works:
mix test
If you want to test your application without an instance of Redis running, you will need to define a double for
RedisMutex
. RedisMutex
defines callbacks for child_spec/1
, start_link/1
, with_lock/2
and with_lock/3
.
If you are using Mox
, you can define the mock along with your other mocks.
Mox.defmock(RedisMutexMock, for: RedisMutex)