arizona_pubsub (arizona v0.4.0)

View Source

Thin wrapper over pg for channel-based pub/sub.

Subscribers join a channel (any term) by pid and receive whatever is broadcast to that channel as a plain mailbox message. There's no serialization or queueing -- it's just pg:join/3 plus a fan-out send.

The broadcast paths iterate pg's raw membership list without sorting or deduplicating -- a per-message usort over the whole member list would tax every broadcast (and every subscribe) to paper over a state only two concurrent subscribes of the SAME pid can create (subscribe/2's check-then-join is not atomic). In that essentially-unreachable state a subscriber receives one copy per membership until unsubscribe/2 clears them all; subscribers/1 still reports each pid once.

Used by arizona_reloader_rule (file change events), arizona_reloader (dev hot reload), and any handler that wants cross-process messaging (e.g. multi-tab chat in arizona_chat).

Example

1> arizona_pubsub:start_link().
2> arizona_pubsub:subscribe(my_topic, self()).
ok
3> arizona_pubsub:broadcast(my_topic, {hello, world}).
ok
4> receive Msg -> Msg end.
{hello, world}

Summary

Functions

Sends Data as a mailbox message to every subscriber of Channel (one copy per pg membership -- see the moduledoc).

Like broadcast/2 but skips From -- useful when the publisher is also a subscriber and shouldn't echo to itself.

Subscribes the caller to membership changes on Channel. It begins receiving {Ref, join, Channel, Pids} and {Ref, leave, Channel, Pids} messages as pids join or leave -- a leave fires on a subscriber's process death too -- and returns the monitor reference plus the current members. The monitor is removed automatically when the caller exits.

Starts the pubsub pg scope. Called from the application supervisor.

Subscribes Pid to Channel. Returns {error, already_joined} if the pid is already subscribed.

Returns the pids currently subscribed to Channel, each once.

Unsubscribes Pid from Channel. Returns {error, not_joined} if the pid was not a subscriber.

Types

channel()

-nominal channel() :: term().

Functions

broadcast(Channel, Data)

-spec broadcast(Channel, Data) -> ok when Channel :: channel(), Data :: term().

Sends Data as a mailbox message to every subscriber of Channel (one copy per pg membership -- see the moduledoc).

broadcast_from(From, Channel, Data)

-spec broadcast_from(From, Channel, Data) -> ok when From :: pid(), Channel :: channel(), Data :: term().

Like broadcast/2 but skips From -- useful when the publisher is also a subscriber and shouldn't echo to itself.

monitor(Channel)

-spec monitor(Channel) -> {reference(), [pid()]} when Channel :: channel().

Subscribes the caller to membership changes on Channel. It begins receiving {Ref, join, Channel, Pids} and {Ref, leave, Channel, Pids} messages as pids join or leave -- a leave fires on a subscriber's process death too -- and returns the monitor reference plus the current members. The monitor is removed automatically when the caller exits.

Useful for presence, e.g. a view that shows a live count of connected peers.

start_link()

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

Starts the pubsub pg scope. Called from the application supervisor.

subscribe(Channel, Pid)

-spec subscribe(Channel, Pid) -> ok | {error, already_joined} when Channel :: channel(), Pid :: pid().

Subscribes Pid to Channel. Returns {error, already_joined} if the pid is already subscribed.

The membership check is a read followed by a join, so two concurrent subscribes of the same pid can both return ok and both reach pg (which has no atomic join-if-absent). subscribers/1 reports each pid once regardless, and unsubscribe/2 drops every pg membership the pid holds rather than leaving a stray one behind; until then a broadcast delivers one copy per membership (see the moduledoc).

subscribers(Channel)

-spec subscribers(Channel) -> [pid()] when Channel :: channel().

Returns the pids currently subscribed to Channel, each once.

pg memberships are a multiset and subscribe/2's check-then-join is not atomic, so a pid can hold more than one membership; this diagnostic view deduplicates. The broadcast paths deliberately do not (see the moduledoc), so they never pay this sort.

unsubscribe(Channel, Pid)

-spec unsubscribe(Channel, Pid) -> ok | {error, not_joined} when Channel :: channel(), Pid :: pid().

Unsubscribes Pid from Channel. Returns {error, not_joined} if the pid was not a subscriber.