RWLock (rwlock v0.1.1)
View SourceA concurrent Readers/Writer lock implementation built on GenServer
.
RWLock
allows multiple readers to acquire a shared (:sh_lock
) lock
simultaneously, while ensuring that only one writer (:ex_lock
) can hold
an exclusive lock at any given time.
This module manages lock ownership across processes and ensures correct
coordination between readers and writers for a given resource key (referred to as on
).
Features
- Multiple readers can hold a lock concurrently (
sh_lock
) - Only one writer can hold a lock exclusively (
ex_lock
) - Fair queueing via an internal
:queue
structure - Lock ownership tracking via process identifiers
- Automatic wake-up of waiting processes when a lock is released
Installation
The package can be installed by adding rwlock
to your list of dependencies in mix.exs
:
def deps do
[
{:rwlock, "~> 0.1.1"}
]
end
This library implements a RW Locking as a process that you would generally start under a supervision tree.
defmodule MyApp.Application do
@moduledoc false
use Application
@impl true
def start(_type, _args) do
children = [
{RWLock, name: MyApp.RWLock}
]
Supervisor.start_link(children, strategy: :one_for_one)
end
end
Example
iex> RWLock.start_link()
iex> RWLock.sh_lock(:any_term1)
:ok
iex> RWLock.sh_lock(:any_term1)
:ok
iex> RWLock.ex_lock(:any_term2)
:ok
iex> RWLock.unlock(:any_term1)
:ok
iex> RWLock.unlock(:any_term1)
:ok
iex> RWLock.unlock(:any_term2)
:ok
Lock Structure
Internally, each lock entry is a map with these keys:
%{
locked?: boolean(),
type: :ex | :sh | nil,
readers: MapSet.t(),
wlist: :queue.t(),
on: any()
}
This structure is maintained per on
key in the GenServer’s state.
Summary
Types
A process identifier that holds a lock
The lock state for a single resource
Lock type: shared (readers) or exclusive (writer)
The GenServer state holding all resource locks
Wait queue containing pending lock requests
Functions
Returns a specification to start this module under a supervisor.
Acquires an exclusive (writer) lock on a resource.
Acquires a shared (reader) lock on a resource.
Starts the RWLock GenServer process.
Unlocks a resource previously locked by the caller process.
Types
@type client_pid() :: pid()
A process identifier that holds a lock
@type lock() :: %{ locked?: boolean(), type: lock_type(), readers: MapSet.t(client_pid()) | nil, wlist: wait_queue(), on: any() }
The lock state for a single resource
@type lock_type() :: :sh | :ex | nil
Lock type: shared (readers) or exclusive (writer)
The GenServer state holding all resource locks
@type wait_queue() :: :queue.queue({:sh_lock | :ex_lock, GenServer.from()})
Wait queue containing pending lock requests
Functions
Returns a specification to start this module under a supervisor.
See Supervisor
.
@spec ex_lock(any()) :: :ok
Acquires an exclusive (writer) lock on a resource.
The call blocks until the lock becomes available.
@spec sh_lock(any()) :: :ok
Acquires a shared (reader) lock on a resource.
Multiple readers may hold the same lock concurrently, provided that no writer is active or waiting.
@spec start_link(map() | nil) :: GenServer.on_start()
Starts the RWLock GenServer process.
Optionally accepts an initial state map (usually empty).
@spec unlock(any()) :: :ok
Unlocks a resource previously locked by the caller process.
This will trigger giving the lock to the next waiting reader(s) or writer.