1use alloc::collections::VecDeque;
15use alloc::format;
16use alloc::string::ToString;
17use core::time::Duration;
18
19use std::sync::{Condvar, Mutex, MutexGuard};
20use std::time::Instant;
21
22use crate::SdkError;
23
24pub const DEFAULT_PUSH_MAILBOX_FRAMES: usize = 1024;
27
28pub const DEFAULT_PUSH_MAILBOX_BYTES: usize = 16 * 1024 * 1024;
32
33pub const DEFAULT_SUBSCRIPTION_MAILBOX_FRAMES: usize = 1024;
36
37pub const DEFAULT_SUBSCRIPTION_MAILBOX_BYTES: usize = 64 * 1024 * 1024;
43
44pub trait MailboxItem {
46 fn payload_len(&self) -> usize;
47}
48
49#[derive(Clone, Copy, Debug, PartialEq, Eq)]
51pub struct MailboxLimits {
52 pub frames: usize,
53 pub bytes: usize,
54}
55
56impl MailboxLimits {
57 pub const PUSH: Self = Self {
58 frames: DEFAULT_PUSH_MAILBOX_FRAMES,
59 bytes: DEFAULT_PUSH_MAILBOX_BYTES,
60 };
61 pub const SUBSCRIPTION: Self = Self {
62 frames: DEFAULT_SUBSCRIPTION_MAILBOX_FRAMES,
63 bytes: DEFAULT_SUBSCRIPTION_MAILBOX_BYTES,
64 };
65}
66
67#[derive(Clone, Copy, Debug)]
70pub struct MailboxWords {
71 pub receive: &'static str,
73 pub item: &'static str,
75 pub reader: &'static str,
77}
78
79#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
82#[non_exhaustive]
83pub struct MailboxStats {
84 pub held_frames: usize,
86 pub held_bytes: usize,
88 pub peak_frames: usize,
90 pub peak_bytes: usize,
92 pub backpressure_waits: u64,
95 pub refused_frames: u64,
97 pub ignored_frames: u64,
100 pub undelivered_errors: u64,
103}
104
105#[derive(Clone, Debug, PartialEq, Eq)]
107pub enum ReaderEnd {
108 Failed(SdkError),
112 Stopped,
115}
116
117#[derive(Clone, Copy, Debug, PartialEq, Eq)]
119pub struct MailboxOverflow {
120 pub payload_bytes: usize,
122 pub max_bytes: usize,
124}
125
126impl core::fmt::Display for MailboxOverflow {
127 fn fmt(&self, formatter: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
128 write!(
129 formatter,
130 "mailbox overflow: a {}-byte item exceeds the {}-byte mailbox",
131 self.payload_bytes, self.max_bytes
132 )
133 }
134}
135
136impl From<MailboxOverflow> for SdkError {
137 fn from(overflow: MailboxOverflow) -> Self {
138 Self::Backpressure {
139 reason: format!("{overflow}"),
140 }
141 }
142}
143
144#[derive(Debug)]
145struct State<T> {
146 queue: VecDeque<T>,
147 stats: MailboxStats,
148 end: Option<ReaderEnd>,
149 abandoned: bool,
150}
151
152#[derive(Debug, PartialEq, Eq)]
154pub enum Offer {
155 Accepted,
157 Refused(MailboxOverflow),
159}
160
161#[derive(Debug)]
164pub struct Mailbox<T> {
165 limits: MailboxLimits,
166 words: MailboxWords,
167 state: Mutex<State<T>>,
168 changed: Condvar,
169}
170
171fn poisoned() -> SdkError {
172 SdkError::Connection {
173 description: "stream mailbox lock poisoned: a thread panicked while holding it".to_string(),
174 }
175}
176
177impl<T: MailboxItem> Mailbox<T> {
178 pub fn new(limits: MailboxLimits, words: MailboxWords) -> Self {
179 Self {
180 limits,
181 words,
182 state: Mutex::new(State {
183 queue: VecDeque::new(),
184 stats: MailboxStats::default(),
185 end: None,
186 abandoned: false,
187 }),
188 changed: Condvar::new(),
189 }
190 }
191
192 fn lock(&self) -> Result<MutexGuard<'_, State<T>>, SdkError> {
193 self.state.lock().map_err(|_| poisoned())
194 }
195
196 pub fn offer(&self, item: T) -> Result<Offer, SdkError> {
201 let size = item.payload_len();
202 if size > self.limits.bytes {
203 let mut state = self.lock()?;
204 state.stats.refused_frames = state.stats.refused_frames.saturating_add(1);
205 drop(state);
206 return Ok(Offer::Refused(MailboxOverflow {
207 payload_bytes: size,
208 max_bytes: self.limits.bytes,
209 }));
210 }
211 let mut state = self.lock()?;
212 let mut counted = false;
213 while !state.abandoned
214 && (state.queue.len() >= self.limits.frames
215 || state.stats.held_bytes.saturating_add(size) > self.limits.bytes)
216 {
217 if !counted {
218 state.stats.backpressure_waits = state.stats.backpressure_waits.saturating_add(1);
219 counted = true;
220 self.changed.notify_all();
221 }
222 state = self.changed.wait(state).map_err(|_| poisoned())?;
223 }
224 if state.abandoned {
225 drop(state);
226 return Ok(Offer::Accepted);
227 }
228 state.queue.push_back(item);
229 let tally = &mut state.stats;
230 tally.held_frames = tally.held_frames.saturating_add(1);
231 tally.held_bytes = tally.held_bytes.saturating_add(size);
232 tally.peak_frames = tally.peak_frames.max(tally.held_frames);
233 tally.peak_bytes = tally.peak_bytes.max(tally.held_bytes);
234 drop(state);
235 self.changed.notify_all();
236 Ok(Offer::Accepted)
237 }
238
239 pub fn close(&self, end: ReaderEnd) {
243 if let Ok(mut state) = self.state.lock() {
244 if state.abandoned && matches!(end, ReaderEnd::Failed(_)) {
245 state.stats.undelivered_errors = state.stats.undelivered_errors.saturating_add(1);
246 }
247 state.end = Some(end);
248 }
249 self.changed.notify_all();
252 }
253
254 pub fn take(&self, timeout: Duration) -> Result<T, SdkError> {
260 let deadline = Instant::now().checked_add(timeout);
261 let mut state = self.lock()?;
262 loop {
263 if let Some(item) = state.queue.pop_front() {
264 let tally = &mut state.stats;
265 tally.held_frames = tally.held_frames.saturating_sub(1);
266 tally.held_bytes = tally.held_bytes.saturating_sub(item.payload_len());
267 drop(state);
268 self.changed.notify_all();
269 return Ok(item);
270 }
271 match &state.end {
272 Some(ReaderEnd::Failed(error)) => return Err(error.clone()),
273 Some(ReaderEnd::Stopped) => {
274 return Err(SdkError::Connection {
275 description: format!(
276 "{}: the {} stopped before a {} arrived",
277 self.words.receive, self.words.reader, self.words.item
278 ),
279 });
280 }
281 None => {}
282 }
283 let remaining = deadline.map_or(timeout, |deadline| {
284 deadline.saturating_duration_since(Instant::now())
285 });
286 if remaining.is_zero() {
287 return Err(SdkError::Connection {
288 description: format!(
289 "{}: no {} arrived within the timeout",
290 self.words.receive, self.words.item
291 ),
292 });
293 }
294 state = self
295 .changed
296 .wait_timeout(state, remaining)
297 .map_err(|_| poisoned())?
298 .0;
299 }
300 }
301
302 pub fn abandon(&self) -> Result<(), SdkError> {
308 let mut state = self.lock()?;
309 state.abandoned = true;
310 state.queue.clear();
311 state.stats.held_frames = 0;
312 state.stats.held_bytes = 0;
313 drop(state);
314 self.changed.notify_all();
315 Ok(())
316 }
317
318 pub fn release(&self) {
322 if let Ok(mut state) = self.state.lock() {
323 state.abandoned = true;
324 state.queue.clear();
325 state.stats.held_frames = 0;
326 state.stats.held_bytes = 0;
327 }
328 self.changed.notify_all();
329 }
330
331 pub fn wait_until(
337 &self,
338 budget: Duration,
339 done: impl Fn(&MailboxStats, Option<&ReaderEnd>) -> bool,
340 ) -> Result<bool, SdkError> {
341 let deadline = Instant::now().checked_add(budget);
342 let mut state = self.lock()?;
343 loop {
344 if done(&state.stats, state.end.as_ref()) {
345 return Ok(true);
346 }
347 let remaining = deadline.map_or(budget, |deadline| {
348 deadline.saturating_duration_since(Instant::now())
349 });
350 if remaining.is_zero() {
351 return Ok(false);
352 }
353 state = self
354 .changed
355 .wait_timeout(state, remaining)
356 .map_err(|_| poisoned())?
357 .0;
358 }
359 }
360
361 pub fn record_ignored_frame(&self) -> Result<(), SdkError> {
366 let mut state = self.lock()?;
367 state.stats.ignored_frames = state.stats.ignored_frames.saturating_add(1);
368 drop(state);
369 Ok(())
370 }
371
372 pub fn stats(&self) -> Result<MailboxStats, SdkError> {
377 Ok(self.lock()?.stats)
378 }
379}
380
381pub fn offer_or_end<T: MailboxItem>(mailbox: &Mailbox<T>, item: T) -> Option<ReaderEnd> {
384 match mailbox.offer(item) {
385 Ok(Offer::Accepted) => None,
386 Ok(Offer::Refused(overflow)) => Some(ReaderEnd::Failed(SdkError::from(overflow))),
387 Err(error) => Some(ReaderEnd::Failed(error)),
388 }
389}