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 PeerClosed(SdkError),
114 Stopped,
117}
118
119#[derive(Clone, Copy, Debug, PartialEq, Eq)]
121pub struct MailboxOverflow {
122 pub payload_bytes: usize,
124 pub max_bytes: usize,
126}
127
128impl core::fmt::Display for MailboxOverflow {
129 fn fmt(&self, formatter: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
130 write!(
131 formatter,
132 "mailbox overflow: a {}-byte item exceeds the {}-byte mailbox",
133 self.payload_bytes, self.max_bytes
134 )
135 }
136}
137
138impl From<MailboxOverflow> for SdkError {
139 fn from(overflow: MailboxOverflow) -> Self {
140 Self::Backpressure {
141 reason: format!("{overflow}"),
142 }
143 }
144}
145
146#[derive(Debug)]
147struct State<T> {
148 queue: VecDeque<T>,
149 stats: MailboxStats,
150 end: Option<ReaderEnd>,
151 abandoned: bool,
152 receiver_claimed: bool,
153 direct_receives: usize,
154 #[cfg(test)]
155 blocking_wait_observer: Option<std::sync::mpsc::Sender<()>>,
156 #[cfg(test)]
157 receive_wakes: u64,
158 #[cfg(test)]
159 receive_timers: u64,
160}
161
162#[derive(Debug, PartialEq, Eq)]
164pub enum Offer {
165 Accepted,
167 Refused(MailboxOverflow),
169}
170
171#[derive(Debug)]
173pub enum BlockingTake<T> {
174 Item(T),
176 Abandoned,
178 Ended(ReaderEnd),
180}
181
182#[derive(Debug)]
185pub struct Mailbox<T> {
186 limits: MailboxLimits,
187 words: MailboxWords,
188 state: Mutex<State<T>>,
189 changed: Condvar,
190}
191
192fn poisoned() -> SdkError {
193 SdkError::Connection {
194 description: "stream mailbox lock poisoned: a thread panicked while holding it".to_string(),
195 }
196}
197
198impl<T: MailboxItem> Mailbox<T> {
199 pub fn new(limits: MailboxLimits, words: MailboxWords) -> Self {
200 Self {
201 limits,
202 words,
203 state: Mutex::new(State {
204 queue: VecDeque::new(),
205 stats: MailboxStats::default(),
206 end: None,
207 abandoned: false,
208 receiver_claimed: false,
209 direct_receives: 0,
210 #[cfg(test)]
211 blocking_wait_observer: None,
212 #[cfg(test)]
213 receive_wakes: 0,
214 #[cfg(test)]
215 receive_timers: 0,
216 }),
217 changed: Condvar::new(),
218 }
219 }
220
221 fn lock(&self) -> Result<MutexGuard<'_, State<T>>, SdkError> {
222 self.state.lock().map_err(|_| poisoned())
223 }
224
225 pub fn offer(&self, item: T) -> Result<Offer, SdkError> {
230 let size = item.payload_len();
231 if size > self.limits.bytes {
232 let mut state = self.lock()?;
233 state.stats.refused_frames = state.stats.refused_frames.saturating_add(1);
234 drop(state);
235 return Ok(Offer::Refused(MailboxOverflow {
236 payload_bytes: size,
237 max_bytes: self.limits.bytes,
238 }));
239 }
240 let mut state = self.lock()?;
241 let mut counted = false;
242 while !state.abandoned
243 && (state.queue.len() >= self.limits.frames
244 || state.stats.held_bytes.saturating_add(size) > self.limits.bytes)
245 {
246 if !counted {
247 state.stats.backpressure_waits = state.stats.backpressure_waits.saturating_add(1);
248 counted = true;
249 self.changed.notify_all();
250 }
251 state = self.changed.wait(state).map_err(|_| poisoned())?;
252 }
253 if state.abandoned {
254 drop(state);
255 return Ok(Offer::Accepted);
256 }
257 state.queue.push_back(item);
258 let tally = &mut state.stats;
259 tally.held_frames = tally.held_frames.saturating_add(1);
260 tally.held_bytes = tally.held_bytes.saturating_add(size);
261 tally.peak_frames = tally.peak_frames.max(tally.held_frames);
262 tally.peak_bytes = tally.peak_bytes.max(tally.held_bytes);
263 drop(state);
264 self.changed.notify_all();
265 Ok(Offer::Accepted)
266 }
267
268 pub fn close(&self, end: ReaderEnd) {
272 if let Ok(mut state) = self.state.lock() {
273 if state.abandoned && matches!(end, ReaderEnd::Failed(_) | ReaderEnd::PeerClosed(_)) {
274 state.stats.undelivered_errors = state.stats.undelivered_errors.saturating_add(1);
275 }
276 state.end = Some(end);
277 }
278 self.changed.notify_all();
281 }
282
283 pub fn take(&self, timeout: Duration) -> Result<T, SdkError> {
289 let deadline = Instant::now().checked_add(timeout);
290 let mut state = self.lock()?;
291 loop {
292 if let Some(item) = state.queue.pop_front() {
293 let tally = &mut state.stats;
294 tally.held_frames = tally.held_frames.saturating_sub(1);
295 tally.held_bytes = tally.held_bytes.saturating_sub(item.payload_len());
296 drop(state);
297 self.changed.notify_all();
298 return Ok(item);
299 }
300 match &state.end {
301 Some(ReaderEnd::Failed(error) | ReaderEnd::PeerClosed(error)) => {
302 return Err(error.clone());
303 }
304 Some(ReaderEnd::Stopped) => {
305 return Err(SdkError::Connection {
306 description: format!(
307 "{}: the {} stopped before a {} arrived",
308 self.words.receive, self.words.reader, self.words.item
309 ),
310 });
311 }
312 None => {}
313 }
314 let remaining = deadline.map_or(timeout, |deadline| {
315 deadline.saturating_duration_since(Instant::now())
316 });
317 if remaining.is_zero() {
318 return Err(SdkError::Connection {
319 description: format!(
320 "{}: no {} arrived within the timeout",
321 self.words.receive, self.words.item
322 ),
323 });
324 }
325 #[cfg(test)]
326 {
327 state.receive_timers = state.receive_timers.saturating_add(1);
328 }
329 state = self
330 .changed
331 .wait_timeout(state, remaining)
332 .map_err(|_| poisoned())?
333 .0;
334 }
335 }
336
337 pub fn claim_receiver(&self) -> Result<(), SdkError> {
344 let mut state = self.lock()?;
345 let claimable = !state.receiver_claimed && state.direct_receives == 0;
346 if claimable {
347 state.receiver_claimed = true;
348 }
349 drop(state);
350 if claimable {
351 Ok(())
352 } else {
353 Err(SdkError::ReceiverAlreadyClaimed)
354 }
355 }
356
357 fn begin_direct(state: &mut State<T>) -> Result<(), SdkError> {
358 if state.receiver_claimed {
359 return Err(SdkError::ReceiverAlreadyClaimed);
360 }
361 state.direct_receives =
362 state
363 .direct_receives
364 .checked_add(1)
365 .ok_or_else(|| SdkError::Protocol {
366 description: "push direct receive count exhausted".to_string(),
367 })?;
368 Ok(())
369 }
370
371 pub fn take_push_timeout(&self, timeout: Duration) -> Result<T, SdkError> {
373 let mut state = self.lock()?;
374 Self::begin_direct(&mut state)?;
375 drop(state);
376 let result = self.take(timeout);
377 let mut state = self.lock()?;
378 state.direct_receives -= 1;
379 result
380 }
381
382 pub fn take_blocking(&self, direct: bool) -> Result<BlockingTake<T>, SdkError> {
384 let mut state = self.lock()?;
385 if direct {
386 Self::begin_direct(&mut state)?;
387 }
388 let result = loop {
389 if state.abandoned {
390 break Ok(BlockingTake::Abandoned);
391 }
392 if let Some(item) = state.queue.pop_front() {
393 state.stats.held_frames -= 1;
394 state.stats.held_bytes -= item.payload_len();
395 break Ok(BlockingTake::Item(item));
396 }
397 if let Some(end) = &state.end {
398 break Ok(BlockingTake::Ended(end.clone()));
399 }
400 #[cfg(test)]
401 if let Some(observer) = &state.blocking_wait_observer {
402 if let Err(error) = observer.send(()) {
403 break Err(SdkError::Connection {
404 description: format!("blocking receive observer failed: {error}"),
405 });
406 }
407 }
408 state = self.changed.wait(state).map_err(|_| poisoned())?;
409 #[cfg(test)]
410 {
411 state.receive_wakes = state.receive_wakes.saturating_add(1);
412 }
413 };
414 if direct {
415 state.direct_receives -= 1;
416 }
417 let made_room = matches!(&result, Ok(BlockingTake::Item(_)));
418 drop(state);
419 if made_room {
420 self.changed.notify_all();
421 }
422 result
423 }
424
425 #[cfg(test)]
426 pub fn observe_blocking_wait(
427 &self,
428 observer: std::sync::mpsc::Sender<()>,
429 ) -> Result<(), SdkError> {
430 self.lock()?.blocking_wait_observer = Some(observer);
431 Ok(())
432 }
433
434 #[cfg(test)]
435 pub fn receive_wait_counts(&self) -> Result<(u64, u64), SdkError> {
436 let state = self.lock()?;
437 Ok((state.receive_wakes, state.receive_timers))
438 }
439
440 pub fn abandon(&self) -> Result<(), SdkError> {
446 let mut state = self.lock()?;
447 state.abandoned = true;
448 state.queue.clear();
449 state.stats.held_frames = 0;
450 state.stats.held_bytes = 0;
451 drop(state);
452 self.changed.notify_all();
453 Ok(())
454 }
455
456 pub fn release(&self) {
460 if let Ok(mut state) = self.state.lock() {
461 state.abandoned = true;
462 state.queue.clear();
463 state.stats.held_frames = 0;
464 state.stats.held_bytes = 0;
465 }
466 self.changed.notify_all();
467 }
468
469 pub fn wait_until(
475 &self,
476 budget: Duration,
477 done: impl Fn(&MailboxStats, Option<&ReaderEnd>) -> bool,
478 ) -> Result<bool, SdkError> {
479 let deadline = Instant::now().checked_add(budget);
480 let mut state = self.lock()?;
481 loop {
482 if done(&state.stats, state.end.as_ref()) {
483 return Ok(true);
484 }
485 let remaining = deadline.map_or(budget, |deadline| {
486 deadline.saturating_duration_since(Instant::now())
487 });
488 if remaining.is_zero() {
489 return Ok(false);
490 }
491 state = self
492 .changed
493 .wait_timeout(state, remaining)
494 .map_err(|_| poisoned())?
495 .0;
496 }
497 }
498
499 pub fn record_ignored_frame(&self) -> Result<(), SdkError> {
504 let mut state = self.lock()?;
505 state.stats.ignored_frames = state.stats.ignored_frames.saturating_add(1);
506 drop(state);
507 Ok(())
508 }
509
510 pub fn stats(&self) -> Result<MailboxStats, SdkError> {
515 Ok(self.lock()?.stats)
516 }
517}
518
519pub fn offer_or_end<T: MailboxItem>(mailbox: &Mailbox<T>, item: T) -> Option<ReaderEnd> {
522 match mailbox.offer(item) {
523 Ok(Offer::Accepted) => None,
524 Ok(Offer::Refused(overflow)) => Some(ReaderEnd::Failed(SdkError::from(overflow))),
525 Err(error) => Some(ReaderEnd::Failed(error)),
526 }
527}