dgen_registry_member (DGen v0.3.0)

Copy Markdown View Source

Local name cache and consistent read/write proxy for dgen_registry.

Each node participating in a named registry runs one member process. The member has two roles depending on whether it is the current leader.

Storage

Both leader and follower keep a names :: #{LogicalName => pid()} map in their gen_server state. There is no ETS table and no durable storage for name→pid mappings. Pids are node-local and process-lifetime-scoped; they have no meaning after a restart and must never be written to the backend.

Consistent reads and writes go through the leader. Snapshot reads (whereis_snapshot) are served from the local member's map without contacting the leader.

Follower role

Keeps its local names map in sync by receiving {name_registered, …}, {name_unregistered, …}, and {apply_names_snapshot, …} casts from the leader (one-way replication). All follower messages come from the leader process, so Erlang's per-pair FIFO guarantee ensures followers always see a snapshot before any {name_registered} broadcast that post-dates it.

Partition recovery

The member subscribes to nodeup/nodedown events via net_kernel:monitor_nodes/1. On {nodeup, Node}, the member re-announces itself to the elector ({join, Self}). This handles the case where an Erlang-level network partition caused both sides to remove each other from the member set via {member_down} while the DB remained healthy: once the partition heals and distribution reconnects, both sides re-join and the elector reconstitutes the cluster without requiring a restart.

Forwards {register, …} calls and {unregister, …} casts to the leader. For register, the follower also updates its own names map on receiving yes from the leader, so a subsequent whereis_snapshot on this node reflects the change without waiting for the replication cast. On no the local map is left unchanged.

Leader role

Assumed when the elector calls {elector_assume_and_distribute, …}. On assuming leadership the member uses the supplied snapshot (or its own names map if the snapshot is self_snapshot), sets up erlang:monitor/2 for every entry, and distributes {apply_names_snapshot} casts to all followers from its own process (same sender as future {name_registered} broadcasts — see elector moduledoc for the FIFO ordering guarantee). Any stale Pid entries are removed when their DOWN signals arrive.

The leader is the sole writer for the name table. It:

  • Handles {register, LogicalName, Pid} calls: checks the in-memory map, updates it, monitors the Pid, and replicates {name_registered, …}.
  • Handles {whereis, LogicalName} calls: consistent read from local map.
  • Handles {unregister, LogicalName} casts: updates the map, demonitors, and replicates {name_unregistered, …}.
  • Monitors every registered Pid. When one dies, removes from the map and replicates {name_unregistered, …} to followers.

On relinquishing leadership the member demonitors all registered Pids and clears the leader-only state. The names map is kept intact (it still serves snapshot reads).

Failure model

Name-to-pid mappings are intentionally not stored in the backend. As a result:

  • Leader crash: the new leader starts with self_snapshot — its own in-memory follower replica. Any registrations the dead leader committed to its map but had not yet broadcast to followers are silently lost. A caller that received yes from register_name/2 may find the name absent after a leader failover. Re-registration after detecting the loss is the caller's responsibility.

  • Full cluster restart: all registered names are lost. Applications must re-register on startup.

Summary

Functions

code_change(OldVsn, State, Extra)

handle_call/3

handle_cast/2

handle_info/2

init/1

start_link(Name, Args)

-spec start_link(Name :: atom(), Args :: map()) -> gen_server:start_ret().

Starts the member process registered as Name.

terminate(Reason, State)