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
//! Per-(capsule, topic, principal) IPC routing demux.
//!
//! `EventBus` owns a publish-side router that fans `Arc<AstridEvent>`s out
//! into per-(capsule, `topic_pattern`, subscription) routes and within each
//! route into per-principal FIFO queues. Guests obtain a [`RoutedEventReceiver`]
//! via [`crate::EventBus::subscribe_topic_routed`], which drains its own
//! queues with deficit-round-robin (DRR) fairness so no single principal
//! can starve another even under sustained burst.
//!
//! ## Why publish-side
//!
//! Pre-#813 every guest subscription consumed the global broadcast channel
//! and filtered by topic + principal on the consumer side. Under a 100-wide
//! cross-principal burst the broadcast channel shed events at its capacity
//! (`DEFAULT_CHANNEL_CAPACITY = 1024`) and the per-receiver post-filter
//! truncated mixed-principal batches. The result was a "concurrency cliff"
//! where orchestration collapsed once principal-count exceeded buffer head
//! room. The publish-side demux owned here is the structural fix: each
//! route gets its own bounded byte budget, each principal gets its own
//! FIFO sub-queue, and fan-out happens before any broadcast-channel back
//! pressure.
//!
//! ## Routing topology
//!
//! ```text
//! EventBus
//! └── routes: RwLock<HashMap<RouteKey, Mutex<RouteEntry>>>
//! │ RouteKey = (capsule_uuid, topic_pattern, subscription_rep)
//! │ RouteEntry { matcher, fanout, total_bytes, notify }
//! │ ↑
//! └─ fanout: HashMap<PrincipalKey, PrincipalQueue>
//! ↑
//! DRR rotated via principal_order
//! ```
//!
//! ## Fairness: deficit round-robin (DRR)
//!
//! Each per-principal sub-queue accrues a `deficit` over rounds; on each
//! visit the queue may emit as many messages as fit under its accumulated
//! deficit (bounded above by [`DRR_QUANTUM_MIN_BYTES`]). 5000 idle
//! principals = zero entries (sub-map is demand-allocated). N active = N
//! entries; the quantum-floor guarantees per-round progress even under
//! extreme principal counts.
//!
//! ## Eviction: oldest-head-first under byte pressure
//!
//! When `entry.total_bytes + msg_size > MAX_SUBSCRIPTION_BUDGET_BYTES` on
//! the publish path, the bucket whose head was enqueued earliest gives up
//! its head message until the new payload fits. Streaming response
//! terminators are preserved by construction: they're always the tail of
//! their principal's queue, so head-eviction trims the prefix not the tail.
pub
pub
pub
pub use ;
pub use ;
pub use ;
pub use ;