Lockring (lockring v0.1.3)

Copy Markdown

Lockring is a mutex library for BEAM languages.

Use it when you need exclusive access to a single resource or one of a pool of resources.

These resources can be static data or GenServers. In the latter case, GenServer crashes are handled automatically, replacing the crashed server and releasing its lock.

Lockring uses ETS tables and Erlang :atomics to coordinate locking, providing high scalability without the bottleneck and message-passing overhead of a GenServer-based system.

Summary

Functions

Attempts one time to acquire a lock on a resource in a pool.

Creates a new Lockring pool.

Releases a lock.

Attempts to acquire a lock on a resource in a pool until the given timeout is reached. Passing :infinity as the timeout will wait forever for a lock.

Attempts to acquire a lock on a resource, If successful, executes fun.(resource) and returns its return value.

Types

index()

@type index() :: non_neg_integer()

lock_ref()

@type lock_ref() :: {name(), index()}

name()

@type name() :: any()

resource()

@type resource() :: any()

Functions

lock(name, opts \\ [])

@spec lock(name(), Keyword.t()) ::
  {:ok, lock_ref(), resource()} | :fail | {:error, String.t()}

Attempts one time to acquire a lock on a resource in a pool.

If the pool did not exist, it will be created, respecting the options passed in opts.

Returns {:ok, lock_ref, resource} if a lock was acquired, :fail if the resource is already locked, or {:error, reason} if something went wrong.

If the lock was acquired, the user must be sure to release it with Lockring.release(lock_ref) when finished with it.

To keep trying for a lock, see wait_for_lock/2.

To lock a resource and execute a function on it, see with_lock/3.

new(name, opts \\ [])

@spec new(name(), Keyword.t()) :: :ok

Creates a new Lockring pool.

Options:

  • :size - The number of resources in this pool. Default 1.

  • :resource - Function or {module, opts} tuple for generating resources.

    If a function is supplied, it must be of arity 2. The return value of fun.(name, index) will be used as the resource.

    If module and opts are supplied, they will be used as inputs to GenServer.start_link/2, adding :name and :index to the given opts. The resulting pid will be used as the resource. If this process crashes, a new one will take its place in the pool automatically.

    If no resource is supplied, the atom :none is used as the resource. This is useful for creating locks that are not attached to a given resource. This is the default behavior.

release(lock_ref)

@spec release(lock_ref()) :: :ok

Releases a lock.

wait_for_lock(name, timeout \\ :infinity, opts \\ [])

Attempts to acquire a lock on a resource in a pool until the given timeout is reached. Passing :infinity as the timeout will wait forever for a lock.

Returns {:ok, lock_ref, resource} if a lock was acquired, :fail if the resource is already locked, or {:error, reason} if something went wrong.

with_lock(name, fun, opts \\ [])

@spec with_lock(name(), (resource() -> any()) | (-> any()), Keyword.t()) ::
  any() | {:error, String.t()}

Attempts to acquire a lock on a resource, If successful, executes fun.(resource) and returns its return value.

During lock wait and the execution of fun, the lowest applicable timeout value will be used.

Options:

  • :timeout - Maximum number of milliseconds before with_lock returns. This includes both wait and execution time. Default 5000. Set to :infinity to wait forever, or set to nil to observe only :wait_timeout and :fun_timeout.

  • :wait_timeout - Maximum number of milliseconds to wait when acquiring a lock. Default :infinity.

  • :fun_timeout - Maximum number of milliseconds to wait when executing fun.(resource). Default :infinity. execution to finish.