Packages

Elixir wrapper for the locker Erlang library.

Current section

Files

Jump to
elixir_locker lib elixir_locker.ex
Raw

lib/elixir_locker.ex

defmodule Locker do
@moduledoc """
Provides Elixir API to locker Erlang library as well
as some utilities that make using locker easier.
Locker is a distributed, de-centralized, consistent in-memory
key-value store written in Erlang by a company called Wooga. The
locker Erlang project can be found from
`https://github.com/wooga/locker`.
This module provides pretty much direct mapping of the locker API
for Elixir as well as some useful mix-in classes for creating long
running locker registered processes. See `Locker.Server` and
`Locker.Fsm`. It also provides a process registry,
`Locker.Registry`, that can be used using e.g., `GenServer`'s
`{:via, module, term}` API.
"""
use Application
@type reply :: {:ok, pos_integer, pos_integer, pos_integer}
def start(_type, _args) do
import Supervisor.Spec, warn: false
# locker configuration
cluster = [Node.self | Node.list]
primaries = cluster
replicas = []
quorum = Application.get_env(:elixir_locker, :quorum, 0)
children = [worker(:locker, [quorum])]
opts = [strategy: :one_for_one, name: Locker.Supervisor]
{:ok, pid} = Supervisor.start_link(children, opts)
# we initialize the locker cluster
:ok = :locker.set_nodes(cluster, primaries, replicas)
{:ok, pid}
end
def stop(_state) do
:ok
end
# the :locker api
@doc """
Sets the desired write quorum for the given cluster.
"""
@spec set_w([atom], pos_integer) :: :ok
def set_w(cluster, w) do
:locker.set_w(cluster, w)
end
@doc """
Sets the primaries and replicas for the given
cluster. Assumes no failures.
"""
@spec set_nodes([atom], [atom], [atom] | []) :: :ok
def set_nodes(cluster, primaries, replicas) do
:locker.set_nodes(cluster, primaries, replicas)
end
@doc """
Tries to acquire the lock.
"""
@spec lock(any, any, pos_integer, pos_integer) :: reply | {:error, :no_quorum}
def lock(key, value, lease_length \\ 2000, timeout \\ 5000) do
:locker.lock(key, value, lease_length, timeout)
end
@doc """
Tries to update the lock. The update only happens if an
existing value of the lock corresponds to the given `value` within the
`w` number of master nodes.
"""
@spec update(any, any, pos_integer, pos_integer) :: reply | {:error, :no_quorum}
def update(key, value, new_value, timeout \\ 5000) do
:locker.update(key, value, new_value, timeout)
end
@doc """
Waits for the key to become available on the local node. If a value
is already available, returns immediately, otherwise it will return
within the `timeout`. In case of timeout, the caller might get a reply
anyway if it sent at the same time as the timeout.
"""
@spec wait_for(any, pos_integer) :: {:ok, any}
def wait_for(key, timeout \\ 5000) do
:locker.wait_for(key, timeout)
end
@doc """
Waits for the key to be released.
"""
@spec wait_for_release(any, pos_integer) :: {:ok, any}
def wait_for_release(key, timeout \\ 5000) do
:locker.wait_for_release(key, timeout)
end
@doc """
Releases the lock on given `key`. The `value` needs to match to the
locked value.
"""
@spec release(any, any, pos_integer) :: reply | {:error, :no_quorum}
def release(key, value, timeout \\ 5000) do
:locker.release(key, value, timeout)
end
@doc """
Extends the lease on given `key` and `value`.
"""
@spec extend_lease(any, any, pos_integer) :: reply | {:error, :no_quorum}
def extend_lease(key, value, lease_length \\ 2000) do
:locker.extend_lease(key, value, lease_length)
end
@doc """
Read the `value` for the given `key`.
A dirty read does not create a read-quorum so consistency is not
guaranteed. The value is read directly from a local ETS-table, so
the performance should be very high.
"""
@spec dirty_read(any) :: {:ok, any} | {:error, :not_found}
def dirty_read(key) do
:locker.dirty_read(key)
end
@doc """
Execute a dirty read on the master. Same caveats as for
`dirty_read/1`.
"""
@spec master_dirty_read(any) :: {:ok, any} | {:error, :not_found}
def master_dirty_read(key) do
:locker.master_dirty_read(key)
end
@doc """
Provides a lag metric for the cluster.
"""
@spec lag :: {number, reply}
def lag do
:locker.lag()
end
@doc """
Some internal stats.
"""
@spec summary :: [{:write_locks, pos_integer}, {:leases, pos_integer}]
def summary do
:locker.summary()
end
end