Packages

SDK for GameServer hooks development. Provides type specs, documentation, and IDE autocomplete for GameServer modules without requiring the full server.

Current section

Files

Jump to
game_server_sdk lib game_server lock.ex
Raw

lib/game_server/lock.ex

defmodule GameServer.Lock do
@moduledoc ~S"""
Serialized execution using database-level advisory locks.
Wraps a function in a `Repo.transaction` with an advisory lock so that
only one process at a time can execute the critical section for a given
`(namespace, resource_id)` pair.
This is useful for game RPCs where multiple players may trigger the same
operation concurrently (e.g. guessing a word, claiming a reward) and the
logic involves read-modify-write on KV entries or lobby metadata.
## How it works
On **PostgreSQL**, the lock is acquired via `pg_advisory_xact_lock` and is
automatically released when the transaction commits or rolls back. Other
callers with the same key block until the lock is available.
On **SQLite** (dev/test), advisory locks are a no-op because SQLite already
serializes all writes at the database level.
## Multi-node safety
Because the lock lives in the database, it works correctly across multiple
application nodes — all nodes share the same Postgres instance, so the lock
is globally consistent.
## Namespace conventions
The `namespace` argument can be:
- A **predefined atom**: `:lobby` (1), `:group` (2), `:party` (3)
- An **arbitrary string**: hashed to a stable integer, e.g. `"word_guessed"`
The `resource_id` is typically the lobby, group, or user id that scopes
the lock.
## Examples
# Serialize all "word_guessed" RPCs per lobby
GameServer.Lock.serialize("word_guessed", lobby_id, fn ->
{:ok, entry} = GameServer.KV.get("game_state", lobby_id: lobby_id)
new_val = Map.update(entry.value, "guessed", [word], &[word | &1])
GameServer.KV.put("game_state", new_val, %{}, lobby_id: lobby_id)
end)
# Using a predefined atom namespace
GameServer.Lock.serialize(:lobby, lobby_id, fn ->
# exclusive per-lobby operation
end)
## Return value
Returns `{:ok, result}` where `result` is the return value of the function,
or `{:error, reason}` if the transaction rolls back.
**Note:** This is an SDK stub. Calling these functions will raise an error.
The actual implementation runs on the GameServer.
"""
@doc ~S"""
Execute `fun` inside a transaction with an advisory lock on `(namespace, resource_id)`.
Only one process at a time can hold the lock for a given key pair. Other
callers block until the lock is released (on transaction commit/rollback).
Returns `{:ok, result}` on success or `{:error, reason}` on rollback.
## Parameters
- `namespace` — atom (`:lobby`, `:group`, `:party`) or any string
- `resource_id` — integer identifying the specific resource (e.g. lobby id)
- `fun` — zero-arity function to execute while holding the lock
"""
@spec serialize(atom() | String.t(), integer(), (-> result)) :: {:ok, result} | {:error, term()}
when result: term()
def serialize(_namespace, _resource_id, _fun) do
case Application.get_env(:game_server_sdk, :stub_mode, :raise) do
:placeholder ->
nil
_ ->
raise "GameServer.Lock.serialize/3 is a stub - only available at runtime on GameServer"
end
end
end