1use std::collections::BTreeMap;
41
42use serde::{Deserialize, Serialize};
43
44use crate::types::cancel::CancelReason;
45use crate::types::outcome::PanicPayload;
46use crate::types::{Outcome, RegionId, TaskId, Time};
47
48#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
57pub struct MonitorRef(u64);
58
59impl MonitorRef {
60 #[inline]
62 fn new(id: u64) -> Self {
63 Self(id)
64 }
65
66 #[cfg(test)]
68 fn from_raw(id: u64) -> Self {
69 Self(id)
70 }
71
72 #[doc(hidden)]
74 #[must_use]
75 #[inline]
76 pub const fn new_for_test(id: u64) -> Self {
77 Self(id)
78 }
79
80 #[must_use]
82 #[inline]
83 pub fn id(self) -> u64 {
84 self.0
85 }
86}
87
88impl std::fmt::Display for MonitorRef {
89 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
90 write!(f, "MonitorRef({})", self.0)
91 }
92}
93
94#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
103pub enum DownReason {
104 Normal,
106 Error(String),
108 Cancelled(CancelReason),
110 Panicked(PanicPayload),
112}
113
114impl DownReason {
115 #[must_use]
117 #[inline]
118 pub fn from_task_outcome(outcome: &Outcome<(), crate::error::Error>) -> Self {
119 match outcome {
120 Outcome::Ok(()) => Self::Normal,
121 Outcome::Err(e) => Self::Error(format!("{e}")),
122 Outcome::Cancelled(r) => Self::Cancelled(r.clone()),
123 Outcome::Panicked(p) => Self::Panicked(p.clone()),
124 }
125 }
126
127 #[must_use]
129 #[inline]
130 pub fn is_normal(&self) -> bool {
131 matches!(self, Self::Normal)
132 }
133
134 #[must_use]
136 #[inline]
137 pub fn is_error(&self) -> bool {
138 matches!(self, Self::Error(_))
139 }
140
141 #[must_use]
143 #[inline]
144 pub fn is_cancelled(&self) -> bool {
145 matches!(self, Self::Cancelled(_))
146 }
147
148 #[must_use]
150 #[inline]
151 pub fn is_panicked(&self) -> bool {
152 matches!(self, Self::Panicked(_))
153 }
154}
155
156impl std::fmt::Display for DownReason {
157 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
158 match self {
159 Self::Normal => write!(f, "normal"),
160 Self::Error(e) => write!(f, "error: {e}"),
161 Self::Cancelled(r) => write!(f, "cancelled: {r:?}"),
162 Self::Panicked(p) => write!(f, "panicked: {p}"),
163 }
164 }
165}
166
167#[derive(Debug, Clone)]
178pub struct DownNotification {
179 pub monitored: TaskId,
181 pub reason: DownReason,
183 pub monitor_ref: MonitorRef,
185}
186
187#[derive(Debug, Clone)]
193struct MonitorRecord {
194 watcher: TaskId,
196 watcher_region: RegionId,
198 monitored: TaskId,
200}
201
202#[derive(Debug)]
218#[allow(clippy::struct_field_names)]
219pub struct MonitorSet {
220 by_ref: BTreeMap<MonitorRef, MonitorRecord>,
221 by_monitored: BTreeMap<TaskId, Vec<MonitorRef>>,
222 by_watcher_region: BTreeMap<RegionId, Vec<MonitorRef>>,
223 next_monitor_ref: u64,
224}
225
226impl Default for MonitorSet {
227 fn default() -> Self {
228 Self::new()
229 }
230}
231
232impl MonitorSet {
233 #[must_use]
235 pub fn new() -> Self {
236 Self {
237 by_ref: BTreeMap::new(),
238 by_monitored: BTreeMap::new(),
239 by_watcher_region: BTreeMap::new(),
240 next_monitor_ref: 1,
241 }
242 }
243
244 #[inline]
245 fn alloc_monitor_ref(&mut self) -> MonitorRef {
246 let next = self.next_monitor_ref;
247 self.next_monitor_ref = self
248 .next_monitor_ref
249 .checked_add(1)
250 .expect("monitor ref space exhausted");
251 MonitorRef::new(next)
252 }
253
254 pub fn establish(
260 &mut self,
261 watcher: TaskId,
262 watcher_region: RegionId,
263 monitored: TaskId,
264 ) -> MonitorRef {
265 let monitor_ref = self.alloc_monitor_ref();
266 let record = MonitorRecord {
267 watcher,
268 watcher_region,
269 monitored,
270 };
271
272 self.by_ref.insert(monitor_ref, record);
273 self.by_monitored
274 .entry(monitored)
275 .or_default()
276 .push(monitor_ref);
277 self.by_watcher_region
278 .entry(watcher_region)
279 .or_default()
280 .push(monitor_ref);
281
282 monitor_ref
283 }
284
285 pub fn demonitor(&mut self, monitor_ref: MonitorRef) -> bool {
287 let Some(record) = self.by_ref.remove(&monitor_ref) else {
288 return false;
289 };
290 if let Some(refs) = self.by_monitored.get_mut(&record.monitored) {
291 refs.retain(|r| *r != monitor_ref);
292 if refs.is_empty() {
293 self.by_monitored.remove(&record.monitored);
294 }
295 }
296 if let Some(refs) = self.by_watcher_region.get_mut(&record.watcher_region) {
297 refs.retain(|r| *r != monitor_ref);
298 if refs.is_empty() {
299 self.by_watcher_region.remove(&record.watcher_region);
300 }
301 }
302 true
303 }
304
305 #[must_use]
309 pub fn watchers_of(&self, monitored: TaskId) -> Vec<(MonitorRef, TaskId)> {
310 let Some(refs) = self.by_monitored.get(&monitored) else {
311 return Vec::new();
312 };
313 refs.iter()
314 .filter_map(|mref| self.by_ref.get(mref).map(|rec| (*mref, rec.watcher)))
315 .collect()
316 }
317
318 pub fn remove_monitored(&mut self, monitored: TaskId) -> Vec<MonitorRef> {
322 let Some(refs) = self.by_monitored.remove(&monitored) else {
323 return Vec::new();
324 };
325 let mut removed = Vec::with_capacity(refs.len());
326 for mref in refs {
327 if let Some(record) = self.by_ref.remove(&mref) {
328 if let Some(region_refs) = self.by_watcher_region.get_mut(&record.watcher_region) {
329 region_refs.retain(|r| *r != mref);
330 if region_refs.is_empty() {
331 self.by_watcher_region.remove(&record.watcher_region);
332 }
333 }
334 removed.push(mref);
335 }
336 }
337 removed
338 }
339
340 pub fn cleanup_region(&mut self, region: RegionId) -> Vec<MonitorRef> {
346 let Some(refs) = self.by_watcher_region.remove(®ion) else {
347 return Vec::new();
348 };
349 let mut removed = Vec::with_capacity(refs.len());
350 for mref in refs {
351 if let Some(record) = self.by_ref.remove(&mref) {
352 if let Some(monitored_refs) = self.by_monitored.get_mut(&record.monitored) {
353 monitored_refs.retain(|r| *r != mref);
354 if monitored_refs.is_empty() {
355 self.by_monitored.remove(&record.monitored);
356 }
357 }
358 removed.push(mref);
359 }
360 }
361 removed
362 }
363
364 #[must_use]
366 pub fn len(&self) -> usize {
367 self.by_ref.len()
368 }
369
370 #[must_use]
372 pub fn is_empty(&self) -> bool {
373 self.by_ref.is_empty()
374 }
375
376 #[must_use]
378 pub fn watcher_of(&self, monitor_ref: MonitorRef) -> Option<TaskId> {
379 self.by_ref.get(&monitor_ref).map(|r| r.watcher)
380 }
381
382 #[must_use]
384 pub fn monitored_of(&self, monitor_ref: MonitorRef) -> Option<TaskId> {
385 self.by_ref.get(&monitor_ref).map(|r| r.monitored)
386 }
387}
388
389#[derive(Debug, Default)]
404pub struct DownBatch {
405 entries: Vec<DownBatchEntry>,
406}
407
408#[derive(Debug, Clone)]
410struct DownBatchEntry {
411 completion_vt: Time,
413 notification: DownNotification,
415}
416
417impl DownBatch {
418 #[must_use]
420 pub fn new() -> Self {
421 Self::default()
422 }
423
424 pub fn push(&mut self, completion_vt: Time, notification: DownNotification) {
426 self.entries.push(DownBatchEntry {
427 completion_vt,
428 notification,
429 });
430 }
431
432 #[must_use]
434 pub fn len(&self) -> usize {
435 self.entries.len()
436 }
437
438 #[must_use]
440 pub fn is_empty(&self) -> bool {
441 self.entries.is_empty()
442 }
443
444 #[must_use]
450 pub fn into_sorted(mut self) -> Vec<DownNotification> {
451 self.entries.sort_by(|a, b| {
452 let vt_cmp = a.completion_vt.cmp(&b.completion_vt);
453 vt_cmp
454 .then_with(|| a.notification.monitored.cmp(&b.notification.monitored))
455 .then_with(|| a.notification.monitor_ref.cmp(&b.notification.monitor_ref))
456 });
457 self.entries.into_iter().map(|e| e.notification).collect()
458 }
459}
460
461#[cfg(test)]
466mod tests {
467 #![allow(
468 clippy::pedantic,
469 clippy::nursery,
470 clippy::expect_fun_call,
471 clippy::map_unwrap_or,
472 clippy::cast_possible_wrap,
473 clippy::future_not_send
474 )]
475 use super::*;
476
477 fn test_task_id(index: u32, generation: u32) -> TaskId {
478 TaskId::new_for_test(index, generation)
479 }
480
481 fn test_region_id(index: u32, generation: u32) -> RegionId {
482 RegionId::new_for_test(index, generation)
483 }
484
485 #[test]
488 fn monitor_ref_uniqueness() {
489 let mut set = MonitorSet::new();
490 let region = test_region_id(0, 0);
491 let target = test_task_id(2, 0);
492 let r1 = set.establish(test_task_id(10, 0), region, target);
493 let r2 = set.establish(test_task_id(11, 0), region, target);
494 assert_ne!(r1, r2);
495 assert!(r1 < r2); }
497
498 #[test]
499 fn fresh_monitor_sets_restart_ref_sequence() {
500 let region = test_region_id(0, 0);
501 let target = test_task_id(2, 0);
502
503 let mut first = MonitorSet::new();
504 let first_a = first.establish(test_task_id(10, 0), region, target);
505 let first_b = first.establish(test_task_id(11, 0), region, target);
506
507 let mut second = MonitorSet::new();
508 let second_a = second.establish(test_task_id(20, 0), region, target);
509 let second_b = second.establish(test_task_id(21, 0), region, target);
510
511 assert_eq!(first_a.id(), 1);
512 assert_eq!(first_b.id(), 2);
513 assert_eq!(second_a.id(), 1);
514 assert_eq!(second_b.id(), 2);
515 }
516
517 #[test]
518 fn monitor_ref_display() {
519 let r = MonitorRef::from_raw(42);
520 assert_eq!(format!("{r}"), "MonitorRef(42)");
521 }
522
523 #[test]
524 fn monitor_ref_ordering() {
525 let r1 = MonitorRef::from_raw(1);
526 let r2 = MonitorRef::from_raw(2);
527 let r3 = MonitorRef::from_raw(3);
528 assert!(r1 < r2);
529 assert!(r2 < r3);
530 }
531
532 #[test]
535 fn down_reason_predicates() {
536 assert!(DownReason::Normal.is_normal());
537 assert!(!DownReason::Normal.is_error());
538
539 assert!(DownReason::Error("oops".into()).is_error());
540 assert!(!DownReason::Error("oops".into()).is_normal());
541
542 assert!(DownReason::Cancelled(CancelReason::default()).is_cancelled());
543 assert!(DownReason::Panicked(PanicPayload::new("boom")).is_panicked());
544 }
545
546 #[test]
547 fn down_reason_display() {
548 assert_eq!(format!("{}", DownReason::Normal), "normal");
549 assert!(format!("{}", DownReason::Error("fail".into())).contains("fail"));
550 assert!(format!("{}", DownReason::Panicked(PanicPayload::new("boom"))).contains("boom"));
551 }
552
553 #[test]
554 fn down_reason_from_task_outcome_ok() {
555 let outcome: Outcome<(), crate::error::Error> = Outcome::ok(());
556 let reason = DownReason::from_task_outcome(&outcome);
557 assert!(reason.is_normal());
558 }
559
560 #[test]
561 fn down_reason_from_task_outcome_cancelled() {
562 let outcome: Outcome<(), crate::error::Error> = Outcome::cancelled(CancelReason::default());
563 let reason = DownReason::from_task_outcome(&outcome);
564 assert!(reason.is_cancelled());
565 }
566
567 #[test]
568 fn down_reason_from_task_outcome_panicked() {
569 let outcome: Outcome<(), crate::error::Error> =
570 Outcome::panicked(PanicPayload::new("test"));
571 let reason = DownReason::from_task_outcome(&outcome);
572 assert!(reason.is_panicked());
573 }
574
575 #[test]
578 fn establish_creates_monitor() {
579 let mut set = MonitorSet::new();
580 let watcher = test_task_id(1, 0);
581 let region = test_region_id(0, 0);
582 let target = test_task_id(2, 0);
583
584 let mref = set.establish(watcher, region, target);
585 assert_eq!(set.len(), 1);
586 assert_eq!(set.watcher_of(mref), Some(watcher));
587 assert_eq!(set.monitored_of(mref), Some(target));
588 }
589
590 #[test]
591 fn establish_multiple_monitors_same_target() {
592 let mut set = MonitorSet::new();
593 let w1 = test_task_id(1, 0);
594 let w2 = test_task_id(2, 0);
595 let region = test_region_id(0, 0);
596 let target = test_task_id(3, 0);
597
598 let m1 = set.establish(w1, region, target);
599 let m2 = set.establish(w2, region, target);
600 assert_ne!(m1, m2);
601 assert_eq!(set.len(), 2);
602
603 let watchers = set.watchers_of(target);
604 assert_eq!(watchers.len(), 2);
605 }
606
607 #[test]
608 fn establish_same_watcher_twice_yields_distinct_refs() {
609 let mut set = MonitorSet::new();
610 let watcher = test_task_id(1, 0);
611 let region = test_region_id(0, 0);
612 let target = test_task_id(2, 0);
613
614 let m1 = set.establish(watcher, region, target);
615 let m2 = set.establish(watcher, region, target);
616 assert_ne!(m1, m2);
617 assert_eq!(set.len(), 2);
618 }
619
620 #[test]
621 fn demonitor_removes_monitor() {
622 let mut set = MonitorSet::new();
623 let watcher = test_task_id(1, 0);
624 let region = test_region_id(0, 0);
625 let target = test_task_id(2, 0);
626
627 let mref = set.establish(watcher, region, target);
628 assert!(set.demonitor(mref));
629 assert_eq!(set.len(), 0);
630 assert!(set.watchers_of(target).is_empty());
631 }
632
633 #[test]
634 fn demonitor_nonexistent_returns_false() {
635 let mut set = MonitorSet::new();
636 assert!(!set.demonitor(MonitorRef::from_raw(999)));
637 }
638
639 #[test]
640 fn demonitor_only_removes_specific_monitor() {
641 let mut set = MonitorSet::new();
642 let w1 = test_task_id(1, 0);
643 let w2 = test_task_id(2, 0);
644 let region = test_region_id(0, 0);
645 let target = test_task_id(3, 0);
646
647 let m1 = set.establish(w1, region, target);
648 let _m2 = set.establish(w2, region, target);
649
650 set.demonitor(m1);
651 assert_eq!(set.len(), 1);
652 assert_eq!(set.watchers_of(target).len(), 1);
653 }
654
655 #[test]
658 fn watchers_of_empty() {
659 let set = MonitorSet::new();
660 assert!(set.watchers_of(test_task_id(99, 0)).is_empty());
661 }
662
663 #[test]
664 fn watchers_of_returns_all_watchers() {
665 let mut set = MonitorSet::new();
666 let region = test_region_id(0, 0);
667 let target = test_task_id(10, 0);
668
669 let w1 = test_task_id(1, 0);
670 let w2 = test_task_id(2, 0);
671 let w3 = test_task_id(3, 0);
672
673 let m1 = set.establish(w1, region, target);
674 let m2 = set.establish(w2, region, target);
675 let m3 = set.establish(w3, region, target);
676
677 let watchers = set.watchers_of(target);
678 assert_eq!(watchers.len(), 3);
679
680 let mrefs: Vec<MonitorRef> = watchers.iter().map(|(r, _)| *r).collect();
681 assert!(mrefs.contains(&m1));
682 assert!(mrefs.contains(&m2));
683 assert!(mrefs.contains(&m3));
684
685 let tids: Vec<TaskId> = watchers.iter().map(|(_, t)| *t).collect();
686 assert!(tids.contains(&w1));
687 assert!(tids.contains(&w2));
688 assert!(tids.contains(&w3));
689 }
690
691 #[test]
694 fn remove_monitored_clears_all_watchers() {
695 let mut set = MonitorSet::new();
696 let region = test_region_id(0, 0);
697 let target = test_task_id(10, 0);
698
699 set.establish(test_task_id(1, 0), region, target);
700 set.establish(test_task_id(2, 0), region, target);
701
702 let removed = set.remove_monitored(target);
703 assert_eq!(removed.len(), 2);
704 assert!(set.is_empty());
705 assert!(set.watchers_of(target).is_empty());
706 }
707
708 #[test]
709 fn remove_monitored_preserves_other_monitors() {
710 let mut set = MonitorSet::new();
711 let region = test_region_id(0, 0);
712 let t1 = test_task_id(10, 0);
713 let t2 = test_task_id(20, 0);
714 let watcher = test_task_id(1, 0);
715
716 set.establish(watcher, region, t1);
717 set.establish(watcher, region, t2);
718
719 set.remove_monitored(t1);
720 assert_eq!(set.len(), 1);
721 assert_eq!(set.watchers_of(t2).len(), 1);
722 }
723
724 #[test]
727 fn cleanup_region_removes_all_monitors_in_region() {
728 let mut set = MonitorSet::new();
729 let r1 = test_region_id(1, 0);
730 let r2 = test_region_id(2, 0);
731 let target = test_task_id(10, 0);
732
733 set.establish(test_task_id(1, 0), r1, target);
735 set.establish(test_task_id(2, 0), r2, target);
737
738 let removed = set.cleanup_region(r1);
739 assert_eq!(removed.len(), 1);
740 assert_eq!(set.len(), 1);
741 assert_eq!(set.watchers_of(target).len(), 1);
743 }
744
745 #[test]
746 fn cleanup_region_empty_is_noop() {
747 let mut set = MonitorSet::new();
748 let removed = set.cleanup_region(test_region_id(99, 0));
749 assert!(removed.is_empty());
750 }
751
752 #[test]
753 fn cleanup_region_cleans_monitored_index() {
754 let mut set = MonitorSet::new();
755 let region = test_region_id(1, 0);
756 let target = test_task_id(10, 0);
757
758 set.establish(test_task_id(1, 0), region, target);
759 set.cleanup_region(region);
760
761 assert!(set.watchers_of(target).is_empty());
763 }
764
765 #[test]
768 fn down_batch_empty() {
769 let batch = DownBatch::new();
770 assert!(batch.is_empty());
771 assert_eq!(batch.len(), 0);
772 assert!(batch.into_sorted().is_empty());
773 }
774
775 #[test]
776 fn down_batch_single_item() {
777 let mut batch = DownBatch::new();
778 let notif = DownNotification {
779 monitored: test_task_id(1, 0),
780 reason: DownReason::Normal,
781 monitor_ref: MonitorRef::from_raw(1),
782 };
783 batch.push(Time::from_nanos(100), notif);
784 assert_eq!(batch.len(), 1);
785
786 let sorted = batch.into_sorted();
787 assert_eq!(sorted.len(), 1);
788 assert_eq!(sorted[0].monitored, test_task_id(1, 0));
789 }
790
791 #[test]
792 fn down_batch_sorts_by_virtual_time() {
793 let mut batch = DownBatch::new();
794
795 batch.push(
797 Time::from_nanos(300),
798 DownNotification {
799 monitored: test_task_id(1, 0),
800 reason: DownReason::Normal,
801 monitor_ref: MonitorRef::from_raw(1),
802 },
803 );
804 batch.push(
805 Time::from_nanos(100),
806 DownNotification {
807 monitored: test_task_id(2, 0),
808 reason: DownReason::Normal,
809 monitor_ref: MonitorRef::from_raw(2),
810 },
811 );
812 batch.push(
813 Time::from_nanos(200),
814 DownNotification {
815 monitored: test_task_id(3, 0),
816 reason: DownReason::Normal,
817 monitor_ref: MonitorRef::from_raw(3),
818 },
819 );
820
821 let sorted = batch.into_sorted();
822 assert_eq!(sorted[0].monitored, test_task_id(2, 0)); assert_eq!(sorted[1].monitored, test_task_id(3, 0)); assert_eq!(sorted[2].monitored, test_task_id(1, 0)); }
826
827 #[test]
828 fn down_batch_tie_breaks_by_task_id() {
829 let mut batch = DownBatch::new();
830 let same_vt = Time::from_nanos(100);
831
832 batch.push(
834 same_vt,
835 DownNotification {
836 monitored: test_task_id(5, 0),
837 reason: DownReason::Normal,
838 monitor_ref: MonitorRef::from_raw(1),
839 },
840 );
841 batch.push(
842 same_vt,
843 DownNotification {
844 monitored: test_task_id(1, 0),
845 reason: DownReason::Normal,
846 monitor_ref: MonitorRef::from_raw(2),
847 },
848 );
849 batch.push(
850 same_vt,
851 DownNotification {
852 monitored: test_task_id(3, 0),
853 reason: DownReason::Normal,
854 monitor_ref: MonitorRef::from_raw(3),
855 },
856 );
857
858 let sorted = batch.into_sorted();
859 assert_eq!(sorted[0].monitored, test_task_id(1, 0));
860 assert_eq!(sorted[1].monitored, test_task_id(3, 0));
861 assert_eq!(sorted[2].monitored, test_task_id(5, 0));
862 }
863
864 #[test]
865 fn down_batch_tie_breaks_duplicate_target_by_monitor_ref() {
866 let mut batch = DownBatch::new();
867 let same_vt = Time::from_nanos(100);
868 let same_target = test_task_id(7, 0);
869
870 batch.push(
871 same_vt,
872 DownNotification {
873 monitored: same_target,
874 reason: DownReason::Normal,
875 monitor_ref: MonitorRef::from_raw(3),
876 },
877 );
878 batch.push(
879 same_vt,
880 DownNotification {
881 monitored: same_target,
882 reason: DownReason::Normal,
883 monitor_ref: MonitorRef::from_raw(1),
884 },
885 );
886 batch.push(
887 same_vt,
888 DownNotification {
889 monitored: same_target,
890 reason: DownReason::Normal,
891 monitor_ref: MonitorRef::from_raw(2),
892 },
893 );
894
895 let sorted = batch.into_sorted();
896 let refs: Vec<u64> = sorted.into_iter().map(|n| n.monitor_ref.id()).collect();
897 assert_eq!(refs, vec![1, 2, 3]);
898 }
899
900 #[test]
901 fn down_batch_tie_breaks_by_generation_then_slot() {
902 let mut batch = DownBatch::new();
903 let same_vt = Time::from_nanos(100);
904
905 batch.push(
909 same_vt,
910 DownNotification {
911 monitored: test_task_id(1, 2), reason: DownReason::Normal,
913 monitor_ref: MonitorRef::from_raw(1),
914 },
915 );
916 batch.push(
917 same_vt,
918 DownNotification {
919 monitored: test_task_id(2, 1), reason: DownReason::Normal,
921 monitor_ref: MonitorRef::from_raw(2),
922 },
923 );
924
925 let sorted = batch.into_sorted();
926 assert_eq!(sorted.len(), 2);
930 let first = sorted[0].monitored;
932 let second = sorted[1].monitored;
933 assert_ne!(first, second);
934 }
935
936 #[test]
937 fn down_batch_mixed_vt_and_tid_ordering() {
938 let mut batch = DownBatch::new();
939
940 batch.push(
942 Time::from_nanos(200),
943 DownNotification {
944 monitored: test_task_id(3, 0),
945 reason: DownReason::Normal,
946 monitor_ref: MonitorRef::from_raw(1),
947 },
948 );
949 batch.push(
950 Time::from_nanos(100),
951 DownNotification {
952 monitored: test_task_id(5, 0),
953 reason: DownReason::Error("err".into()),
954 monitor_ref: MonitorRef::from_raw(2),
955 },
956 );
957 batch.push(
958 Time::from_nanos(100),
959 DownNotification {
960 monitored: test_task_id(2, 0),
961 reason: DownReason::Cancelled(CancelReason::default()),
962 monitor_ref: MonitorRef::from_raw(3),
963 },
964 );
965 batch.push(
966 Time::from_nanos(200),
967 DownNotification {
968 monitored: test_task_id(1, 0),
969 reason: DownReason::Panicked(PanicPayload::new("boom")),
970 monitor_ref: MonitorRef::from_raw(4),
971 },
972 );
973
974 let sorted = batch.into_sorted();
975 assert_eq!(sorted[0].monitored, test_task_id(2, 0));
977 assert_eq!(sorted[1].monitored, test_task_id(5, 0));
978 assert_eq!(sorted[2].monitored, test_task_id(1, 0));
980 assert_eq!(sorted[3].monitored, test_task_id(3, 0));
981 }
982
983 #[test]
986 fn end_to_end_monitor_to_notification() {
987 let mut set = MonitorSet::new();
988 let region = test_region_id(0, 0);
989 let watcher = test_task_id(1, 0);
990 let target1 = test_task_id(10, 0);
991 let target2 = test_task_id(20, 0);
992
993 let m1 = set.establish(watcher, region, target1);
994 let m2 = set.establish(watcher, region, target2);
995
996 let completion_vt = Time::from_nanos(500);
998 let mut batch = DownBatch::new();
999
1000 for (mref, _watcher_tid) in set.watchers_of(target1) {
1001 batch.push(
1002 completion_vt,
1003 DownNotification {
1004 monitored: target1,
1005 reason: DownReason::Normal,
1006 monitor_ref: mref,
1007 },
1008 );
1009 }
1010 for (mref, _watcher_tid) in set.watchers_of(target2) {
1011 batch.push(
1012 completion_vt,
1013 DownNotification {
1014 monitored: target2,
1015 reason: DownReason::Error("fail".into()),
1016 monitor_ref: mref,
1017 },
1018 );
1019 }
1020
1021 let sorted = batch.into_sorted();
1022 assert_eq!(sorted.len(), 2);
1023 assert_eq!(sorted[0].monitored, target1);
1025 assert_eq!(sorted[0].monitor_ref, m1);
1026 assert!(sorted[0].reason.is_normal());
1027
1028 assert_eq!(sorted[1].monitored, target2);
1029 assert_eq!(sorted[1].monitor_ref, m2);
1030 assert!(sorted[1].reason.is_error());
1031
1032 set.remove_monitored(target1);
1034 set.remove_monitored(target2);
1035 assert!(set.is_empty());
1036 }
1037
1038 #[test]
1039 fn region_cleanup_prevents_stale_notifications() {
1040 let mut set = MonitorSet::new();
1041 let region = test_region_id(1, 0);
1042 let watcher = test_task_id(1, 0);
1043 let target = test_task_id(10, 0);
1044
1045 set.establish(watcher, region, target);
1046
1047 set.cleanup_region(region);
1049
1050 assert!(set.watchers_of(target).is_empty());
1052 assert!(set.is_empty());
1053 }
1054
1055 #[test]
1068 fn conformance_multiple_watchers_independent_notifications() {
1069 let mut set = MonitorSet::new();
1070 let region = test_region_id(0, 0);
1071 let target = test_task_id(100, 0);
1072
1073 let w1 = test_task_id(1, 0);
1074 let w2 = test_task_id(2, 0);
1075 let w3 = test_task_id(3, 0);
1076 let w4 = test_task_id(4, 0);
1077
1078 let m1 = set.establish(w1, region, target);
1079 let m2 = set.establish(w2, region, target);
1080 let m3 = set.establish(w3, region, target);
1081 let m4 = set.establish(w4, region, target);
1082
1083 let watchers = set.watchers_of(target);
1085 assert_eq!(watchers.len(), 4);
1086
1087 let completion_vt = Time::from_nanos(1000);
1088 let mut batch = DownBatch::new();
1089 for (mref, _watcher) in &watchers {
1090 batch.push(
1091 completion_vt,
1092 DownNotification {
1093 monitored: target,
1094 reason: DownReason::Error("crash".into()),
1095 monitor_ref: *mref,
1096 },
1097 );
1098 }
1099
1100 let sorted = batch.into_sorted();
1101 assert_eq!(sorted.len(), 4, "each watcher must receive a notification");
1102
1103 for notif in &sorted {
1105 assert_eq!(notif.monitored, target);
1106 assert!(notif.reason.is_error());
1107 }
1108
1109 let mrefs: Vec<MonitorRef> = sorted.iter().map(|n| n.monitor_ref).collect();
1111 assert!(mrefs.contains(&m1));
1112 assert!(mrefs.contains(&m2));
1113 assert!(mrefs.contains(&m3));
1114 assert!(mrefs.contains(&m4));
1115 }
1116
1117 #[test]
1121 fn conformance_simultaneous_downs_deterministic_order() {
1122 let mut set = MonitorSet::new();
1123 let region = test_region_id(0, 0);
1124 let watcher = test_task_id(1, 0);
1125
1126 let targets: Vec<TaskId> = (10..15).map(|i| test_task_id(i, 0)).collect();
1128 let mrefs: Vec<MonitorRef> = targets
1129 .iter()
1130 .map(|t| set.establish(watcher, region, *t))
1131 .collect();
1132
1133 let same_vt = Time::from_nanos(500);
1135 let mut batch = DownBatch::new();
1136
1137 for i in (0..5).rev() {
1139 batch.push(
1140 same_vt,
1141 DownNotification {
1142 monitored: targets[i],
1143 reason: DownReason::Error(format!("error_{i}")),
1144 monitor_ref: mrefs[i],
1145 },
1146 );
1147 }
1148
1149 let sorted = batch.into_sorted();
1150 assert_eq!(sorted.len(), 5);
1151
1152 for (i, notif) in sorted.iter().enumerate() {
1155 assert_eq!(
1156 notif.monitored,
1157 targets[i],
1158 "notification {i} should be for target tid({})",
1159 10 + i
1160 );
1161 }
1162
1163 for _trial in 0..10 {
1165 let mut batch2 = DownBatch::new();
1166 for i in (0..5).rev() {
1167 batch2.push(
1168 same_vt,
1169 DownNotification {
1170 monitored: targets[i],
1171 reason: DownReason::Error(format!("error_{i}")),
1172 monitor_ref: mrefs[i],
1173 },
1174 );
1175 }
1176 let sorted2 = batch2.into_sorted();
1177 for (i, notif) in sorted2.iter().enumerate() {
1178 assert_eq!(notif.monitored, targets[i]);
1179 }
1180 }
1181 }
1182
1183 #[test]
1187 fn conformance_mixed_vt_deterministic_interleaving() {
1188 let mut set = MonitorSet::new();
1189 let region = test_region_id(0, 0);
1190 let watcher = test_task_id(1, 0);
1191
1192 let t_a = test_task_id(5, 0);
1193 let t_b = test_task_id(3, 0);
1194 let t_c = test_task_id(8, 0);
1195 let t_d = test_task_id(2, 0);
1196
1197 let m_a = set.establish(watcher, region, t_a);
1198 let m_b = set.establish(watcher, region, t_b);
1199 let m_c = set.establish(watcher, region, t_c);
1200 let m_d = set.establish(watcher, region, t_d);
1201
1202 let mut batch = DownBatch::new();
1203 batch.push(
1205 Time::from_nanos(200),
1206 DownNotification {
1207 monitored: t_a,
1208 reason: DownReason::Error("a".into()),
1209 monitor_ref: m_a,
1210 },
1211 );
1212 batch.push(
1213 Time::from_nanos(100),
1214 DownNotification {
1215 monitored: t_b,
1216 reason: DownReason::Panicked(PanicPayload::new("b")),
1217 monitor_ref: m_b,
1218 },
1219 );
1220 batch.push(
1221 Time::from_nanos(200),
1222 DownNotification {
1223 monitored: t_c,
1224 reason: DownReason::Normal,
1225 monitor_ref: m_c,
1226 },
1227 );
1228 batch.push(
1229 Time::from_nanos(100),
1230 DownNotification {
1231 monitored: t_d,
1232 reason: DownReason::Cancelled(CancelReason::default()),
1233 monitor_ref: m_d,
1234 },
1235 );
1236
1237 let sorted = batch.into_sorted();
1238 assert_eq!(sorted[0].monitored, t_d); assert_eq!(sorted[1].monitored, t_b); assert_eq!(sorted[2].monitored, t_a); assert_eq!(sorted[3].monitored, t_c); }
1244
1245 #[test]
1249 fn conformance_cancellation_cleanup_cross_region() {
1250 let mut set = MonitorSet::new();
1251 let r_closing = test_region_id(1, 0);
1252 let r_open = test_region_id(2, 0);
1253 let target = test_task_id(100, 0);
1254
1255 let w_closing = test_task_id(1, 0);
1256 let w_open = test_task_id(2, 0);
1257
1258 set.establish(w_closing, r_closing, target);
1259 let m_open = set.establish(w_open, r_open, target);
1260
1261 let removed = set.cleanup_region(r_closing);
1263 assert_eq!(removed.len(), 1);
1264
1265 let watchers = set.watchers_of(target);
1267 assert_eq!(watchers.len(), 1);
1268 assert_eq!(watchers[0].0, m_open);
1269 assert_eq!(watchers[0].1, w_open);
1270
1271 let mut batch = DownBatch::new();
1273 for (mref, _) in &watchers {
1274 batch.push(
1275 Time::from_nanos(500),
1276 DownNotification {
1277 monitored: target,
1278 reason: DownReason::Error("target died".into()),
1279 monitor_ref: *mref,
1280 },
1281 );
1282 }
1283
1284 let sorted = batch.into_sorted();
1285 assert_eq!(
1286 sorted.len(),
1287 1,
1288 "only the open-region watcher gets notified"
1289 );
1290 assert_eq!(sorted[0].monitor_ref, m_open);
1291 }
1292
1293 #[test]
1296 fn conformance_cleanup_index_consistency() {
1297 let mut set = MonitorSet::new();
1298 let r1 = test_region_id(1, 0);
1299 let r2 = test_region_id(2, 0);
1300
1301 let t1 = test_task_id(1, 0);
1302 let t2 = test_task_id(2, 0);
1303 let t3 = test_task_id(3, 0);
1304 let target = test_task_id(100, 0);
1305
1306 set.establish(t1, r1, target);
1308 set.establish(t2, r1, target);
1309 let m3 = set.establish(t3, r2, target);
1310
1311 set.cleanup_region(r1);
1313
1314 assert_eq!(set.len(), 1);
1316 assert_eq!(set.watchers_of(target).len(), 1);
1317 assert_eq!(set.watcher_of(m3), Some(t3));
1318 assert_eq!(set.monitored_of(m3), Some(target));
1319
1320 set.cleanup_region(r2);
1322 assert!(set.is_empty());
1323 assert!(set.watchers_of(target).is_empty());
1324 }
1325
1326 #[test]
1329 fn conformance_monotone_severity_in_down() {
1330 let outcomes = vec![
1331 ("Normal", DownReason::Normal),
1332 ("Error", DownReason::Error("fail".into())),
1333 ("Cancelled", DownReason::Cancelled(CancelReason::default())),
1334 ("Panicked", DownReason::Panicked(PanicPayload::new("boom"))),
1335 ];
1336
1337 for (name, reason) in outcomes {
1338 let notif = DownNotification {
1339 monitored: test_task_id(1, 0),
1340 reason: reason.clone(),
1341 monitor_ref: MonitorRef::from_raw(1),
1342 };
1343
1344 match name {
1346 "Normal" => assert!(notif.reason.is_normal()),
1347 "Error" => assert!(notif.reason.is_error()),
1348 "Cancelled" => assert!(notif.reason.is_cancelled()),
1349 "Panicked" => assert!(notif.reason.is_panicked()),
1350 _ => unreachable!(),
1351 }
1352 }
1353 }
1354
1355 #[test]
1358 fn conformance_sequential_cleanup_no_leaks() {
1359 let mut set = MonitorSet::new();
1360 let r1 = test_region_id(1, 0);
1361 let r2 = test_region_id(2, 0);
1362
1363 let w1 = test_task_id(1, 0);
1364 let w2 = test_task_id(2, 0);
1365 let t1 = test_task_id(10, 0);
1366 let t2 = test_task_id(20, 0);
1367
1368 set.establish(w1, r1, t1);
1370 set.establish(w1, r1, t2);
1371 set.establish(w2, r2, t1);
1373
1374 assert_eq!(set.len(), 3);
1375
1376 set.remove_monitored(t1);
1378 assert_eq!(set.len(), 1); set.cleanup_region(r1);
1382 assert!(set.is_empty());
1383
1384 assert!(set.watchers_of(t1).is_empty());
1386 assert!(set.watchers_of(t2).is_empty());
1387 assert_eq!(set.len(), 0);
1388 }
1389
1390 #[test]
1393 fn conformance_demonitor_selective_cancellation() {
1394 let mut set = MonitorSet::new();
1395 let region = test_region_id(0, 0);
1396 let target = test_task_id(100, 0);
1397
1398 let w1 = test_task_id(1, 0);
1399 let w2 = test_task_id(2, 0);
1400 let w3 = test_task_id(3, 0);
1401
1402 let m1 = set.establish(w1, region, target);
1403 let _m2 = set.establish(w2, region, target);
1404 let _m3 = set.establish(w3, region, target);
1405
1406 assert!(set.demonitor(m1));
1408
1409 let watchers = set.watchers_of(target);
1411 assert_eq!(watchers.len(), 2);
1412
1413 let watcher_tids: Vec<TaskId> = watchers.iter().map(|(_, t)| *t).collect();
1414 assert!(
1415 !watcher_tids.contains(&w1),
1416 "demonitored watcher must not appear"
1417 );
1418 assert!(watcher_tids.contains(&w2));
1419 assert!(watcher_tids.contains(&w3));
1420 }
1421
1422 #[test]
1423 fn monitor_ref_debug_clone_copy_eq_hash_ord() {
1424 use std::collections::HashSet;
1425
1426 let r = MonitorRef::from_raw(42);
1427 let dbg = format!("{r:?}");
1428 assert!(dbg.contains("MonitorRef"));
1429
1430 let r2 = r;
1431 assert_eq!(r, r2);
1432
1433 let r3 = r;
1435 assert_eq!(r, r3);
1436
1437 let r4 = MonitorRef::from_raw(100);
1439 assert!(r < r4);
1440
1441 let mut set = HashSet::new();
1443 set.insert(r);
1444 set.insert(r4);
1445 assert_eq!(set.len(), 2);
1446 }
1447
1448 #[test]
1449 fn down_reason_debug_clone_eq() {
1450 let d = DownReason::Normal;
1451 let dbg = format!("{d:?}");
1452 assert!(dbg.contains("Normal"));
1453
1454 let d2 = d.clone();
1455 assert_eq!(d, d2);
1456
1457 let d3 = DownReason::Error("oops".into());
1458 assert_ne!(d, d3);
1459 }
1460}
1461
1462#[cfg(test)]
1467#[path = "monitor_conformance_tests.rs"]
1468mod monitor_conformance_tests;
1469
1470#[cfg(test)]
1471mod conformance_integration {
1472 use super::monitor_conformance_tests::{MonitorConformanceHarness, TestVerdict};
1473
1474 #[test]
1475 fn monitor_conformance_suite() {
1476 crate::test_utils::init_test_logging();
1477
1478 let mut harness = MonitorConformanceHarness::new();
1479
1480 let results = harness.run_full_suite();
1482
1483 let mut failures = Vec::new();
1484 let mut passes = 0;
1485
1486 for result in results {
1487 match result.verdict {
1488 TestVerdict::Pass => {
1489 passes += 1;
1490 }
1491 TestVerdict::Fail(reason) => {
1492 failures.push(format!("{}: {}", result.test_name, reason));
1493 }
1494 }
1495 }
1496
1497 assert!(
1498 failures.is_empty(),
1499 "Monitor conformance failures:\n{}",
1500 failures.join("\n")
1501 );
1502
1503 assert!(
1504 passes > 0,
1505 "No conformance tests passed - harness may be broken"
1506 );
1507
1508 crate::test_complete!("monitor_conformance_suite");
1509 }
1510}