minato_listener (minato v0.18.6)
View SourceLISTEN on a connection of its own, delivered to subscribing processes.
A notification arrives when the server decides, not when a caller asks, so
somebody has to be holding the connection and reading it. That is what this
process is. It owns one connection, keeps it under minato_conn:activate/1 so
the bytes arrive as messages, and sends what it decodes to whoever subscribed.
ok = minato_listener:listen(events, ~"job_ready"),
receive
{minato_notification, ~"job_ready", Payload, _Pid} -> Payload
end.The connection is this process's and nothing else runs on it. LISTEN is
session state: a notification is delivered to the session that registered for
it, so a pooled connection would deliver to whichever caller happened to hold it
next, which is to say nowhere.
Subscribers
Subscribing is per process, and the process is monitored: a subscriber that dies
is dropped, and a channel nobody is left listening to is UNLISTENed. Otherwise
a supervisor restarting a worker in a loop leaves the server delivering to a
session where nothing is waiting.
Notifications get missed
A connection that drops takes its subscriptions with it, and anything the server
published while it was gone is gone: NOTIFY is fire and forget, with no
replay. This process reconnects with backoff, LISTENs again for every channel
that still has a subscriber, and then tells each subscriber
{minato_listener, Name, resubscribed}.
That message is the point. A consumer that treats notifications as the only way
it learns anything will silently stall; one that is told it was disconnected can
go and look. NOTIFY is a hint that something changed, and a poll it makes
faster - never the only path.
Summary
Functions
Every channel with a subscriber, and how many each has.
Subscribe the calling process to a channel.
Start a listener under a supervisor.
Stop the listener and close its connection.
Stop the calling process's subscription.
Types
-type name() :: atom().
How the listener is registered and asked for.
-type notification() :: {minato_notification, Channel :: binary(), Payload :: binary(), From :: integer()}.
What a subscriber receives.
The payload is whatever NOTIFY was given, and the pid is the server side
process that sent it, which is how a subscriber tells its own writes from
somebody else's.
-type opts() :: #{connection := minato_conn:opts()}.
What to connect to.
#{connection := minato_conn:opts()}Passed to minato_conn:connect/1 unchanged. One connection, held for the life
of the listener.
Functions
-spec channels(name()) -> #{binary() => non_neg_integer()}.
Every channel with a subscriber, and how many each has.
-spec handle_call(request(), gen_server:from(), #state{name :: name(), opts :: minato_conn:opts(), conn :: minato_conn:conn() | undefined, channels :: #{binary() => #{pid() => reference()}}, backoff :: pos_integer()}) -> {reply, term(), #state{name :: name(), opts :: minato_conn:opts(), conn :: minato_conn:conn() | undefined, channels :: #{binary() => #{pid() => reference()}}, backoff :: pos_integer()}}.
-spec handle_cast(term(), #state{name :: name(), opts :: minato_conn:opts(), conn :: minato_conn:conn() | undefined, channels :: #{binary() => #{pid() => reference()}}, backoff :: pos_integer()}) -> {noreply, #state{name :: name(), opts :: minato_conn:opts(), conn :: minato_conn:conn() | undefined, channels :: #{binary() => #{pid() => reference()}}, backoff :: pos_integer()}}.
-spec handle_info(info(), #state{name :: name(), opts :: minato_conn:opts(), conn :: minato_conn:conn() | undefined, channels :: #{binary() => #{pid() => reference()}}, backoff :: pos_integer()}) -> {noreply, #state{name :: name(), opts :: minato_conn:opts(), conn :: minato_conn:conn() | undefined, channels :: #{binary() => #{pid() => reference()}}, backoff :: pos_integer()}}.
-spec init({name(), opts()}) -> {ok, #state{name :: name(), opts :: minato_conn:opts(), conn :: minato_conn:conn() | undefined, channels :: #{binary() => #{pid() => reference()}}, backoff :: pos_integer()}}.
Subscribe the calling process to a channel.
Answers ok once the server has acknowledged the LISTEN, so a caller that
gets ok is registered rather than about to be. A channel already listened to
costs no round trip.
The name is quoted rather than interpolated, so a channel name from outside is a channel name and not a statement.
-spec start_link(name(), opts()) -> gen_server:start_ret().
Start a listener under a supervisor.
-spec stop(name()) -> ok.
Stop the listener and close its connection.
-spec terminate(term(), #state{name :: name(), opts :: minato_conn:opts(), conn :: minato_conn:conn() | undefined, channels :: #{binary() => #{pid() => reference()}}, backoff :: pos_integer()}) -> ok.
Stop the calling process's subscription.
The channel is UNLISTENed when the last subscriber to it goes.