Advisory locking for protecting TOCTOU (Time-of-Check-Time-of-Use) patterns.
On PostgreSQL, acquires a transaction-scoped advisory lock via
pg_advisory_xact_lock(namespace, resource_id). The lock is automatically
released when the enclosing Repo.transaction commits or rolls back.
On SQLite, this function is a no-op, because SQLite has no advisory locks. That is a fact about this function, not a claim that locking is unnecessary there: SQLite serializes each write, which is not the same as serializing a read-modify-write spanning statements, and does nothing at all for a critical section held over ETS or process state.
Callers should not use this module directly for that reason. Go through
Gamend.Lock.serialize/3, which picks this on Postgres and a keyed
:global mutex (Gamend.Lock.Local) everywhere else, so the guarantee
holds on both adapters.
Usage
Always call within a Repo.transaction:
Repo.transaction(fn ->
AdvisoryLock.lock(:lobby, lobby.id)
count = count_members(lobby.id)
if count >= lobby.max_users, do: Repo.rollback(:full)
do_join(...)
end)Namespaces
Each resource type uses a distinct integer namespace to avoid collisions:
:lobby→ 1:group→ 2:party→ 3:friendship→ 4
You can also pass an arbitrary string as the namespace. The string is
hashed to a stable 32-bit integer via :erlang.phash2/2, so any
string (e.g. "word_guessed", "my_rpc") works without pre-registration.
Examples
# Atom namespace (predefined):
AdvisoryLock.lock(:lobby, lobby_id)
# String namespace (ad-hoc):
AdvisoryLock.lock("word_guessed", lobby_id)
Summary
Functions
Acquire a transaction-scoped advisory lock for the given resource.
The registered lock namespaces and their ids (for introspection).
Returns true if the Repo was compiled with the PostgreSQL adapter.
Functions
Acquire a transaction-scoped advisory lock for the given resource.
namespace can be a predefined atom (:lobby, :group, :party) or any
arbitrary string. resource_id is a UUID string; it is hashed to a stable
32-bit integer for pg_advisory_xact_lock (a hash collision only causes
extra serialization, never lost mutual exclusion).
Must be called inside a Repo.transaction. On PostgreSQL, blocks until
the lock is available. On SQLite, returns immediately — see the moduledoc.
The registered lock namespaces and their ids (for introspection).
@spec postgres?() :: boolean()
Returns true if the Repo was compiled with the PostgreSQL adapter.