asobi_world_lobby_server (asobi v0.75.1)

View Source

Single point of serialization for asobi_world_lobby:find_or_create/1.

The naive find_or_create implementation has a TOCTOU race: two clients calling at the same time both see list_worlds(...) = [] because neither has finished create_world/1 yet, and both end up spawning a new world for the same mode. Customer-visible symptom: two players opening barrow at the same instant land in different hub worlds and never see each other.

This gen_server forces all find_or_create calls through a single process. The handler runs the existing list_worlds + create_world sequence atomically with respect to other callers, so the second caller sees the world the first one just spawned.

Calls are sequential, but create_world/1 is the slowest step and takes <100ms in practice; for the small number of distinct modes a typical deployment supports, the queue stays empty.

It also owns the protected ETS table backing the cached discovery listings for both worlds and matches. Callers read the table directly (protected reads); only this server writes, via cache_listing/3.

Summary

Functions

Store a computed listing against a bounded key with an absolute expiry. Async: the caller keeps the value it already computed; this only seeds the shared cache for the next reader.

Atomic version of asobi_world_lobby:find_or_create/1. Public callers should always go through this — calling the inner function directly is racy.

Functions

cache_listing/3

-spec cache_listing({module(), boolean()}, [map()], integer()) -> ok.

Store a computed listing against a bounded key with an absolute expiry. Async: the caller keeps the value it already computed; this only seeds the shared cache for the next reader.

The key is {ServerMod, HasCapacity} - four keys total across worlds and matches. It must stay bounded and free of client-controlled values: keying on mode would let a client cycle distinct modes to miss on every request and grow the table without bound.

find_or_create(Mode)

-spec find_or_create(binary()) -> {ok, pid(), map()} | {error, term()}.

Atomic version of asobi_world_lobby:find_or_create/1. Public callers should always go through this — calling the inner function directly is racy.

find_or_create(Mode, PlayerId)

-spec find_or_create(binary(), binary() | undefined) -> {ok, pid(), map()} | {error, term()}.

handle_call/3

-spec handle_call(term(), gen_server:from(), map()) -> {reply, term(), map()}.

handle_cast/2

-spec handle_cast(term(), map()) -> {noreply, map()}.

init/1

-spec init([]) -> {ok, #{}}.

start_link()

-spec start_link() -> gen_server:start_ret().