The node-local bookkeeping behind :max_listens_per_principal: which
subscriptions/listen streams of each principal are live, oldest first.
Started by ConduitMcp.Application; streams reach it through
ConduitMcp.Subscriptions (admit/3, leave/2, live_listeners/1).
A stream is admitted with one GenServer.call/2. In that one message the
process counts the principal's live streams, supersedes the oldest beyond
the cap - removing them from the live set at once, so they are never
counted or evicted twice, and sending each
{ConduitMcp.Subscriptions, ref, :superseded} with its own ref - and
records the caller. Admissions are answered in arrival order, so even a
burst of concurrent listens of one principal never holds more than the cap
live (SV-10). A stream that ends removes its entry (leave/1, a cast:
under HTTP/1.1 keep-alive the same process goes on to serve the
connection's next request, and its next admission queues behind it); a
stream that dies is dropped on its monitor's :DOWN.
Why a process you call
The library's ETS owners create a table and idle, and must not become
handle_call gateways: their tables are read and written on every
request, so a single process in front of them would serialize the whole
server. Admission is the opposite case. It happens once per listen opened,
not per message; it needs count, evict and record to be one atomic step
per principal; and the state is small (at most the cap per principal).
The alternative - a lock around ETS updates, such as :global.trans/3 -
retries contended acquisitions with random sleeps of up to seconds, so a
burst of listens of one principal waited seconds for what this process
does in microseconds, with no lock and no backoff. The process runs no
user code: each request is a few map updates and message sends.
Summary
Types
A live stream: admission order, process, and the ref its messages carry.
Functions
Admits the calling process as a stream of principal_key tagged ref,
superseding the principal's oldest live streams so that at most max
are live, this one included.
Returns a specification to start this module under a supervisor.
Removes the calling process's streams of principal_key. A no-op for a
process that holds none, or when the admission process is not running.
The live (not superseded) streams of principal_key, oldest first.
Types
@type listener() :: {non_neg_integer(), pid(), reference()}
A live stream: admission order, process, and the ref its messages carry.
Functions
@spec admit(String.t(), pos_integer(), reference()) :: :ok
Admits the calling process as a stream of principal_key tagged ref,
superseding the principal's oldest live streams so that at most max
are live, this one included.
Returns a specification to start this module under a supervisor.
See Supervisor.
@spec leave(String.t()) :: :ok
Removes the calling process's streams of principal_key. A no-op for a
process that holds none, or when the admission process is not running.
The live (not superseded) streams of principal_key, oldest first.