# EtsLock

EtsLock is an Elixir library for acquiring exclusive locks on data in
[ETS]( tables.

Using `with_ets_lock/4`, you can process all `{key, value}` tuples for a
given `key` while being sure other processes using `with_ets_lock/4`
are not mutating the data stored for this key.

Processing is performed in a separate process so that an execution
timeout (`:exec_timeout`) can be enforced.

For high concurrent performance, all locks are stored in a separate
ETS table, and no GenServers or other processes are used to coordinate
access.  This is in contrast to the
[erlang-ets-lock library](,
which uses a single GenServer to serialize access to ETS.

## Docs

Full documentation is available on

## Example usage

    iex> table =, [:set, :public])
    iex> spawn(fn ->
    ...>   ## Wait 50ms, try to acquire lock, then insert
    ...>   Process.sleep(50)
    ...>   EtsLock.with_ets_lock(table, :key, fn _ ->
    ...>     :ets.insert(table, {:key, :yup})
    ...>   end)
    ...> end)
    iex> spawn(fn ->
    ...>   ## Acquire lock immediately, hold it for 100ms, then insert
    ...>   EtsLock.with_ets_lock(table, :key, fn _ ->
    ...>     Process.sleep(100)
    ...>     :ets.insert(table, {:key, :nope})
    ...>   end)
    ...> end)
    iex> Process.sleep(200)
    iex> :ets.lookup(table, :key)
    [{:key, :yup}]

## Options

* `:wait_timeout` - Milliseconds to wait when acquiring a lock.
  Default 5000.  Set to `:infinity` to try forever to acquire a lock.

* `:exec_timeout` - Milliseconds to allow `fun` to hold the lock before
  its execution is cancelled and the lock is released.  Default 5000.
  Set to `:infinity` to hold the lock indefinitely until `fun` has finished.

* `:fail_timeout` - Milliseconds to wait before forcibly deleting a
  lock after it _should_ have been released, but wasn't.  Default 1000.
  Provides protection against permanently hanging locks in the case that
  both the caller and the spawned task crash.  Set to `:infinity` to
  disable this protection (not recommended).

* `:spin_delay` - Milliseconds to wait between every attempt to acquire a
  lock.  Default 2.

* `:lock_table` - ETS table in which to store locks. Default `EtsLock.Locks`.

## Installation

Add `ets_lock` to your list of dependencies in `mix.exs`:

def deps do
    {:ets_lock, "~> 0.2.0"}

## Authorship and License

EtsLock is copyright 2019, Appcues, Inc.

EtsLock is released under the MIT License, available at