Skip to main content

byteflow/scheduler/mailbox/
policy.rs

1/// What happens when a hop would exceed the mailbox's **logical** capacity.
2///
3/// # Why there is no `Block`
4///
5/// Parking the **OS worker** on a full mailbox would stall every other flow
6/// on that thread — the opposite of an M:N scheduler. Backpressure that
7/// waits belongs to a future scheduler state (`WAITING_SEND` → runnable
8/// when a slot frees), not to a queue method that holds a worker.
9///
10/// This revision therefore only offers **non-blocking** overflow:
11///
12/// | Policy | Effect |
13/// |--------|--------|
14/// | [`Reject`](Self::Reject) | Hop is not accepted; caller sees [`super::MailboxFull`] / [`crate::SendError::MailboxFull`] |
15/// | [`DropNewest`](Self::DropNewest) | Incoming hop discarded; queue unchanged |
16/// | [`DropOldest`](Self::DropOldest) | Oldest queued hop dropped, incoming enqueued (still FIFO among survivors) |
17///
18/// Default is [`Reject`](Self::Reject): fail-closed. Silent drop is an
19/// explicit embedder choice.
20#[repr(u8)]
21#[derive(Clone, Copy, Debug, PartialEq, Eq)]
22pub enum OverflowPolicy {
23    Reject = 0,
24    DropNewest = 1,
25    DropOldest = 2,
26}
27
28impl Default for OverflowPolicy {
29    fn default() -> Self {
30        Self::Reject
31    }
32}
33
34/// Memory + overflow contract for every mailbox spawned by a [`crate::Runtime`].
35///
36/// Fields are private so an out-of-range capacity or byte budget cannot
37/// sneak in. Construct with [`MailboxConfig::new`] or
38/// [`MailboxConfig::DEFAULT`], then narrow the byte budget with
39/// [`MailboxConfig::with_bytes`].
40///
41/// Both bounds apply; the first one reached refuses the hop. See
42/// [`super::MailboxBytes`] for why the hop count alone is not a memory
43/// bound.
44#[derive(Clone, Copy, Debug, PartialEq, Eq)]
45pub struct MailboxConfig {
46    capacity: super::MailboxCapacity,
47    bytes: super::MailboxBytes,
48    overflow: OverflowPolicy,
49}
50
51impl MailboxConfig {
52    /// Compile-time-valid default: 256 hops, 4 MiB, reject on overflow.
53    pub const DEFAULT: MailboxConfig = MailboxConfig {
54        capacity: super::MailboxCapacity::DEFAULT,
55        bytes: super::MailboxBytes::DEFAULT,
56        overflow: OverflowPolicy::Reject,
57    };
58
59    /// Capacity + policy, with the default byte budget.
60    ///
61    /// The byte budget is a separate builder step rather than a third
62    /// parameter so embedders that only care about hop count are not
63    /// forced to reason about bytes to keep compiling.
64    pub const fn new(capacity: super::MailboxCapacity, overflow: OverflowPolicy) -> Self {
65        Self {
66            capacity,
67            bytes: super::MailboxBytes::DEFAULT,
68            overflow,
69        }
70    }
71
72    /// Replace the byte budget.
73    pub const fn with_bytes(self, bytes: super::MailboxBytes) -> Self {
74        Self { bytes, ..self }
75    }
76
77    #[inline]
78    pub const fn capacity(&self) -> super::MailboxCapacity {
79        self.capacity
80    }
81
82    #[inline]
83    pub const fn bytes(&self) -> super::MailboxBytes {
84        self.bytes
85    }
86
87    #[inline]
88    pub const fn overflow(&self) -> OverflowPolicy {
89        self.overflow
90    }
91}
92
93impl Default for MailboxConfig {
94    fn default() -> Self {
95        Self::DEFAULT
96    }
97}
98
99#[cfg(test)]
100mod tests {
101    use super::*;
102    use crate::scheduler::mailbox::{MailboxBytes, MailboxCapacity};
103
104    #[test]
105    fn default_is_reject_256() {
106        let c = MailboxConfig::DEFAULT;
107        assert_eq!(c.capacity().get(), 256);
108        assert_eq!(c.bytes().get(), MailboxBytes::DEFAULT.get());
109        assert_eq!(c.overflow(), OverflowPolicy::Reject);
110    }
111
112    #[test]
113    fn new_preserves_parts() -> Result<(), &'static str> {
114        let cap = MailboxCapacity::new(4).ok_or("cap")?;
115        let c = MailboxConfig::new(cap, OverflowPolicy::DropOldest);
116        assert_eq!(c.capacity().get(), 4);
117        assert_eq!(c.overflow(), OverflowPolicy::DropOldest);
118        assert_eq!(c.bytes().get(), MailboxBytes::DEFAULT.get());
119        Ok(())
120    }
121
122    #[test]
123    fn with_bytes_narrows_only_the_budget() -> Result<(), &'static str> {
124        let cap = MailboxCapacity::new(4).ok_or("cap")?;
125        let budget = MailboxBytes::new(8192).ok_or("bytes")?;
126        let c = MailboxConfig::new(cap, OverflowPolicy::DropOldest).with_bytes(budget);
127        assert_eq!(c.bytes().get(), 8192);
128        assert_eq!(c.capacity().get(), 4);
129        assert_eq!(c.overflow(), OverflowPolicy::DropOldest);
130        Ok(())
131    }
132}