1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
/// 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.
#[derive(Debug, Clone, Copy, serde::Deserialize)]
#[serde(deny_unknown_fields)]
pub struct LimitsConfig {
/// Total live connections the listener admits before refusing (§5: 256 — a
/// worker-bus, an order of magnitude above any observed fleet).
#[serde(default = "default_max_connections")]
pub max_connections: usize,
/// Subscriptions one connection may hold (§5: 32).
#[serde(default = "default_max_subscriptions_per_connection")]
pub max_subscriptions_per_connection: usize,
/// Open conversations one connection may hold (§5: 32).
#[serde(default = "default_max_conversations_per_connection")]
pub max_conversations_per_connection: usize,
/// In-flight server→client correlated pushes per connection (§5: 32).
#[serde(default = "default_max_pending_pushes_per_connection")]
pub max_pending_pushes_per_connection: usize,
/// Entries in the per-connection pending-reply table (§1.2(3b)/§5: 32 —
/// distinct from server-push slots).
#[serde(default = "default_max_pending_conversation_replies_per_connection")]
pub max_pending_conversation_replies_per_connection: 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.
#[serde(default = "default_max_pending_replies_per_conversation")]
pub max_pending_replies_per_conversation: 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.
#[serde(default = "default_max_connection_inbox_bytes")]
pub max_connection_inbox_bytes: usize,
/// One outbound byte buffer per connection, holding every server-originated
/// frame (acks, errors, `Push`, `Deliver`, `Disconnect`, `Pong`) until the
/// connection's scheduler slice drains it to the socket.
///
/// This is a DELIVERY bound, not a fairness one: a single frame larger than
/// the whole buffer can never be queued, so this value is the largest frame
/// the server can send on one connection. Raising it raises that ceiling at
/// the cost of per-connection memory; lowering it lowers the ceiling for
/// every connection at once.
#[serde(default = "default_max_connection_outbound_bytes")]
pub max_connection_outbound_bytes: 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.
#[serde(default = "default_max_subscription_inbox_depth")]
pub max_subscription_inbox_depth: 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.
#[serde(default = "default_delivery_slice_budget")]
pub delivery_slice_budget: 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`].
#[serde(default)]
pub max_channels: Option<usize>,
/// How long a client's correlated conversation request waits for its reply
/// before the server answers it timed out, in milliseconds (default 5,000).
/// A zero would time every request out on its first slice.
#[serde(default = "default_reply_timeout_ms")]
pub reply_timeout_ms: u64,
}
impl LimitsConfig {
/// §5 default: total live connections before the listener refuses.
pub const DEFAULT_MAX_CONNECTIONS: usize = 256;
/// §5 default: subscriptions per connection.
pub const DEFAULT_MAX_SUBSCRIPTIONS_PER_CONNECTION: usize = 32;
/// §5 default: open conversations per connection.
pub const DEFAULT_MAX_CONVERSATIONS_PER_CONNECTION: usize = 32;
/// §5 default: in-flight server pushes per connection.
pub const DEFAULT_MAX_PENDING_PUSHES_PER_CONNECTION: usize = 32;
/// §5 default: pending-reply table entries per connection.
pub const DEFAULT_MAX_PENDING_CONVERSATION_REPLIES_PER_CONNECTION: usize = 32;
/// §5 default: per-conversation pending-reply sub-cap.
pub const DEFAULT_MAX_PENDING_REPLIES_PER_CONVERSATION: usize = 8;
/// §5 default: shared per-connection inbox byte budget (4 MiB).
pub const DEFAULT_MAX_CONNECTION_INBOX_BYTES: usize = 4 * 1024 * 1024;
/// Default per-connection OUTBOUND byte budget (4 MiB) — the twin the inbound
/// budget was always described as mirroring, now declared in its own right.
///
/// It is the honest single-frame delivery bound: a frame up to this size is
/// delivered intact across as many scheduler slices as the peer's read rate
/// needs, and a frame LARGER than the whole buffer can never be queued at all.
/// The number is unchanged from the buffer's former private constant; what
/// changes is that an operator can now see it and set it, instead of meeting
/// it only as a connection that stopped carrying something.
pub const DEFAULT_MAX_CONNECTION_OUTBOUND_BYTES: usize = 4 * 1024 * 1024;
/// 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.
pub const DEFAULT_MAX_SUBSCRIPTION_INBOX_DEPTH: usize = 4096;
/// 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`].
pub const DEFAULT_DELIVERY_SLICE_BUDGET: usize = 32;
/// Default wait for a correlated conversation reply: 5 s, the bound a
/// client has always been answered within.
pub const DEFAULT_REPLY_TIMEOUT_MS: u64 = 5_000;
/// Validates the caps: every value must be non-zero (a zero cap gates nothing
/// — the unlimited-by-silence state §5 outlaws). Errors are accumulated into
/// `errors` (one per offending field) so an operator sees every bad cap at
/// once, matching the rest of config validation.
pub(crate) fn collect_errors(&self, errors: &mut Vec<String>) {
let checks: [(&str, usize); 10] = [
("max_connections", self.max_connections),
(
"max_subscriptions_per_connection",
self.max_subscriptions_per_connection,
),
(
"max_conversations_per_connection",
self.max_conversations_per_connection,
),
(
"max_pending_pushes_per_connection",
self.max_pending_pushes_per_connection,
),
(
"max_pending_conversation_replies_per_connection",
self.max_pending_conversation_replies_per_connection,
),
(
"max_pending_replies_per_conversation",
self.max_pending_replies_per_conversation,
),
(
"max_connection_inbox_bytes",
self.max_connection_inbox_bytes,
),
// A zero here would leave a connection unable to queue ANY frame at
// all — every push and every delivery refused on its first byte.
(
"max_connection_outbound_bytes",
self.max_connection_outbound_bytes,
),
(
"max_subscription_inbox_depth",
self.max_subscription_inbox_depth,
),
// A zero here would make the delivery pump enqueue nothing on any
// slice — every subscription silently stalled forever, which is the
// unlimited-by-silence failure wearing the opposite sign.
("delivery_slice_budget", self.delivery_slice_budget),
];
for (field, value) in checks {
if value == 0 {
errors.push(zero_cap_error(field));
}
}
// `max_channels` is checked BESIDE the array rather than inside it,
// because it is the one optional cap and the array's element type has no
// way to say "absent". Forcing it in would need a stand-in value for
// `None` — a number standing where a declaration is not — and any
// stand-in would either invent a bound or make an undeclared cap look
// declared. So the shape stays honest and the RULE stays uniform: a
// declared zero is refused with the same message as the other eight.
//
// The parenthetical about unlimited-by-silence is the other eight caps'
// ground; for this one a zero would refuse every registration rather
// than admit every registration. The message is kept identical anyway:
// an operator reading nine cap errors should learn one rule — zero is
// not a legal cap value anywhere in this section — not two.
if self.max_channels == Some(0) {
errors.push(zero_cap_error("max_channels"));
}
// A duration, not a count, so it sits beside the array too; zero is
// refused by the same rule.
if self.reply_timeout_ms == 0 {
errors.push(zero_cap_error("reply_timeout_ms"));
}
}
}
/// The typed refusal for a zero-valued cap, shared by every field in `[limits]`
/// so the wording cannot drift between them.
fn zero_cap_error(field: &str) -> String {
format!(
"limits.{field}: must be greater than zero (a zero cap would be \
unlimited-by-silence, which §5 forbids)"
)
}
impl Default for LimitsConfig {
fn default() -> Self {
Self {
max_connections: default_max_connections(),
max_subscriptions_per_connection: default_max_subscriptions_per_connection(),
max_conversations_per_connection: default_max_conversations_per_connection(),
max_pending_pushes_per_connection: default_max_pending_pushes_per_connection(),
max_pending_conversation_replies_per_connection:
default_max_pending_conversation_replies_per_connection(),
max_pending_replies_per_conversation: default_max_pending_replies_per_conversation(),
max_connection_inbox_bytes: default_max_connection_inbox_bytes(),
max_connection_outbound_bytes: default_max_connection_outbound_bytes(),
max_subscription_inbox_depth: default_max_subscription_inbox_depth(),
delivery_slice_budget: default_delivery_slice_budget(),
// No signed bound exists for this cap, so the default is the
// absence of a declaration — never a number.
max_channels: None,
reply_timeout_ms: default_reply_timeout_ms(),
}
}
}
const fn default_max_connections() -> usize {
LimitsConfig::DEFAULT_MAX_CONNECTIONS
}
const fn default_max_subscriptions_per_connection() -> usize {
LimitsConfig::DEFAULT_MAX_SUBSCRIPTIONS_PER_CONNECTION
}
const fn default_max_conversations_per_connection() -> usize {
LimitsConfig::DEFAULT_MAX_CONVERSATIONS_PER_CONNECTION
}
const fn default_max_pending_pushes_per_connection() -> usize {
LimitsConfig::DEFAULT_MAX_PENDING_PUSHES_PER_CONNECTION
}
const fn default_max_pending_conversation_replies_per_connection() -> usize {
LimitsConfig::DEFAULT_MAX_PENDING_CONVERSATION_REPLIES_PER_CONNECTION
}
const fn default_max_pending_replies_per_conversation() -> usize {
LimitsConfig::DEFAULT_MAX_PENDING_REPLIES_PER_CONVERSATION
}
const fn default_max_connection_inbox_bytes() -> usize {
LimitsConfig::DEFAULT_MAX_CONNECTION_INBOX_BYTES
}
const fn default_max_connection_outbound_bytes() -> usize {
LimitsConfig::DEFAULT_MAX_CONNECTION_OUTBOUND_BYTES
}
const fn default_max_subscription_inbox_depth() -> usize {
LimitsConfig::DEFAULT_MAX_SUBSCRIPTION_INBOX_DEPTH
}
const fn default_delivery_slice_budget() -> usize {
LimitsConfig::DEFAULT_DELIVERY_SLICE_BUDGET
}
const fn default_reply_timeout_ms() -> u64 {
LimitsConfig::DEFAULT_REPLY_TIMEOUT_MS
}