Normandy.Behaviours.SessionRegistry.Redis (normandy v2.2.0)

View Source

Distributed SessionRegistry using Redis as the shared session_id → pid name table, with cross-node routing over Erlang distribution (Redis replaces Horde's CRDT, not distribution). A per-node owner GenServer monitors locally registered pids (clearing the key on :DOWN) and refreshes key TTLs so a crashed node's keys lapse on their own.

The handle is a Normandy.Behaviours.SessionRegistry.Redis.Handle, %Handle{owner:, conn:, namespace:}: the owner's registered name, the Redix connection and the key namespace. new/1 returns one; build one with handle/1 from the values given to start_link/1, or with fetch_handle/1 for an owner that opened its own connection from :url. whereis/2 runs in the caller, on the handle's connection: a lookup never waits on the owner. register/3 and unregister/2 go through the owner, which monitors the pids. An owner started with :url registers its connection as Module.concat(name, Redix), so a handle keeps working across owner restarts; that name is reserved for it, and an owner whose derived name another process holds fails to start with {:error, {:connection_name_taken, name}} (pass :conn or rename the owner). A handle is meant for the node its owner runs on: on another node, a lookup reaches the connection over distribution.

Registration is atomic via SET … NX (no read-modify-write), so a concurrent start cluster-wide yields one winner; the losers route to it. Use as a :via registry through child_name/2 (the via term is {handle, session_id}) to get atomic, supervisor-driven starts (closes the start race), exactly like the Horde impl.

Refresh and lost ownership

Every ttl_ms / 2 the owner extends the TTL of each key it holds, in pipelines of 1,000 EVALSHA calls. A key that lapsed (a late refresh, an eviction) is claimed again with SET … NX. A key another node now holds means two servers for one session: the owner stops its local process with exit reason {:shutdown, :registration_lost}, logs a warning and emits [:normandy, :session_registry, :ownership_lost]. A Turn.Server stopped this way is not restarted (restart: :transient); its session goes on where the key points. The stop is an exit signal: a registered process that traps exits receives it as a message and must stop itself; the owner no longer monitors it either way.

Summary

Functions

Returns a specification to start this module under a supervisor.

The handle of a running owner, asked of it once: for an owner started with :url, whose connection only the owner knows. Keep the result; a lookup with it never calls the owner. The handle names the owner's connection (Module.concat(owner, Redix) for a :url owner, else the :conn given), so it stays valid when the owner restarts under the same name, as long as a :conn given by name is restarted too.

Builds the handle of an owner started with start_link/1's :name, :conn and :namespace. Starts and calls nothing.

Starts an owner GenServer with its own Redix connection to :url and returns its handle. :url and :namespace are required.

Starts the per-node owner GenServer. Build the handle with handle/1 from the same :name, :conn and :namespace, or with fetch_handle/1 when the owner connects from :url.

Functions

child_spec(init_arg)

Returns a specification to start this module under a supervisor.

See Supervisor.

fetch_handle(owner)

The handle of a running owner, asked of it once: for an owner started with :url, whose connection only the owner knows. Keep the result; a lookup with it never calls the owner. The handle names the owner's connection (Module.concat(owner, Redix) for a :url owner, else the :conn given), so it stays valid when the owner restarts under the same name, as long as a :conn given by name is restarted too.

handle(opts)

Builds the handle of an owner started with start_link/1's :name, :conn and :namespace. Starts and calls nothing.

Options

new(opts)

Starts an owner GenServer with its own Redix connection to :url and returns its handle. :url and :namespace are required.

Options

  • :name (atom/0) - The owner's name (default: a unique one).

  • :url (String.t/0) - Required. The Redis URL.

  • :namespace (String.t/0) - Required. The key namespace.

start_link(opts)

@spec start_link(keyword()) :: GenServer.on_start()

Starts the per-node owner GenServer. Build the handle with handle/1 from the same :name, :conn and :namespace, or with fetch_handle/1 when the owner connects from :url.

Options

  • :name (atom/0) - Required. The owner's registered name (the handle's :owner).

  • :conn (GenServer.server/0) - A started Redix connection (pid or name).

  • :url (String.t/0) - A Redis URL to connect to when :conn is not given; the owner starts that connection linked, registered as Module.concat(name, Redix), a name no other process may hold (the start fails with {:error, {:connection_name_taken, name}}).

  • :namespace (String.t/0) - Required. The key namespace.

  • :ttl_ms (pos_integer/0) - Registration TTL in ms, refreshed every ttl_ms / 2; a dead node's keys lapse after it. The default value is 60000.