ConduitMcp.Subscriptions.Admission (ConduitMCP v0.11.0)

Copy Markdown View Source

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

listener()

@type listener() :: {non_neg_integer(), pid(), reference()}

A live stream: admission order, process, and the ref its messages carry.

Functions

admit(principal_key, max, ref)

@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.

child_spec(init_arg)

Returns a specification to start this module under a supervisor.

See Supervisor.

leave(principal_key)

@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.

live_listeners(principal_key)

@spec live_listeners(String.t()) :: [listener()]

The live (not superseded) streams of principal_key, oldest first.