1mod capacity;
33mod metrics;
34mod policy;
35mod queue;
36
37use std::collections::VecDeque;
38use std::sync::Mutex;
39
40use crate::bytecode::Value;
41
42use super::error::RuntimeError;
43use super::process::{Flow, FlowId};
44use super::sync_lock;
45
46pub use capacity::{MailboxBytes, MailboxCapacity};
47pub use metrics::MailboxStats;
48pub use policy::{MailboxConfig, OverflowPolicy};
49
50use queue::{EnqueueEffect, MailboxQueue};
51
52#[derive(Debug, Clone, Copy, PartialEq, Eq)]
57pub(crate) enum WaitFilter {
58 Any,
60 Tag(u16),
62 Correlation {
70 expect_request_id: u64,
71 expect_sender: Option<u64>,
72 },
73 TaggedCorrelation { tag: u16, expect_request_id: u64 },
75}
76
77impl WaitFilter {
78 #[inline]
79 pub(crate) fn matches(&self, value: &Value) -> bool {
80 match *self {
81 Self::Any => true,
82 Self::Tag(expected_tag) => match value.as_message() {
83 Some(m) => m.tag == expected_tag,
84 None => false,
85 },
86 Self::Correlation {
87 expect_request_id,
88 expect_sender,
89 } => match value.as_message() {
90 Some(m) => {
91 let id_ok = m.request_id == expect_request_id;
92 let sender_ok = match expect_sender {
93 Some(s) => m.sender == s,
94 None => true,
95 };
96 id_ok && sender_ok
97 }
98 None => false,
99 },
100 Self::TaggedCorrelation {
101 tag,
102 expect_request_id,
103 } => match value.as_message() {
104 Some(m) => m.tag == tag && m.request_id == expect_request_id,
105 None => false,
106 },
107 }
108 }
109}
110
111pub struct Mailbox {
160 inner: Mutex<MailboxInner>,
161 config: MailboxConfig,
162}
163
164struct MailboxInner {
165 queue: MailboxQueue,
166 parked: Option<Box<Flow>>,
167 parked_filter: WaitFilter,
169 wait_epoch: u64,
172 stats: MailboxStats,
173 waiting_senders: VecDeque<WaitingSender>,
176 closed: bool,
179}
180
181struct WaitingSender {
182 flow: Box<Flow>,
183 message: Value,
184}
185
186pub enum Delivery {
194 Queued,
195 QueuedDropOldest,
196 DroppedNewest,
197 Handoff(Box<Flow>),
198}
199
200#[derive(Debug, Clone, Copy, PartialEq, Eq)]
208pub enum MailboxFullReason {
209 MessageLimit,
211 ByteLimit,
215}
216
217impl std::fmt::Display for MailboxFullReason {
218 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
219 match self {
220 MailboxFullReason::MessageLimit => write!(f, "hop count limit"),
221 MailboxFullReason::ByteLimit => write!(f, "byte budget"),
222 }
223 }
224}
225
226#[derive(Debug, Clone, Copy, PartialEq, Eq)]
228pub struct MailboxFull {
229 reason: MailboxFullReason,
230}
231
232impl MailboxFull {
233 #[inline]
234 pub const fn reason(self) -> MailboxFullReason {
235 self.reason
236 }
237}
238
239impl std::fmt::Display for MailboxFull {
240 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
241 write!(f, "mailbox full ({})", self.reason)
242 }
243}
244
245impl std::error::Error for MailboxFull {}
246
247#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
273pub struct WaitEpoch(u64);
274
275impl WaitEpoch {
276 #[inline]
279 pub const fn get(self) -> u64 {
280 self.0
281 }
282}
283
284impl std::fmt::Display for WaitEpoch {
285 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
286 write!(f, "wait#{}", self.0)
287 }
288}
289
290impl Mailbox {
291 pub fn new() -> Self {
292 Self::with_config(MailboxConfig::DEFAULT)
293 }
294
295 pub fn with_config(config: MailboxConfig) -> Self {
296 Mailbox {
297 inner: Mutex::new(MailboxInner {
298 queue: MailboxQueue::new(config.capacity().get(), config.bytes().get()),
299 parked: None,
300 parked_filter: WaitFilter::Any,
301 wait_epoch: 0,
302 stats: MailboxStats::default(),
303 waiting_senders: VecDeque::new(),
304 closed: false,
305 }),
306 config,
307 }
308 }
309
310 #[inline]
311 pub fn config(&self) -> MailboxConfig {
312 self.config
313 }
314
315 pub fn stats(&self) -> Result<MailboxStats, RuntimeError> {
319 let inner = sync_lock::lock(&self.inner, "Mailbox::stats")?;
320 let mut stats = inner.stats;
321 stats.queued_messages = inner.queue.len();
322 stats.queued_bytes = inner.queue.bytes();
323 Ok(stats)
324 }
325
326 pub fn push(&self, value: Value) -> Result<Result<Delivery, MailboxFull>, RuntimeError> {
338 let mut inner = sync_lock::lock(&self.inner, "Mailbox::push")?;
339 if let Some(flow) = inner.parked.take() {
340 if inner.parked_filter.matches(&value) {
341 inner.parked_filter = WaitFilter::Any;
342 inner.stats.dequeued = inner.stats.dequeued.saturating_add(1);
343 inner.stats.enqueued = inner.stats.enqueued.saturating_add(1);
344 return Ok(Ok(Delivery::Handoff(flow)));
345 }
346 inner.parked = Some(flow);
347 return Ok(enqueue_locked(
348 &mut inner,
349 value,
350 self.config.overflow(),
351 ));
352 }
353 Ok(enqueue_locked(
354 &mut inner,
355 value,
356 self.config.overflow(),
357 ))
358 }
359
360 pub fn try_pop(&self) -> Result<Option<Value>, RuntimeError> {
362 self.try_pop_filter(WaitFilter::Any)
363 }
364
365 pub fn try_pop_match(&self, tag: u16) -> Result<Option<Value>, RuntimeError> {
367 self.try_pop_filter(WaitFilter::Tag(tag))
368 }
369
370 pub(crate) fn try_pop_filter(
372 &self,
373 filter: WaitFilter,
374 ) -> Result<Option<Value>, RuntimeError> {
375 let mut inner = sync_lock::lock(&self.inner, "Mailbox::try_pop_filter")?;
376 let got = inner.queue.take(filter);
377 if got.is_some() {
378 inner.stats.dequeued = inner.stats.dequeued.saturating_add(1);
379 }
380 Ok(got)
381 }
382
383 pub fn park(&self, flow: Box<Flow>) -> Result<Result<WaitEpoch, Box<Flow>>, RuntimeError> {
396 self.park_filter(flow, WaitFilter::Any)
397 }
398
399 pub fn park_match(
403 &self,
404 flow: Box<Flow>,
405 tag: u16,
406 ) -> Result<Result<WaitEpoch, Box<Flow>>, RuntimeError> {
407 self.park_filter(flow, WaitFilter::Tag(tag))
408 }
409
410 pub(crate) fn park_filter(
413 &self,
414 flow: Box<Flow>,
415 filter: WaitFilter,
416 ) -> Result<Result<WaitEpoch, Box<Flow>>, RuntimeError> {
417 let mut inner = sync_lock::lock(&self.inner, "Mailbox::park_filter")?;
418 if let Some(value) = inner.queue.take(filter) {
419 inner.stats.dequeued = inner.stats.dequeued.saturating_add(1);
420 drop(inner);
421 return Ok(Err(with_pending(flow, value)));
422 }
423 inner.wait_epoch = inner.wait_epoch.wrapping_add(1);
427 let epoch = WaitEpoch(inner.wait_epoch);
428 inner.parked_filter = filter;
429 inner.parked = Some(flow);
430 Ok(Ok(epoch))
431 }
432
433 pub fn take_parked_at(&self, epoch: WaitEpoch) -> Result<Option<Box<Flow>>, RuntimeError> {
446 let mut inner = sync_lock::lock(&self.inner, "Mailbox::take_parked_at")?;
447 if inner.wait_epoch != epoch.0 {
448 return Ok(None);
449 }
450 match inner.parked.take() {
451 Some(flow) => {
452 inner.parked_filter = WaitFilter::Any;
453 Ok(Some(flow))
454 }
455 None => Ok(None),
456 }
457 }
458
459 pub(crate) fn take_parked(&self) -> Result<Option<Box<Flow>>, RuntimeError> {
461 let mut inner = sync_lock::lock(&self.inner, "Mailbox::take_parked")?;
462 inner.parked_filter = WaitFilter::Any;
463 Ok(inner.parked.take())
464 }
465
466 pub(crate) fn push_system(&self, value: Value) -> Result<Delivery, RuntimeError> {
468 let mut inner = sync_lock::lock(&self.inner, "Mailbox::push_system")?;
469 if let Some(flow) = inner.parked.take() {
470 if inner.parked_filter.matches(&value) {
471 inner.parked_filter = WaitFilter::Any;
472 inner.stats.dequeued = inner.stats.dequeued.saturating_add(1);
473 inner.stats.enqueued = inner.stats.enqueued.saturating_add(1);
474 return Ok(Delivery::Handoff(flow));
475 }
476 inner.parked = Some(flow);
477 }
478 inner.queue.force_push(value);
479 inner.stats.enqueued = inner.stats.enqueued.saturating_add(1);
480 Ok(Delivery::Queued)
481 }
482
483 pub(crate) fn close(&self) -> Result<Vec<Flow>, RuntimeError> {
488 let mut inner = sync_lock::lock(&self.inner, "Mailbox::close")?;
489 inner.closed = true;
490 Ok(inner.waiting_senders.drain(..).map(|w| *w.flow).collect())
491 }
492
493 pub(crate) fn park_sender(&self, flow: Box<Flow>, message: Value) -> ParkSender {
498 match sync_lock::lock(&self.inner, "Mailbox::park_sender") {
499 Ok(inner) if inner.closed => ParkSender::Closed(flow),
500 Ok(mut inner) => {
501 inner.waiting_senders.push_back(WaitingSender { flow, message });
502 ParkSender::Parked
503 }
504 Err(e) => {
505 super::error::report_fault(e);
506 ParkSender::Closed(flow)
507 }
508 }
509 }
510
511 pub(crate) fn admit_waiting_sender(&self) -> Result<Option<Box<Flow>>, RuntimeError> {
513 let mut inner = sync_lock::lock(&self.inner, "Mailbox::admit_waiting_sender")?;
514 if inner.closed {
515 return Ok(None);
516 }
517 let Some(waiter) = inner.waiting_senders.pop_front() else {
518 return Ok(None);
519 };
520 match enqueue_locked(&mut inner, waiter.message.clone(), OverflowPolicy::Reject) {
521 Ok(_) => Ok(Some(waiter.flow)),
522 Err(_) => {
523 inner.waiting_senders.push_front(waiter);
524 Ok(None)
525 }
526 }
527 }
528
529 pub(crate) fn take_waiting_sender(
531 &self,
532 sender: FlowId,
533 ) -> Result<Option<Box<Flow>>, RuntimeError> {
534 let mut inner = sync_lock::lock(&self.inner, "Mailbox::take_waiting_sender")?;
535 if let Some(pos) = inner.waiting_senders.iter().position(|w| w.flow.id == sender) {
536 return Ok(inner.waiting_senders.remove(pos).map(|w| w.flow));
537 }
538 Ok(None)
539 }
540
541}
542
543pub(crate) enum ParkSender {
545 Parked,
546 Closed(Box<Flow>),
548}
549
550impl Default for Mailbox {
551 fn default() -> Self {
552 Self::new()
553 }
554}
555
556fn enqueue_locked(
557 inner: &mut MailboxInner,
558 value: Value,
559 policy: OverflowPolicy,
560) -> Result<Delivery, MailboxFull> {
561 match inner.queue.enqueue(value, policy) {
562 Ok(EnqueueEffect::Enqueued) => {
563 inner.stats.enqueued = inner.stats.enqueued.saturating_add(1);
564 Ok(Delivery::Queued)
565 }
566 Ok(EnqueueEffect::DroppedOldest) => {
567 inner.stats.dropped_oldest = inner.stats.dropped_oldest.saturating_add(1);
568 inner.stats.enqueued = inner.stats.enqueued.saturating_add(1);
569 Ok(Delivery::QueuedDropOldest)
570 }
571 Ok(EnqueueEffect::DroppedNewest) => {
572 inner.stats.dropped_newest = inner.stats.dropped_newest.saturating_add(1);
573 Ok(Delivery::DroppedNewest)
574 }
575 Err(reason) => {
576 inner.stats.rejected = inner.stats.rejected.saturating_add(1);
577 if reason == MailboxFullReason::ByteLimit {
578 inner.stats.rejected_byte_limit =
579 inner.stats.rejected_byte_limit.saturating_add(1);
580 }
581 Err(MailboxFull { reason })
582 }
583 }
584}
585
586fn with_pending(mut flow: Box<Flow>, value: Value) -> Box<Flow> {
590 flow.pending_message = Some(value);
591 flow
592}
593
594#[cfg(test)]
595mod tests {
596 use super::*;
597 use crate::bytecode::Message;
598 use crate::scheduler::oneshot;
599 use crate::scheduler::process::{next_flow_id, RestartPolicy};
600 use crate::vm::{NativeTable, Vm};
601 use crate::bytecode::builder::ChunkBuilder;
602 use std::sync::Arc;
603
604 type TestResult = Result<(), Box<dyn std::error::Error>>;
605
606 fn dummy_flow() -> Result<Box<Flow>, Box<dyn std::error::Error>> {
607 let mut b = ChunkBuilder::new("mb");
608 b.begin_function("main", 0, 1);
609 b.emit_return(0);
610 let chunk = b.finish();
611 let vm = Vm::new(Arc::new(chunk), NativeTable::empty(), 0, &[])?;
612 let (tx, _rx) = oneshot::channel();
613 Ok(Box::new(Flow::new(
614 next_flow_id(),
615 vm,
616 Arc::new(Mailbox::new()),
617 RestartPolicy::Never,
618 tx,
619 )))
620 }
621
622 fn hop_payload_int(msg: &Message) -> i64 {
623 msg.payload.as_int().unwrap_or(0)
624 }
625
626 fn hop(sender: u64, request_id: u64, tag: u16, payload: impl Into<Value>) -> Value {
627 Value::Message(Message::new(sender, request_id, tag, payload))
628 }
629
630 fn msg(tag: u16, payload: impl Into<Value>) -> Value {
631 hop(1, 1, tag, payload)
632 }
633
634 fn tiny_reject(n: u32) -> Result<Mailbox, Box<dyn std::error::Error>> {
635 let cap = MailboxCapacity::new(n).ok_or("invalid mailbox capacity")?;
636 Ok(Mailbox::with_config(MailboxConfig::new(
637 cap,
638 OverflowPolicy::Reject,
639 )))
640 }
641
642 fn hop_msg(value: &Value) -> Result<&Message, Box<dyn std::error::Error>> {
643 value.as_message().ok_or_else(|| "expected Message hop".into())
644 }
645
646 #[test]
647 fn try_pop_match_skips_non_matching_fifo() -> TestResult {
648 let mb = Mailbox::new();
649 mb.push(msg(9, 1))??;
650 mb.push(msg(1, 42))??;
651 mb.push(msg(9, 2))??;
652 let got = mb.try_pop_match(1)?.ok_or("match")?;
653 assert_eq!(hop_payload_int(hop_msg(&got)?), 42);
654 assert_eq!(
655 hop_msg(&mb.try_pop()?.ok_or("first leftover")?)?.tag,
656 9
657 );
658 assert_eq!(
659 hop_payload_int(hop_msg(&mb.try_pop()?.ok_or("second leftover")?)?),
660 2
661 );
662 Ok(())
663 }
664
665 #[test]
666 fn push_while_park_match_queues_junk_keeps_waiter() -> TestResult {
667 let mb = Mailbox::new();
668 let flow = dummy_flow()?;
669 assert!(mb.park_match(flow, 1)?.is_ok());
670 assert!(matches!(mb.push(msg(9, 0))??, Delivery::Queued));
671 assert!(matches!(mb.push(msg(1, 7))??, Delivery::Handoff(_)));
672 assert_eq!(hop_msg(&mb.try_pop()?.ok_or("queued junk")?)?.tag, 9);
673 Ok(())
674 }
675
676 #[test]
677 fn ask_does_not_consume_reply_for_another_request() -> TestResult {
678 let mb = Mailbox::new();
679 mb.push(hop(10, 2, 2, 99))??;
680 mb.push(hop(10, 1, 2, 42))??;
681 let filter = WaitFilter::Correlation {
682 expect_request_id: 1,
683 expect_sender: Some(10),
684 };
685 let got = mb.try_pop_filter(filter)?.ok_or("id=1")?;
686 assert_eq!(hop_payload_int(hop_msg(&got)?), 42);
687 let left = mb.try_pop()?.ok_or("leftover")?;
688 assert_eq!(hop_msg(&left)?.request_id, 2);
689 Ok(())
690 }
691
692 #[test]
693 fn tagged_correlation_skips_wrong_id_and_tag() -> TestResult {
694 let mb = Mailbox::new();
695 mb.push(hop(10, 1, 1, 7))??;
696 mb.push(hop(10, 2, 2, 9))??;
697 mb.push(hop(10, 1, 2, 42))??;
698 assert!(mb
699 .try_pop_filter(WaitFilter::TaggedCorrelation {
700 tag: 99,
701 expect_request_id: 1,
702 })?
703 .is_none());
704 let got = mb
705 .try_pop_filter(WaitFilter::TaggedCorrelation {
706 tag: 2,
707 expect_request_id: 1,
708 })?
709 .ok_or("tag=2 id=1")?;
710 assert_eq!(hop_payload_int(hop_msg(&got)?), 42);
711 Ok(())
712 }
713
714 #[test]
715 fn ask_requires_reply_from_target() -> TestResult {
716 let mb = Mailbox::new();
717 let flow = dummy_flow()?;
718 let filter = WaitFilter::Correlation {
719 expect_request_id: 1,
720 expect_sender: Some(10),
721 };
722 assert!(mb.park_filter(flow, filter)?.is_ok());
723 assert!(matches!(mb.push(hop(99, 1, 2, 0))??, Delivery::Queued));
724 assert!(matches!(mb.push(hop(10, 1, 2, 42))??, Delivery::Handoff(_)));
725 assert_eq!(
726 hop_msg(&mb.try_pop()?.ok_or("non-matching queued")?)?.sender,
727 99
728 );
729 Ok(())
730 }
731
732 #[test]
733 fn reject_when_full_without_waiter() -> TestResult {
734 let mb = tiny_reject(1)?;
735 assert!(matches!(mb.push(msg(1, 1))??, Delivery::Queued));
736 match mb.push(msg(1, 2))? {
740 Err(full) => assert_eq!(full.reason(), MailboxFullReason::MessageLimit),
741 Ok(_) => return Err("expected the hop count bound to refuse".into()),
742 }
743 let s = mb.stats()?;
744 assert_eq!(s.enqueued, 1);
745 assert_eq!(s.rejected, 1);
746 assert_eq!(s.rejected_byte_limit, 0);
747 assert_eq!(s.queued_messages, 1);
748 Ok(())
749 }
750
751 fn park_now(mb: &Mailbox, flow: Box<Flow>) -> Result<WaitEpoch, Box<dyn std::error::Error>> {
754 match mb.park(flow)? {
755 Ok(epoch) => Ok(epoch),
756 Err(_) => Err("an empty mailbox should have parked the flow".into()),
757 }
758 }
759
760 fn handoff(mb: &Mailbox, value: Value) -> Result<Box<Flow>, Box<dyn std::error::Error>> {
761 match mb.push(value)?? {
762 Delivery::Handoff(flow) => Ok(flow),
763 other => Err(format!("expected a handoff, got {}", delivery_name(&other)).into()),
764 }
765 }
766
767 fn delivery_name(d: &Delivery) -> &'static str {
768 match d {
769 Delivery::Queued => "Queued",
770 Delivery::QueuedDropOldest => "QueuedDropOldest",
771 Delivery::DroppedNewest => "DroppedNewest",
772 Delivery::Handoff(_) => "Handoff",
773 }
774 }
775
776 #[test]
777 fn a_stale_deadline_cannot_steal_a_later_wait() -> TestResult {
778 let mb = Mailbox::new();
779 let first = park_now(&mb, dummy_flow()?)?;
780 let woken = handoff(&mb, msg(1, 1))?;
782 let second = park_now(&mb, woken)?;
784 assert_ne!(first, second, "each park must get its own epoch");
785
786 assert!(mb.take_parked_at(first)?.is_none());
790 assert!(mb.take_parked_at(second)?.is_some());
792 Ok(())
793 }
794
795 #[test]
796 fn a_stale_deadline_does_not_downgrade_a_selective_waiter() -> TestResult {
797 let mb = Mailbox::new();
798 let first = park_now(&mb, dummy_flow()?)?;
799 let woken = handoff(&mb, msg(1, 1))?;
800 let second = match mb.park_match(woken, 7)? {
802 Ok(epoch) => epoch,
803 Err(_) => return Err("empty mailbox should have parked the flow".into()),
804 };
805 assert_ne!(first, second);
806
807 assert!(mb.take_parked_at(first)?.is_none());
808 assert!(matches!(mb.push(msg(9, 0))??, Delivery::Queued));
811 assert!(matches!(mb.push(msg(7, 0))??, Delivery::Handoff(_)));
813 Ok(())
814 }
815
816 #[test]
817 fn a_deadline_for_a_wait_that_a_hop_ended_does_nothing() -> TestResult {
818 let mb = Mailbox::new();
819 let epoch = park_now(&mb, dummy_flow()?)?;
820 let _woken = handoff(&mb, msg(1, 1))?;
821 assert!(mb.take_parked_at(epoch)?.is_none());
824 Ok(())
825 }
826
827 #[test]
828 fn byte_budget_refuses_before_the_hop_count_and_says_so() -> TestResult {
829 let cap = MailboxCapacity::new(64).ok_or("cap")?;
831 let budget = MailboxBytes::new(MailboxBytes::MIN).ok_or("bytes")?;
832 let mb = Mailbox::with_config(
833 MailboxConfig::new(cap, OverflowPolicy::Reject).with_bytes(budget),
834 );
835 assert!(matches!(mb.push(Value::bytes(vec![0u8; 900]))??, Delivery::Queued));
836 match mb.push(Value::bytes(vec![0u8; 900]))? {
837 Err(full) => assert_eq!(full.reason(), MailboxFullReason::ByteLimit),
838 Ok(_) => return Err("expected the byte budget to refuse".into()),
839 }
840 let s = mb.stats()?;
841 assert_eq!(s.queued_messages, 1);
842 assert!(s.queued_bytes >= 900);
843 assert_eq!(s.rejected, 1);
844 assert_eq!(s.rejected_byte_limit, 1);
845 Ok(())
846 }
847
848 #[test]
849 fn draining_a_hop_frees_its_byte_charge() -> TestResult {
850 let cap = MailboxCapacity::new(64).ok_or("cap")?;
851 let budget = MailboxBytes::new(MailboxBytes::MIN).ok_or("bytes")?;
852 let mb = Mailbox::with_config(
853 MailboxConfig::new(cap, OverflowPolicy::Reject).with_bytes(budget),
854 );
855 mb.push(Value::bytes(vec![0u8; 900]))??;
856 mb.try_pop()?.ok_or("queued blob")?;
857 assert_eq!(mb.stats()?.queued_bytes, 0);
858 assert!(matches!(mb.push(Value::bytes(vec![0u8; 900]))??, Delivery::Queued));
861 Ok(())
862 }
863
864 #[test]
865 fn matching_handoff_does_not_count_as_full() -> TestResult {
866 let mb = tiny_reject(1)?;
867 mb.push(msg(9, 0))??;
868 let flow = dummy_flow()?;
869 assert!(mb.park_match(flow, 1)?.is_ok());
870 assert!(matches!(mb.push(msg(1, 7))??, Delivery::Handoff(_)));
871 assert_eq!(hop_msg(&mb.try_pop()?.ok_or("queued")?)?.tag, 9);
872 Ok(())
873 }
874
875 #[test]
876 fn waiting_send_admits_one_after_pop() -> TestResult {
877 let mb = tiny_reject(1)?;
878 assert!(matches!(mb.push(msg(1, 1))??, Delivery::Queued));
879 assert!(matches!(
880 mb.park_sender(dummy_flow()?, msg(1, 2)),
881 ParkSender::Parked
882 ));
883 assert!(mb.try_pop()?.is_some());
884 let woken = mb.admit_waiting_sender()?.ok_or("admitted")?;
885 drop(woken);
886 assert_eq!(hop_payload_int(hop_msg(&mb.try_pop()?.ok_or("second hop")?)?), 2);
887 assert!(mb.admit_waiting_sender()?.is_none());
888 Ok(())
889 }
890
891 #[test]
892 fn close_rejects_late_park_sender() -> TestResult {
893 let mb = tiny_reject(1)?;
894 let leftover = mb.close()?;
895 assert!(leftover.is_empty());
896 match mb.park_sender(dummy_flow()?, msg(1, 1)) {
897 ParkSender::Closed(_) => {}
898 ParkSender::Parked => return Err("closed mailbox must not park a sender".into()),
899 }
900 Ok(())
901 }
902
903 #[test]
904 fn drop_oldest_still_wakes_on_match() -> TestResult {
905 let cap = MailboxCapacity::new(1).ok_or("cap")?;
906 let mb = Mailbox::with_config(MailboxConfig::new(cap, OverflowPolicy::DropOldest));
907 let flow = dummy_flow()?;
908 assert!(mb.park_match(flow, 1)?.is_ok());
909 mb.push(msg(9, 1))??;
910 assert!(matches!(mb.push(msg(1, 2))??, Delivery::Handoff(_)));
911 Ok(())
912 }
913}