flybrain/services/flysim/crates/flybus/src/limits.rs
acamilo 093db147eb feat(flybus): add standalone IPC bus and conformance tests
(cherry picked from commit 95ac8d1bdbc866a7c49fad3ddba468d945267650)
2026-09-22 03:46:27 +00:00

183 lines
7.9 KiB
Rust

//! Router limits (bus-v1 section 9). The defaults are the draft's prototype starting point,
//! not capacity data.
use serde_json::{Map, Value};
use crate::wire::{Fields, MAX_ATTACHMENTS, MAX_BATCH, MAX_CREDIT, MAX_ENVELOPE_BYTES, WireError};
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct Limits {
pub max_clients: usize,
pub max_services: usize,
pub max_topics: usize,
pub max_subscriptions_per_client: usize,
pub max_subscriptions: usize,
/// Calls a client may have admitted and not yet finished (result consumed, failed or
/// cancelled before dispatch). A call detached by a post-dispatch cancel no longer counts.
pub max_active_calls_per_client: usize,
/// Upper bounds on what `service.register` may ask for.
pub max_service_queued: u64,
pub max_service_in_flight: u64,
/// Upper bound on a `latest` subscription's credits; its queue is always exactly 1.
pub max_latest_in_flight: u64,
pub max_bounded_queued: u64,
pub max_bounded_in_flight: u64,
/// Owners (deliveries handed to the client and not consumed, explicit holds and staging
/// writers) a connection may have at once.
pub max_owners_per_client: usize,
/// Of those, how many only RPC request/result deliveries may use. Topic deliveries, holds
/// and writers are refused (holds, writers) or wait (deliveries) beyond
/// `max_owners_per_client - reserved_owners_per_client`.
pub reserved_owners_per_client: usize,
/// Bytes of staging, sealing copies and sealed objects the store may hold.
pub max_store_bytes: u64,
pub max_artifact_bytes: u64,
/// Artifact bytes pinned by retained topic values, counted once per topic.
pub max_retained_bytes: u64,
/// Envelope bytes queued for a client's `bounded` subscriptions and not yet delivered.
pub max_queued_bytes_per_client: usize,
/// Replies and notices waiting to be written to one client. Exceeding either closes it.
pub max_control_frames: usize,
pub max_control_bytes: usize,
}
impl Default for Limits {
fn default() -> Limits {
Limits {
max_clients: 64,
max_services: 256,
max_topics: 512,
max_subscriptions_per_client: 128,
max_subscriptions: 1024,
max_active_calls_per_client: 64,
max_service_queued: 16,
max_service_in_flight: 16,
max_latest_in_flight: 2,
max_bounded_queued: 64,
max_bounded_in_flight: 16,
max_owners_per_client: 256,
reserved_owners_per_client: 64,
max_store_bytes: 512 << 20,
max_artifact_bytes: 128 << 20,
max_retained_bytes: 128 << 20,
max_queued_bytes_per_client: 1 << 20,
max_control_frames: 128,
max_control_bytes: 1 << 20,
}
}
}
impl Limits {
/// Refuses a configuration the router could not honour.
pub fn validate(&self) -> Result<(), String> {
let credits = [
("max_service_queued", self.max_service_queued),
("max_service_in_flight", self.max_service_in_flight),
("max_latest_in_flight", self.max_latest_in_flight),
("max_bounded_queued", self.max_bounded_queued),
("max_bounded_in_flight", self.max_bounded_in_flight),
];
for (name, v) in credits {
if !(1..=MAX_CREDIT).contains(&v) {
return Err(format!("{name} must be in 1..={MAX_CREDIT}"));
}
}
if self.reserved_owners_per_client >= self.max_owners_per_client {
return Err("reserved_owners_per_client must be below max_owners_per_client".into());
}
if self.max_artifact_bytes > self.max_store_bytes {
return Err("max_artifact_bytes must not exceed max_store_bytes".into());
}
if self.max_control_frames == 0 || self.max_control_bytes < MAX_ENVELOPE_BYTES {
return Err("the control lane must hold at least one full envelope".into());
}
Ok(())
}
/// The `limits` object in the hello reply. Counts are JSON integers; byte sizes are U64
/// strings.
pub fn to_json(&self) -> Value {
let mut m = Map::new();
let mut n = |k: &str, v: u64| {
m.insert(k.into(), Value::from(v));
};
n("maxEnvelopeBytes", MAX_ENVELOPE_BYTES as u64);
n("maxAttachments", MAX_ATTACHMENTS as u64);
n("maxBatch", MAX_BATCH as u64);
n("maxClients", self.max_clients as u64);
n("maxServices", self.max_services as u64);
n("maxTopics", self.max_topics as u64);
n(
"maxSubscriptionsPerClient",
self.max_subscriptions_per_client as u64,
);
n("maxSubscriptions", self.max_subscriptions as u64);
n(
"maxActiveCallsPerClient",
self.max_active_calls_per_client as u64,
);
n("maxServiceQueued", self.max_service_queued);
n("maxServiceInFlight", self.max_service_in_flight);
n("maxLatestInFlight", self.max_latest_in_flight);
n("maxBoundedQueued", self.max_bounded_queued);
n("maxBoundedInFlight", self.max_bounded_in_flight);
n("maxOwnersPerClient", self.max_owners_per_client as u64);
n(
"reservedOwnersPerClient",
self.reserved_owners_per_client as u64,
);
n("maxControlFrames", self.max_control_frames as u64);
let mut s = |k: &str, v: u64| {
m.insert(k.into(), Value::from(v.to_string()));
};
s("maxStoreBytes", self.max_store_bytes);
s("maxArtifactBytes", self.max_artifact_bytes);
s("maxRetainedBytes", self.max_retained_bytes);
s(
"maxQueuedBytesPerClient",
self.max_queued_bytes_per_client as u64,
);
s("maxControlBytes", self.max_control_bytes as u64);
Value::Object(m)
}
/// Reads the hello reply's `limits` object back.
pub fn from_json(v: &Value) -> Result<Limits, WireError> {
let mut f = Fields::new(v, "limits")?;
let big = u64::MAX;
for (k, want) in [
("maxEnvelopeBytes", MAX_ENVELOPE_BYTES as u64),
("maxAttachments", MAX_ATTACHMENTS as u64),
("maxBatch", MAX_BATCH as u64),
] {
if f.int(k, 0, big)? != want {
return Err(WireError(format!(
"limits: {k} disagrees with this implementation"
)));
}
}
let limits = Limits {
max_clients: f.int("maxClients", 0, big)? as usize,
max_services: f.int("maxServices", 0, big)? as usize,
max_topics: f.int("maxTopics", 0, big)? as usize,
max_subscriptions_per_client: f.int("maxSubscriptionsPerClient", 0, big)? as usize,
max_subscriptions: f.int("maxSubscriptions", 0, big)? as usize,
max_active_calls_per_client: f.int("maxActiveCallsPerClient", 0, big)? as usize,
max_service_queued: f.int("maxServiceQueued", 1, MAX_CREDIT)?,
max_service_in_flight: f.int("maxServiceInFlight", 1, MAX_CREDIT)?,
max_latest_in_flight: f.int("maxLatestInFlight", 1, MAX_CREDIT)?,
max_bounded_queued: f.int("maxBoundedQueued", 1, MAX_CREDIT)?,
max_bounded_in_flight: f.int("maxBoundedInFlight", 1, MAX_CREDIT)?,
max_owners_per_client: f.int("maxOwnersPerClient", 0, big)? as usize,
reserved_owners_per_client: f.int("reservedOwnersPerClient", 0, big)? as usize,
max_control_frames: f.int("maxControlFrames", 0, big)? as usize,
max_store_bytes: f.u64_string("maxStoreBytes")?,
max_artifact_bytes: f.u64_string("maxArtifactBytes")?,
max_retained_bytes: f.u64_string("maxRetainedBytes")?,
max_queued_bytes_per_client: f.u64_string("maxQueuedBytesPerClient")? as usize,
max_control_bytes: f.u64_string("maxControlBytes")? as usize,
};
f.finish()?;
Ok(limits)
}
}