# `ConduitMcp.Subscriptions.Admission`
[🔗](https://github.com/nyo16/conduit_mcp/blob/v0.11.0/lib/conduit_mcp/subscriptions/admission.ex#L1)

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.

# `listener`

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

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

# `admit`

```elixir
@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`

Returns a specification to start this module under a supervisor.

See `Supervisor`.

# `leave`

```elixir
@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`

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

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

---

*Consult [api-reference.md](api-reference.md) for complete listing*
