v0.3.0
0.3.0
This release replaces the :pg backend with PgRegistry.Pg — a
self-contained Elixir port of OTP-27 pg.erl extended with
per-entry metadata — and grows the public surface to roughly match
Elixir's Registry.
Backwards-incompatible changes
-
Backend swap.
PgRegistryno longer uses Erlang:pgunder
the hood. Anyone reaching past the public API into
:pg.get_local_members(scope, key)(or similar) must switch to
PgRegistry.Pg.get_local_members/2. The two storage worlds are
now disjoint — a scope started byPgRegistry.start_link/1lives
inPgRegistry.Pg's ETS table, not in:pg's. -
Wire format.
PgRegistry.Pguses its own wire format and is
no longer compatible with:pg. A protocol-version handshake
(@protocol_version 1) was added — peers running mismatched
versions refuse to peer with aLogger.warningrather than
silently corrupting state. Bumping the constant on any future
wire change is the migration story. -
register_name/2may now return:no. In a scope started
withkeys: :unique, registering under an already-held key
returns:nosoGenServer.start_link(name: {:via, ...})
surfaces{:error, {:already_started, pid}}automatically.
In:duplicatemode (the default) the return is unchanged. -
Pg.joinmay return{:error, {:already_registered, pid}}
in unique mode.
New: per-entry metadata
{:via, PgRegistry, {scope, key, value}}— 3-tuple via name
attaches a value at registration time.PgRegistry.lookup/2—[{pid, value}], the metadata-aware view.PgRegistry.update_value/3andupdate_value/4— replace the
value of self()'s entry, or any local pid's entry.Pg.update_meta/4— lower-level form, broadcasts a{:update_meta, ...}
message so the change reaches every node.- Subscription messages grow a third verb
:updatecarrying
[{pid, old_meta, new_meta}].
New: Registry-shaped API
register/3,unregister/2— self()-shaped wrappers.lookup/2,lookup_local/2,values/3,keys/2,count/1.match/3,match/4,count_match/3,count_match/4.select/2,count_select/2— user-supplied match-specs against
{key, pid, value}triples, translated to operate on the 4-tuple
storage shape internally and run as native ETS queries.unregister_match/3,unregister_match/4.meta/2,put_meta/3,delete_meta/2— local-only registry
metadata stored in a sibling ETS table named:"#{scope}_meta".dispatch/4(with an_optsaccumulator argument for Registry
compat).monitor_scope/1,monitor/2,demonitor/2— runtime
subscription with refs.
New: listeners
start_link(name: :reg, listeners: [MyListener])— Registry-shaped
listener configuration. Listeners receive raw
{:register, scope, key, pid, value}and
{:unregister, scope, key, pid}messages on join/leave events
(matchingRegistry's contract). Local-only; addressed by
registered name; missing names are silently dropped rather than
crashing the scope.
New: per-node :unique mode
start_link(name: :reg, keys: :unique)— each node enforces local
uniqueness, but the cluster can still have one holder per node.
Use this for "one singleton per node" patterns. For cluster-wide
unique names, use:global.
New: Registry-shaped keyword start_link
PgRegistry.start_link(name: :reg, listeners: [...], keys: :duplicate)
for users porting from Registry.:keys(:duplicateor
:unique) and:partitions(must be1) are validated and
raiseArgumentErrorwith helpful messages on unsupported values.
Unknown options also raise. The same validation runs in the
2-aritystart_link/2form.
Bug fixes
unregister_matchdeleted the LIFO head of self()'s entries
rather than the entry that matched the pattern. Fixed by adding
a gen-server-side primitive that deletes by exact tag.count_selectover-counted when the user's match-spec body
filtered some matches out (we were stripping the body).count/1was O(N×M); now uses:ets.info(scope, :size).- A missing/crashed listener atom no longer crashes the scope —
delivery usesProcess.whereis/1first. - The local DOWN handler no longer fabricates a
nil-meta leave
notification when the ETS row is missing during cleanup. keys: :uniqueregistration now skips dead holders, closing the
race where a:DOWNfor the previous holder was queued in the
GenServer mailbox but hadn't been processed yet.Pg.leave(scope, key, [])returns:okinstead of:not_joined.
Known limitations
Pg.unregister_matchand friends are by-tag exact deletes, but
sync-driven:updatenotifications after a netsplit are still
not emitted (state converges correctly, only the notification
stream during convergence is incomplete).- No
lock/3. No cluster-wide unique keys. Nopartitions: > 1.
Each is a deliberate design decision; see the README.