Skip to main content

LimitsConfig

Struct LimitsConfig 

Source
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: usize

Total live connections the listener admits before refusing (§5: 256 — a worker-bus, an order of magnitude above any observed fleet).

§max_subscriptions_per_connection: usize

Subscriptions one connection may hold (§5: 32).

§max_conversations_per_connection: usize

Open conversations one connection may hold (§5: 32).

§max_pending_pushes_per_connection: usize

In-flight server→client correlated pushes per connection (§5: 32).

§max_pending_conversation_replies_per_connection: usize

Entries in the per-connection pending-reply table (§1.2(3b)/§5: 32 — distinct from server-push slots).

§max_pending_replies_per_conversation: usize

Per-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: usize

One 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: usize

Per-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: usize

Per-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

Source

pub const DEFAULT_MAX_CONNECTIONS: usize = 256

§5 default: total live connections before the listener refuses.

Source

pub const DEFAULT_MAX_SUBSCRIPTIONS_PER_CONNECTION: usize = 32

§5 default: subscriptions per connection.

Source

pub const DEFAULT_MAX_CONVERSATIONS_PER_CONNECTION: usize = 32

§5 default: open conversations per connection.

Source

pub const DEFAULT_MAX_PENDING_PUSHES_PER_CONNECTION: usize = 32

§5 default: in-flight server pushes per connection.

Source

pub const DEFAULT_MAX_PENDING_CONVERSATION_REPLIES_PER_CONNECTION: usize = 32

§5 default: pending-reply table entries per connection.

Source

pub const DEFAULT_MAX_PENDING_REPLIES_PER_CONVERSATION: usize = 8

§5 default: per-conversation pending-reply sub-cap.

Source

pub const DEFAULT_MAX_CONNECTION_INBOX_BYTES: usize

§5 default: shared per-connection inbox byte budget (4 MiB).

Source

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:

depthcrossover record sizebinds first for a 1 KiB record
2564194304/256 = 16 KiBthe COUNT (at 256 KiB, 6% of 4 MiB)
40964194304/4096 = 1 KiBthe 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.

Source

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

Source§

fn clone(&self) -> LimitsConfig

Returns a duplicate of the value. Read more
1.0.0 (const: unstable) · Source§

fn clone_from(&mut self, source: &Self)

Performs copy-assignment from source. Read more
Source§

impl Copy for LimitsConfig

Source§

impl Debug for LimitsConfig

Source§

fn fmt(&self, f: &mut Formatter<'_>) -> Result

Formats the value using the given formatter. Read more
Source§

impl Default for LimitsConfig

Source§

fn default() -> Self

Returns the “default value” for a type. Read more
Source§

impl<'de> Deserialize<'de> for LimitsConfig

Source§

fn deserialize<__D>(__deserializer: __D) -> Result<Self, __D::Error>
where __D: Deserializer<'de>,

Deserialize this value from the given Serde deserializer. Read more

Auto Trait Implementations§

Blanket Implementations§

Source§

impl<T> Any for T
where T: 'static + ?Sized,

Source§

fn type_id(&self) -> TypeId

Gets the TypeId of self. Read more
Source§

impl<T> AsOut<T> for T
where T: Copy,

Source§

fn as_out(&mut self) -> Out<'_, T>

Returns an out reference to self.
Source§

impl<T> Borrow<T> for T
where T: ?Sized,

Source§

fn borrow(&self) -> &T

Immutably borrows from an owned value. Read more
Source§

impl<T> BorrowMut<T> for T
where T: ?Sized,

Source§

fn borrow_mut(&mut self) -> &mut T

Mutably borrows from an owned value. Read more
Source§

impl<ST, DT> CastableFrom<ST, Initialized, Initialized> for DT
where ST: ?Sized, DT: ?Sized,

Source§

impl<ST, DT> CastableFrom<ST, Uninit, Uninit> for DT
where ST: ?Sized, DT: ?Sized,

Source§

impl<T> CloneToUninit for T
where T: Clone,

Source§

unsafe fn clone_to_uninit(&self, dest: *mut u8)

🔬This is a nightly-only experimental API. (clone_to_uninit)
Performs copy-assignment from self to dest. Read more
Source§

impl<T> DeserializeOwned for T
where T: for<'de> Deserialize<'de>,

Source§

impl<T> From<T> for T

Source§

fn from(t: T) -> T

Returns the argument unchanged.

Source§

impl<T> Instrument for T

Source§

fn instrument(self, span: Span) -> Instrumented<Self>

Instruments this type with the provided Span, returning an Instrumented wrapper. Read more
Source§

fn in_current_span(self) -> Instrumented<Self>

Instruments this type with the current Span, returning an Instrumented wrapper. Read more
Source§

impl<T, U> Into<U> for T
where U: From<T>,

Source§

fn into(self) -> U

Calls U::from(self).

That is, this conversion is whatever the implementation of From<T> for U chooses to do.

Source§

impl<T> Pointable for T

Source§

const ALIGN: usize

The alignment of pointer.
Source§

type Init = T

The type for initializers.
Source§

unsafe fn init(init: <T as Pointable>::Init) -> usize

Initializes a with the given initializer. Read more
Source§

unsafe fn deref<'a>(ptr: usize) -> &'a T

Dereferences the given pointer. Read more
Source§

unsafe fn deref_mut<'a>(ptr: usize) -> &'a mut T

Mutably dereferences the given pointer. Read more
Source§

unsafe fn drop(ptr: usize)

Drops the object pointed to by the given pointer. Read more
Source§

impl<T> Read<Exclusive, BecauseExclusive> for T
where T: ?Sized,

Source§

impl<T> Same for T

Source§

type Output = T

Should always be Self
Source§

impl<T> ToOwned for T
where T: Clone,

Source§

type Owned = T

The resulting type after obtaining ownership.
Source§

fn to_owned(&self) -> T

Creates owned data from borrowed data, usually by cloning. Read more
Source§

fn clone_into(&self, target: &mut T)

Uses borrowed data to replace owned data, usually by cloning. Read more
Source§

impl<T, U> TryFrom<U> for T
where U: Into<T>,

Source§

type Error = Infallible

The type returned in the event of a conversion error.
Source§

fn try_from(value: U) -> Result<T, <T as TryFrom<U>>::Error>

Performs the conversion.
Source§

impl<T, U> TryInto<U> for T
where U: TryFrom<T>,

Source§

type Error = <U as TryFrom<T>>::Error

The type returned in the event of a conversion error.
Source§

fn try_into(self) -> Result<U, <U as TryFrom<T>>::Error>

Performs the conversion.
Source§

impl<T> WithSubscriber for T

Source§

fn with_subscriber<S>(self, subscriber: S) -> WithDispatch<Self>
where S: Into<Dispatch>,

Attaches the provided Subscriber to this type, returning a WithDispatch wrapper. Read more
Source§

fn with_current_subscriber(self) -> WithDispatch<Self>

Attaches the current default Subscriber to this type, returning a WithDispatch wrapper. Read more