macula_stream_sessions (macula v11.4.0)

View Source

The node's count of served stream sessions, and of the bytes their streams keep unread.

A station link asks admit/2 before it serves a session it has started, naming the verified caller whose STREAM_OPEN the session serves. The session is admitted while that caller holds fewer than max_served_sessions_per_caller sessions on the node and the node fewer than max_served_sessions, both read from the macula application env when it asks. An admitted session holds its place until its stream process ends, however it ends: this process monitors it.

The caps are 16 per caller and 1000 on the node by default. A cap is kept per caller and not per link, because a link to a station carries every caller that station sends here: a cap per link would let one busy caller take the places of all the others. 16 leaves a caller room for many sessions at once, and it takes at least 63 callers to fill the node. The node cap bounds the processes all served sessions hold together, two for each session: its handler and its stream. A node that serves more sets its own value.

A served stream charges the bytes of each chunk it queues unread with charge/2, and gives them back with release/2 when a reader takes the chunk. Both run in the stream's own process against the table, so no chunk waits on another process. A charge is refused when it would take the bytes of the stream's caller past max_served_inbox_bytes_per_caller (16 MiB by default) or the node's past max_served_inbox_bytes (256 MiB by default). The caller budget is one stream's own bound, so a caller with many sessions keeps no more unread than one session may; the node budget is sixteen callers' worth. What a stream still has charged when its process ends comes back then, through this process's monitor.

A charge adds to the caller's count, then the node's, then the stream's own, and a release takes off in the opposite order; a refused add is taken back at once. So a stream that ends in the middle of one leaves at most that chunk counted too much, never too little, and this process clears what is left when the caller holds no session any more, and on the node when it holds none. Those clears run where a session ends, and a new session is admitted by this same process before its stream can charge anything.

The sessions and bytes are kept in a table that macula_stream_sessions_keeper holds, so they outlive this process. When it starts again it counts the kept sessions anew and monitors their streams, and a stream that ended in between frees its place and its bytes at once.

A refusal is counted by its reason: caller_limit or node_limit for a session, caller_budget or node_budget for a charge. A refusal is logged at once when no refusal was logged within the last served_session_refusal_log_interval_ms; the ones after it are logged together when that interval has passed.

Summary

Functions

Admit the session that Stream serves for the verified Caller, or refuse it when that caller or the node already holds as many sessions as its cap allows. A stream already admitted keeps its one place.

Charge Bytes that Stream keeps unread to its caller and the node, or refuse when that would take either past its budget, or when Stream is not an admitted session. Called in the stream's own process.

The bytes the node's served streams keep unread now.

Create the table the sessions are kept in, owned by the calling process. macula_stream_sessions_keeper creates it, so it outlives this process.

The refusals since the macula application started, by reason.

Give back Bytes that Stream charged and no longer keeps. Called in the stream's own process.

The number of served sessions the node holds now.

Types

refusal/0

-type refusal() :: caller_limit | node_limit | caller_budget | node_budget.

Functions

admit(Caller, Stream)

-spec admit(binary(), pid()) -> ok | {error, caller_limit | node_limit | unavailable}.

Admit the session that Stream serves for the verified Caller, or refuse it when that caller or the node already holds as many sessions as its cap allows. A stream already admitted keeps its one place.

Admission fails closed and never holds up the link that asks: when this process does not answer within a second, because it is restarting, gone or held up, the session is refused as unavailable.

charge(Stream, Bytes)

-spec charge(pid(), non_neg_integer()) -> ok | {error, caller_budget | node_budget | not_admitted}.

Charge Bytes that Stream keeps unread to its caller and the node, or refuse when that would take either past its budget, or when Stream is not an admitted session. Called in the stream's own process.

handle_call(_, From, S)

handle_cast(Msg, S)

handle_info(Info, S)

inbox_bytes()

-spec inbox_bytes() -> integer().

The bytes the node's served streams keep unread now.

init(_)

new_table()

-spec new_table() -> ets:table().

Create the table the sessions are kept in, owned by the calling process. macula_stream_sessions_keeper creates it, so it outlives this process.

refusals()

-spec refusals() -> #{refusal() => pos_integer()}.

The refusals since the macula application started, by reason.

release(Stream, Bytes)

-spec release(pid(), non_neg_integer()) -> ok.

Give back Bytes that Stream charged and no longer keeps. Called in the stream's own process.

sessions()

-spec sessions() -> non_neg_integer().

The number of served sessions the node holds now.

start_link()

-spec start_link() -> {ok, pid()} | {error, term()}.

terminate(Reason, S)