pub struct LimitsConfig {
pub max_connections: usize,
pub max_subscriptions_per_connection: usize,
pub max_conversations_per_connection: usize,
pub max_pending_pushes_per_connection: usize,
pub max_pending_conversation_replies_per_connection: usize,
pub max_pending_replies_per_conversation: usize,
pub max_connection_inbox_bytes: usize,
pub max_subscription_inbox_depth: usize,
pub delivery_slice_budget: usize,
pub max_channels: Option<usize>,
}Expand description
Operational bounds (§5, scout Q4 — rule-2 items).
Each field is a hard per-scope cap with a typed refusal and a
certifying-pair-signed default (the numbers below are §5’s). The struct is
the single wire surface for [limits]; [LimitsConfig::validate] rejects any
zero value as a typed config error (a zero cap would gate nothing — the exact
unlimited-by-silence state §5 outlaws). Defaults come from the default_*
free functions so an absent key resolves to the signed number, not zero.
Fields§
§max_connections: usizeTotal live connections the listener admits before refusing (§5: 256 — a worker-bus, an order of magnitude above any observed fleet).
max_subscriptions_per_connection: usizeSubscriptions one connection may hold (§5: 32).
max_conversations_per_connection: usizeOpen conversations one connection may hold (§5: 32).
max_pending_pushes_per_connection: usizeIn-flight server→client correlated pushes per connection (§5: 32).
max_pending_conversation_replies_per_connection: usizeEntries in the per-connection pending-reply table (§1.2(3b)/§5: 32 — distinct from server-push slots).
max_pending_replies_per_conversation: usizePer-conversation sub-cap that confines tombstone ambiguity to its own conversation (§1.2(3b)/§5: 8). Pending entries count against BOTH this and the connection table; tombstones against THIS alone.
max_connection_inbox_bytes: usizeOne shared inbox-byte budget per connection, spent across ALL its subscription inboxes (§5: 4 MiB — deliberately mirroring the outbound 4 MiB bound). Accounting unit: serialized envelope bytes as admitted, charged at enqueue and released at dequeue.
max_subscription_inbox_depth: usizePer-inbox envelope-count secondary fairness trip — stops one subscription
starving its siblings inside the shared byte budget. See
LimitsConfig::DEFAULT_MAX_SUBSCRIPTION_INBOX_DEPTH for why the default
is what it is; the short version is that this cap is the CRUDE bound and
LimitsConfig::max_connection_inbox_bytes is the real one.
delivery_slice_budget: usizePer-slice cap on Deliver frames one connection may enqueue across all of
its subscriptions, before the scheduler moves on to its peers.
Operator-visible because P0 #55 proved it decides whether a subscriber
behind a burst survives — but see
LimitsConfig::DEFAULT_DELIVERY_SLICE_BUDGET for why the DEFAULT is not
raised. Raising it trades one starvation for another, and the measurement
that showed it moves the outcome could not see the starvation it causes.
max_channels: Option<usize>Runtime-registered channels this deployment admits.
§Why this cap departs the uniform pattern
Every other field here carries #[serde(default = "…")] naming a §5
constant, because each of those numbers is a signed §5 bound. There is
no signed §5 bound for channel count, and inventing one is barred: a
number nobody certified, presented in the same shape as eight numbers
somebody did, is a forged citation. So the type is Option<usize> and
the serde default is the ABSENCE itself (None), never a value —
#[serde(default)] here resolves a missing key to “no bound declared”,
which is a different statement from any number.
Nor may this be a usize with a large default: unbounded-by-default is
not a bound, it is the gap wearing a number.
None refuses every runtime channel registration with a typed error
naming this key, so a deployment that wants runtime registration declares
its own bound and a deployment that never registers needs no config
change at all. The cap bounds RUNTIME-registered channels only:
[[channels]] entries are the bound the operator already wrote.
Some(0) is a validation error like every other zero cap here — see
LimitsConfig::collect_errors.
Implementations§
Source§impl LimitsConfig
impl LimitsConfig
Sourcepub const DEFAULT_MAX_CONNECTIONS: usize = 256
pub const DEFAULT_MAX_CONNECTIONS: usize = 256
§5 default: total live connections before the listener refuses.
Sourcepub const DEFAULT_MAX_SUBSCRIPTIONS_PER_CONNECTION: usize = 32
pub const DEFAULT_MAX_SUBSCRIPTIONS_PER_CONNECTION: usize = 32
§5 default: subscriptions per connection.
Sourcepub const DEFAULT_MAX_CONVERSATIONS_PER_CONNECTION: usize = 32
pub const DEFAULT_MAX_CONVERSATIONS_PER_CONNECTION: usize = 32
§5 default: open conversations per connection.
Sourcepub const DEFAULT_MAX_PENDING_PUSHES_PER_CONNECTION: usize = 32
pub const DEFAULT_MAX_PENDING_PUSHES_PER_CONNECTION: usize = 32
§5 default: in-flight server pushes per connection.
Sourcepub const DEFAULT_MAX_PENDING_CONVERSATION_REPLIES_PER_CONNECTION: usize = 32
pub const DEFAULT_MAX_PENDING_CONVERSATION_REPLIES_PER_CONNECTION: usize = 32
§5 default: pending-reply table entries per connection.
Sourcepub const DEFAULT_MAX_PENDING_REPLIES_PER_CONVERSATION: usize = 8
pub const DEFAULT_MAX_PENDING_REPLIES_PER_CONVERSATION: usize = 8
§5 default: per-conversation pending-reply sub-cap.
Sourcepub const DEFAULT_MAX_CONNECTION_INBOX_BYTES: usize
pub const DEFAULT_MAX_CONNECTION_INBOX_BYTES: usize
§5 default: shared per-connection inbox byte budget (4 MiB).
Sourcepub const DEFAULT_MAX_SUBSCRIPTION_INBOX_DEPTH: usize = 4096
pub const DEFAULT_MAX_SUBSCRIPTION_INBOX_DEPTH: usize = 4096
Default per-inbox envelope-count fairness trip.
§Why 4096 and not the §5-era 256 (P0 #55)
A subscription inbox is bounded TWICE: by
Self::DEFAULT_MAX_CONNECTION_INBOX_BYTES (4 MiB, shared across all of
one connection’s inboxes) and by this envelope COUNT. Memory is the real
resource, so the byte budget is the bound that should bind and this one is
meant to be a fairness backstop behind it.
Which one binds first is decided by record size. For a connection holding a
single subscription, the crossover — the record size at which the two caps
trip together — is 4 MiB / depth:
| depth | crossover record size | binds first for a 1 KiB record |
|---|---|---|
| 256 | 4194304/256 = 16 KiB | the COUNT (at 256 KiB, 6% of 4 MiB) |
| 4096 | 4194304/4096 = 1 KiB | the BYTES (as intended) |
At 256 the count cap therefore binds for every record smaller than 16 KiB — which is nearly all real traffic — and it binds at a small fraction of the memory the connection is actually permitted: at a 164-byte record, 256 envelopes is 41 KiB, ~1% of the 4 MiB budget. A replay burst is thousands of SMALL records, which is precisely the shape that hits the crude bound while the real bound sits untouched. That is the P0: a live subscriber shed for exceeding a fairness trip it reached at 1% of its memory allowance.
At 4096 the crossover falls to 1 KiB, so bytes bind for realistic records and this cap returns to backstop duty.
Honest bound on that arithmetic: the byte budget is per CONNECTION and this
cap is per SUBSCRIPTION, so with S subscriptions sharing one connection
the effective crossover is 4 MiB / S / depth. The table above is the S=1
case. A connection holding many subscriptions of small records can still
reach the count cap first — which is exactly the sibling-starvation case
this cap exists to catch.
Sourcepub const DEFAULT_DELIVERY_SLICE_BUDGET: usize = 32
pub const DEFAULT_DELIVERY_SLICE_BUDGET: usize = 32
Default per-slice Deliver budget for one connection — the source of truth
for crate::config::LimitsConfig::delivery_slice_budget and for the
delivery pump’s own DELIVERY_SLICE_BUDGET.
§Why this is NOT raised (P0 #55)
This is the cross-connection FAIRNESS bound: it caps how long one connection may hold a shared scheduler thread before its peers get a turn. Raising it makes a burst subscriber’s own drain finish in fewer scheduling round trips, and the P0 #55 2x2 measured exactly that — 0/192 boots lost a subscriber at 256 against roughly half of them at 32.
That experiment does not license raising the default. It ran two or three connections, so the peer starvation a raise would CAUSE was unobservable BY CONSTRUCTION: with no queue of waiting peers there is nothing for a longer slice to delay. What was measured is that this knob moves the outcome, not that moving it is safe.
So the number stays 32 and becomes an operator DECISION instead: a
deployment that knows its connection count and its burst shape can raise it
deliberately. The default does not make that trade on anyone’s behalf.
The P0’s actual fix is Self::DEFAULT_MAX_SUBSCRIPTION_INBOX_DEPTH.
Trait Implementations§
Source§impl Clone for LimitsConfig
impl Clone for LimitsConfig
Source§fn clone(&self) -> LimitsConfig
fn clone(&self) -> LimitsConfig
1.0.0 (const: unstable) · Source§fn clone_from(&mut self, source: &Self)
fn clone_from(&mut self, source: &Self)
source. Read more