Packages
game_server_sdk
1.0.1082
1.0.1082
1.0.1079
1.0.1078
1.0.1077
1.0.1076
1.0.1075
1.0.1074
1.0.1073
1.0.1070
1.0.1068
1.0.1067
1.0.1063
1.0.1059
1.0.1058
1.0.1057
1.0.1056
1.0.1055
1.0.1050
1.0.1049
1.0.1048
1.0.1047
1.0.1046
1.0.1044
1.0.1043
1.0.1042
1.0.1041
1.0.1040
1.0.1039
1.0.1038
1.0.1034
1.0.1033
1.0.1029
1.0.1028
1.0.1026
1.0.1025
1.0.1024
1.0.1023
1.0.1022
1.0.1021
1.0.1020
1.0.1019
1.0.1018
1.0.1017
1.0.1016
1.0.1015
1.0.1014
1.0.1013
1.0.1012
1.0.1011
1.0.1009
1.0.1008
1.0.1007
1.0.1006
1.0.1005
1.0.1004
1.0.1003
1.0.1001
1.0.999
1.0.998
1.0.997
1.0.996
1.0.995
1.0.994
1.0.993
1.0.992
1.0.991
1.0.990
1.0.989
1.0.988
1.0.987
1.0.986
1.0.985
1.0.984
1.0.983
1.0.982
1.0.981
1.0.980
1.0.979
1.0.978
1.0.977
1.0.976
1.0.975
1.0.974
1.0.973
1.0.972
1.0.971
1.0.970
1.0.969
1.0.968
1.0.967
1.0.966
1.0.965
1.0.964
1.0.963
1.0.962
1.0.961
1.0.959
1.0.958
1.0.956
1.0.951
1.0.950
1.0.943
1.0.942
1.0.941
1.0.940
1.0.938
1.0.936
1.0.935
1.0.931
1.0.929
1.0.928
1.0.927
1.0.926
1.0.925
1.0.924
1.0.923
1.0.921
1.0.920
1.0.919
1.0.918
1.0.917
1.0.916
1.0.911
1.0.910
1.0.902
1.0.899
1.0.898
1.0.897
1.0.896
1.0.894
1.0.893
1.0.891
1.0.890
1.0.889
1.0.888
1.0.887
1.0.886
1.0.885
1.0.884
1.0.883
1.0.882
1.0.881
1.0.880
1.0.879
1.0.878
1.0.877
1.0.26
1.0.25
1.0.22
1.0.21
1.0.20
1.0.19
1.0.15
1.0.14
1.0.13
1.0.12
1.0.10
1.0.9
1.0.8
1.0.7
1.0.6
1.0.5
1.0.4
1.0.3
1.0.2
1.0.1
1.0.0
0.1.0
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
Current section
Files
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
Serialized on **both** adapters, per key, by different mechanisms:
* **PostgreSQL** — `pg_advisory_xact_lock` inside the transaction.
* **SQLite** — `GameServer.Lock.Local`, a `:global` mutex taken *around* the
transaction, since SQLite has no advisory locks and its single-writer rule
covers neither a read-modify-write across statements nor a critical
section that never touches the database.
`default_transaction_mode: :immediate` is a backstop for callers who forget
this function, not the mechanism — it does nothing for ETS or process state.
## Nesting
Take the lock at the outermost point. A `serialize/3` reached inside an open
transaction cannot take the mutex — the caller holds the write lock, so
waiting on a mutex whose holder wants that lock deadlocks — so it runs under
the outer transaction and relies on the outer lock.
## Prefer an atomic write
Prefer an atomic write where one exists: `Economy.debit/3` does
`balance = balance - x where balance >= x` in one statement, which needs no
lock at all.
## Multi-node safety
Both paths are cluster-wide: the Postgres lock lives in the shared database,
and `:global.trans` coordinates across connected nodes. A netsplit can produce
two holders on either path, which is why value operations must also be atomic.
## 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` — id of the specific resource (e.g. lobby id)
- `fun` — zero-arity function to execute while holding the lock
"""
@spec serialize(atom() | String.t(), String.t(), (-> 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