1use crate::{
2 Error,
3 telemetry::metrics::raw::Gauge,
4 utils::{extract_panic_message, supervision::Tree},
5};
6use commonware_utils::{
7 channel::oneshot,
8 sync::{Exclusive, Mutex, Once},
9};
10use futures::{
11 future::{Either, poll_fn, select},
12 pin_mut,
13 stream::{AbortHandle, Abortable, Aborted},
14};
15use std::{
16 any::Any,
17 cell::UnsafeCell,
18 future::Future,
19 panic::{AssertUnwindSafe, catch_unwind, resume_unwind},
20 pin::Pin,
21 sync::Arc,
22 task::{Context, Poll},
23 vec::IntoIter,
24};
25use tracing::error;
26
27pub struct Handle<T>
33where
34 T: Send + 'static,
35{
36 state: HandleState<T>,
37}
38
39type BoxedCompletion<T> = Pin<Box<dyn Future<Output = Result<T, Error>> + Send + 'static>>;
41
42enum HandleState<T>
44where
45 T: Send + 'static,
46{
47 Task {
48 receiver: oneshot::Receiver<Result<T, Error>>,
49 abort_handle: AbortHandle,
50 metric: MetricHandle,
51 },
52 Completion {
53 future: Abortable<Completion<T>>,
54 abort_handle: AbortHandle,
55 },
56}
57
58struct HandleGroup<T>(IntoIter<AbortOnDrop<T>>)
61where
62 T: Send + 'static;
63
64impl<T: Send + 'static> HandleGroup<T> {
65 fn abort(&self) {
66 for guard in self.0.as_slice() {
67 guard.0.abort();
68 }
69 }
70}
71
72impl<T> Drop for HandleGroup<T>
73where
74 T: Send + 'static,
75{
76 fn drop(&mut self) {
77 self.abort();
78 }
79}
80
81struct TaskGuard {
83 tree: Arc<Tree>,
85 metric: MetricHandle,
87}
88
89impl Drop for TaskGuard {
90 fn drop(&mut self) {
91 self.tree.abort();
92 self.metric.finish();
93 }
94}
95
96pub(crate) struct FactoryGuard<'a> {
101 tree: &'a Arc<Tree>,
103 metric: Option<MetricHandle>,
105}
106
107impl<'a> FactoryGuard<'a> {
108 pub(crate) const fn new(tree: &'a Arc<Tree>, metric: MetricHandle) -> Self {
110 Self {
111 tree,
112 metric: Some(metric),
113 }
114 }
115
116 pub(crate) fn disarm(mut self) -> MetricHandle {
118 self.metric.take().expect("factory guard disarmed twice")
119 }
120}
121
122impl Drop for FactoryGuard<'_> {
123 fn drop(&mut self) {
124 if let Some(metric) = &self.metric {
125 metric.finish();
126 self.tree.abort();
127 }
128 }
129}
130
131enum Completion<T>
133where
134 T: Send + 'static,
135{
136 Receiver(oneshot::Receiver<Result<T, Error>>),
137 Future(Exclusive<BoxedCompletion<T>>),
139}
140
141impl<T> Unpin for Completion<T> where T: Send + 'static {}
142
143impl<T> Future for Completion<T>
144where
145 T: Send + 'static,
146{
147 type Output = Result<T, Error>;
148
149 fn poll(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
150 match &mut *self {
151 Self::Receiver(receiver) => Pin::new(receiver)
152 .poll(cx)
153 .map(|result| result.unwrap_or(Err(Error::Closed))),
154 Self::Future(future) => future.get_mut().as_mut().poll(cx),
155 }
156 }
157}
158
159struct FutureCell<F>(UnsafeCell<F>);
169
170impl<F: Future> Future for FutureCell<F> {
171 type Output = std::thread::Result<F::Output>;
172
173 fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
174 let future = unsafe { self.map_unchecked_mut(|cell| cell.0.get_mut()) };
177 catch_unwind(AssertUnwindSafe(|| future.poll(cx)))?.map(Ok)
178 }
179}
180
181impl<T> Handle<T>
182where
183 T: Send + 'static,
184{
185 #[inline(always)]
190 pub(crate) fn init<F>(
191 f: F,
192 metric: MetricHandle,
193 panicker: Panicker,
194 tree: Arc<Tree>,
195 ) -> (impl Future<Output = ()>, Self)
196 where
197 F: Future<Output = T> + Send + 'static,
198 {
199 let (sender, receiver) = oneshot::channel();
201 let (abort_handle, abort_registration) = AbortHandle::new_pair();
202
203 tree.register(Aborter::new(abort_handle.clone(), metric.clone()));
205
206 let guard = TaskGuard {
209 tree,
210 metric: metric.clone(),
211 };
212
213 let task = async move {
219 let _guard = guard;
222
223 let result = Abortable::new(FutureCell(UnsafeCell::new(f)), abort_registration).await;
225
226 match result {
228 Ok(Ok(result)) => {
229 let _ = sender.send(Ok(result));
230 }
231 Ok(Err(panic)) => {
232 panicker.notify(panic);
233 let _ = sender.send(Err(Error::Exited));
234 }
235 Err(Aborted) => {}
236 }
237 };
238
239 (
240 task,
241 Self {
242 state: HandleState::Task {
243 receiver,
244 abort_handle,
245 metric,
246 },
247 },
248 )
249 }
250
251 pub fn from_receiver(receiver: oneshot::Receiver<Result<T, Error>>) -> Self {
253 let (abort_handle, abort_registration) = AbortHandle::new_pair();
254 Self {
255 state: HandleState::Completion {
256 future: Abortable::new(Completion::Receiver(receiver), abort_registration),
257 abort_handle,
258 },
259 }
260 }
261
262 pub fn from_future<F>(future: F) -> Self
264 where
265 F: Future<Output = Result<T, Error>> + Send + 'static,
266 {
267 let (abort_handle, abort_registration) = AbortHandle::new_pair();
268 Self {
269 state: HandleState::Completion {
270 future: Abortable::new(
271 Completion::Future(Exclusive::new(Box::pin(future))),
272 abort_registration,
273 ),
274 abort_handle,
275 },
276 }
277 }
278
279 pub fn ready(result: Result<T, Error>) -> Self {
281 let (sender, receiver) = oneshot::channel();
282 let _ = sender.send(result);
283 Self::from_receiver(receiver)
284 }
285
286 pub fn select(
313 handles: impl IntoIterator<Item = Self>,
314 ) -> impl Future<Output = Result<T, Error>> + Send + 'static {
315 let mut handles = HandleGroup(
318 handles
319 .into_iter()
320 .map(Self::abort_on_drop)
321 .collect::<Vec<_>>()
322 .into_iter(),
323 );
324
325 async move {
326 if handles.0.as_slice().is_empty() {
327 return Err(Error::Closed);
328 }
329
330 poll_fn(move |cx| {
331 for guard in handles.0.as_mut_slice() {
332 if let Poll::Ready(result) = Pin::new(&mut guard.0).poll(cx) {
333 return Poll::Ready(result);
334 }
335 }
336 Poll::Pending
337 })
338 .await
339 }
340 }
341
342 pub(crate) fn closed(metric: MetricHandle) -> Self {
344 metric.finish();
346
347 let (sender, receiver) = oneshot::channel();
349 drop(sender);
350
351 Self::from_receiver(receiver)
352 }
353
354 pub fn abort(&self) {
356 match &self.state {
357 HandleState::Task {
358 abort_handle,
359 metric,
360 ..
361 } => {
362 abort_handle.abort();
363
364 metric.finish();
367 }
368 HandleState::Completion { abort_handle, .. } => {
369 abort_handle.abort();
370 }
371 }
372 }
373
374 #[cfg(test)]
376 pub(crate) fn aborter(&self) -> Option<Aborter> {
377 match &self.state {
378 HandleState::Task {
379 abort_handle,
380 metric,
381 ..
382 } => Some(Aborter::new(abort_handle.clone(), metric.clone())),
383 HandleState::Completion { .. } => None,
384 }
385 }
386
387 pub const fn abort_on_drop(self) -> AbortOnDrop<T> {
389 AbortOnDrop(self)
390 }
391}
392
393pub struct AbortOnDrop<T: Send + 'static>(Handle<T>);
401
402impl<T: Send + 'static> AbortOnDrop<T> {
403 pub async fn abort(mut self) {
405 self.0.abort();
406 let _ = (&mut self.0).await;
407 }
408
409 pub async fn join(mut self) -> Result<T, Error> {
411 (&mut self.0).await
414 }
415}
416
417impl<T, E1> AbortOnDrop<Result<T, E1>>
418where
419 T: Send + 'static,
420 E1: Send + 'static,
421{
422 pub fn join_all<E2>(guards: Vec<Self>) -> impl Future<Output = Result<Vec<T>, E2>>
434 where
435 E2: From<E1> + From<Error>,
436 {
437 let guards = HandleGroup(guards.into_iter());
438 async move {
439 let mut joined = Vec::with_capacity(guards.0.len());
441 let mut guards = guards;
442 while let Some(guard) = guards.0.as_mut_slice().first_mut() {
443 let result = (&mut guard.0).await;
445 if !matches!(result, Ok(Ok(_))) {
446 guards.abort();
447 }
448 match result
449 .map_err(E2::from)
450 .and_then(|result| result.map_err(E2::from))
451 {
452 Ok(output) => joined.push(output),
453 Err(error) => {
454 drop(joined);
456 drop(guards.0.next());
457 futures::future::join_all(guards.0.by_ref().map(Self::abort)).await;
458 return Err(error);
459 }
460 }
461 drop(guards.0.next());
462 }
463 Ok(joined)
464 }
465 }
466}
467
468impl<T: Send + 'static> Drop for AbortOnDrop<T> {
469 fn drop(&mut self) {
470 self.0.abort();
471 }
472}
473
474impl<T> Future for Handle<T>
475where
476 T: Send + 'static,
477{
478 type Output = Result<T, Error>;
479
480 fn poll(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
481 match &mut self.state {
482 HandleState::Task { receiver, .. } => Pin::new(receiver)
483 .poll(cx)
484 .map(|result| result.unwrap_or_else(|_| Err(Error::Closed))),
485 HandleState::Completion { future, .. } => Pin::new(future)
486 .poll(cx)
487 .map(|result| result.unwrap_or(Err(Error::Aborted))),
488 }
489 }
490}
491
492#[derive(Clone)]
494pub(crate) struct MetricHandle {
495 gauge: Gauge,
496 finished: Arc<Once>,
497}
498
499impl MetricHandle {
500 pub(crate) fn new(gauge: Gauge) -> Self {
503 gauge.inc();
504
505 Self {
506 gauge,
507 finished: Arc::new(Once::new()),
508 }
509 }
510
511 pub(crate) fn finish(&self) {
516 let gauge = self.gauge.clone();
517 self.finished.call_once(move || {
518 gauge.dec();
519 });
520 }
521}
522
523pub type Panic = Box<dyn Any + Send + 'static>;
525
526#[derive(Clone)]
528pub(crate) struct Panicker {
529 catch: bool,
530 sender: Arc<Mutex<Option<oneshot::Sender<Panic>>>>,
531}
532
533impl Panicker {
534 pub(crate) fn new(catch: bool) -> (Self, Panicked) {
536 let (sender, receiver) = oneshot::channel();
537 let panicker = Self {
538 catch,
539 sender: Arc::new(Mutex::new(Some(sender))),
540 };
541 let panicked = Panicked { receiver };
542 (panicker, panicked)
543 }
544
545 #[commonware_macros::stability(ALPHA)]
547 pub(crate) const fn catch(&self) -> bool {
548 self.catch
549 }
550
551 pub(crate) fn notify(&self, panic: Box<dyn Any + Send + 'static>) {
553 let err = extract_panic_message(&*panic);
555 error!(?err, "task panicked");
556
557 if self.catch {
559 return;
560 }
561
562 let mut sender = self.sender.lock();
564 let Some(sender) = sender.take() else {
565 return;
566 };
567
568 let _ = sender.send(panic);
570 }
571}
572
573pub(crate) struct Panicked {
575 receiver: oneshot::Receiver<Panic>,
576}
577
578impl Panicked {
579 pub(crate) async fn interrupt<Fut>(self, task: Fut) -> Fut::Output
581 where
582 Fut: Future,
583 {
584 let panicked = self.receiver;
586 pin_mut!(panicked);
587 pin_mut!(task);
588 match select(panicked, task).await {
589 Either::Left((panic, task)) => match panic {
590 Ok(panic) => {
592 resume_unwind(panic);
593 }
594 Err(_) => task.await,
597 },
598 Either::Right((output, mut panicked)) => {
599 if let Ok(panic) = panicked.try_recv() {
601 resume_unwind(panic);
602 }
603 output
604 }
605 }
606 }
607
608 #[cfg(target_os = "linux")]
610 #[commonware_macros::stability(ALPHA)]
611 pub(crate) fn try_take(&mut self) -> Option<Panic> {
612 self.receiver.try_recv().ok()
613 }
614}
615
616pub(crate) struct Aborter {
618 inner: AbortHandle,
619 metric: MetricHandle,
620}
621
622impl Aborter {
623 pub(crate) const fn new(inner: AbortHandle, metric: MetricHandle) -> Self {
625 Self { inner, metric }
626 }
627
628 pub(crate) fn abort(self) {
630 self.inner.abort();
631
632 self.metric.finish();
635 }
636}
637
638#[cfg(test)]
639mod tests {
640 use super::{AbortOnDrop, Handle, MetricHandle, Panicker};
641 use crate::{
642 Error, Metrics as _, Runner, Spawner, Supervisor as _, deterministic,
643 telemetry::metrics::raw::Gauge,
644 utils::{extract_panic_message, reschedule, supervision::Tree},
645 };
646 use commonware_utils::{channel::oneshot, sync::Mutex};
647 use futures::{FutureExt as _, future, poll, stream::AbortHandle};
648 use rstest::rstest;
649 use std::{
650 panic::{AssertUnwindSafe, catch_unwind},
651 sync::{
652 Arc,
653 atomic::{AtomicBool, Ordering},
654 },
655 };
656
657 const METRIC_PREFIX: &str = "runtime_tasks_running{";
658
659 fn running_tasks_for_label(metrics: &str, label: &str) -> Option<u64> {
660 let label_fragment = format!("name=\"{label}\"");
661 metrics.lines().find_map(|line| {
662 if line.starts_with(METRIC_PREFIX) && line.contains(&label_fragment) {
663 line.rsplit_once(' ')
664 .and_then(|(_, value)| value.trim().parse::<u64>().ok())
665 } else {
666 None
667 }
668 })
669 }
670
671 #[test]
672 fn tasks_running_decreased_after_completion() {
673 const LABEL: &str = "tasks_running_after_completion";
674
675 let runner = deterministic::Runner::default();
676 runner.start(|context| async move {
677 let handle = context.child(LABEL).spawn(|_| async move { "done" });
678
679 let metrics = context.encode();
680 assert_eq!(
681 running_tasks_for_label(&metrics, LABEL),
682 Some(1),
683 "expected tasks_running gauge to be 1 before completion: {metrics}",
684 );
685
686 let output = handle.await.expect("task failed");
687 assert_eq!(output, "done");
688
689 let metrics = context.encode();
690 assert_eq!(
691 running_tasks_for_label(&metrics, LABEL),
692 Some(0),
693 "expected tasks_running gauge to return to 0 after completion: {metrics}",
694 );
695 });
696 }
697
698 #[test]
699 fn tasks_running_unchanged_when_handle_dropped() {
700 const LABEL: &str = "tasks_running_unchanged";
701
702 let runner = deterministic::Runner::default();
703 runner.start(|context| async move {
704 let handle = context.child(LABEL).spawn(|_| async move {
705 future::pending::<()>().await;
706 });
707
708 let metrics = context.encode();
709 assert_eq!(
710 running_tasks_for_label(&metrics, LABEL),
711 Some(1),
712 "expected tasks_running gauge to be 1 before dropping handle: {metrics}",
713 );
714
715 drop(handle);
716
717 let metrics = context.encode();
718 assert_eq!(
719 running_tasks_for_label(&metrics, LABEL),
720 Some(1),
721 "dropping handle should not finish metrics: {metrics}",
722 );
723 });
724 }
725
726 #[test]
727 fn tasks_running_decreased_immediately_on_abort_via_handle() {
728 const LABEL: &str = "tasks_running_abort_via_handle";
729
730 let runner = deterministic::Runner::default();
731 runner.start(|context| async move {
732 let handle = context.child(LABEL).spawn(|_| async move {
733 future::pending::<()>().await;
734 });
735
736 let metrics = context.encode();
737 assert_eq!(
738 running_tasks_for_label(&metrics, LABEL),
739 Some(1),
740 "expected tasks_running gauge to be 1 before abort: {metrics}",
741 );
742
743 handle.abort();
744
745 let metrics = context.encode();
746 assert_eq!(
747 running_tasks_for_label(&metrics, LABEL),
748 Some(0),
749 "expected tasks_running gauge to return to 0 after abort: {metrics}",
750 );
751 });
752 }
753
754 #[test]
755 fn select_aborts_remaining_tasks() {
756 const LABEL: &str = "tasks_running_select_remaining";
757
758 deterministic::Runner::default().start(|context| async move {
759 let completed = context.child("select_completed").spawn(|_| async {});
760 let pending = context.child(LABEL).spawn(|_| future::pending());
761
762 Handle::select([completed, pending])
763 .await
764 .expect("task failed");
765
766 let metrics = context.encode();
767 assert_eq!(
768 running_tasks_for_label(&metrics, LABEL),
769 Some(0),
770 "select should abort remaining tasks: {metrics}",
771 );
772 });
773 }
774
775 #[test]
776 fn select_empty_returns_closed() {
777 deterministic::Runner::default().start(|_| async move {
778 assert!(matches!(Handle::<()>::select([]).await, Err(Error::Closed)));
779 });
780 }
781
782 #[test]
783 fn dropping_select_aborts_tasks_before_polling() {
784 const FIRST_LABEL: &str = "tasks_running_select_drop_first";
785 const SECOND_LABEL: &str = "tasks_running_select_drop_second";
786
787 deterministic::Runner::default().start(|context| async move {
788 let first = context
789 .child(FIRST_LABEL)
790 .spawn(|_| future::pending::<()>());
791 let second = context
792 .child(SECOND_LABEL)
793 .spawn(|_| future::pending::<()>());
794
795 drop(Handle::select([first, second]));
796
797 let metrics = context.encode();
798 assert_eq!(running_tasks_for_label(&metrics, FIRST_LABEL), Some(0));
799 assert_eq!(running_tasks_for_label(&metrics, SECOND_LABEL), Some(0));
800 });
801 }
802
803 #[test]
804 fn completion_handle_abort_stops_waiting() {
805 deterministic::Runner::default().start(|_| async move {
806 let (sender, receiver) = oneshot::channel();
807 let handle = Handle::from_receiver(receiver);
808
809 handle.abort();
810
811 assert!(sender.send(Ok(())).is_ok());
812 assert!(matches!(handle.await, Err(Error::Aborted)));
813 });
814 }
815
816 #[test]
817 fn abort_on_drop_cancels_pending_join() {
818 for poll_join in [false, true] {
819 deterministic::Runner::default().start(|context| async move {
820 let (held, released) = oneshot::channel::<()>();
821 let guard = context
822 .child("guarded")
823 .spawn(move |_| async move {
824 let _held = held;
825 future::pending::<()>().await;
826 })
827 .abort_on_drop();
828 let mut join = Box::pin(guard.join());
829 if poll_join {
830 assert!(poll!(&mut join).is_pending());
831 }
832 drop(join);
833 assert!(released.await.is_err());
834 });
835 }
836 }
837
838 #[test]
839 fn abort_on_drop_join_all_drains_pending_task() {
840 deterministic::Runner::default().start(|context| async move {
841 let (held, released) = oneshot::channel::<()>();
842 let pending = context
843 .child("pending")
844 .spawn(move |_| async move {
845 let _held = held;
846 future::pending::<Result<(), Error>>().await
847 })
848 .abort_on_drop();
849 let failed = Handle::ready(Ok(Err(Error::Closed))).abort_on_drop();
850 let result = AbortOnDrop::join_all::<Error>(vec![failed, pending]).await;
851 assert!(matches!(result, Err(Error::Closed)));
852 assert!(
853 released
854 .now_or_never()
855 .expect("task still owns sender")
856 .is_err()
857 );
858 });
859 }
860
861 struct AbortObservation {
862 abort_handle: AbortHandle,
863 observed: Arc<Mutex<Option<bool>>>,
864 }
865
866 impl Drop for AbortObservation {
867 fn drop(&mut self) {
868 *self.observed.lock() = Some(self.abort_handle.is_aborted());
869 }
870 }
871
872 impl From<AbortObservation> for Error {
873 fn from(_error: AbortObservation) -> Self {
874 Self::Closed
875 }
876 }
877
878 #[derive(Debug)]
879 enum JoinCleanup {
880 Unpolled,
881 CollectedOutput,
882 RemainingOutput,
883 PendingCompletion,
884 ErrorConversion,
885 }
886
887 #[rstest]
888 #[case::unpolled(JoinCleanup::Unpolled)]
889 #[case::collected_output(JoinCleanup::CollectedOutput)]
890 #[case::remaining_output(JoinCleanup::RemainingOutput)]
891 #[case::pending_completion(JoinCleanup::PendingCompletion)]
892 #[case::error_conversion(JoinCleanup::ErrorConversion)]
893 fn join_all_aborts_before_destruction(#[case] cleanup: JoinCleanup) {
894 deterministic::Runner::default().start(|context| async move {
895 let pending = context
896 .child("pending")
897 .spawn(|_| future::pending::<Result<Option<AbortObservation>, AbortObservation>>());
898 let abort_handle = pending.aborter().unwrap().inner;
899 assert!(!abort_handle.is_aborted());
900 let observed = Arc::new(Mutex::new(None));
901 let resource = AbortObservation {
902 abort_handle,
903 observed: observed.clone(),
904 };
905 let first = match cleanup {
906 JoinCleanup::PendingCompletion => Handle::from_future(async move {
907 let _resource = resource;
908 future::pending().await
909 }),
910 JoinCleanup::ErrorConversion => Handle::ready(Ok(Err(resource))),
911 _ => Handle::ready(Ok(Ok(Some(resource)))),
912 };
913 let mut handles = vec![first, pending];
914 if matches!(cleanup, JoinCleanup::RemainingOutput) {
915 handles.insert(0, Handle::from_future(future::pending()));
916 handles.insert(0, Handle::ready(Ok(Ok(None))));
917 }
918 let mut joining = Box::pin(AbortOnDrop::join_all::<Error>(
919 handles.into_iter().map(Handle::abort_on_drop).collect(),
920 ));
921 if matches!(cleanup, JoinCleanup::ErrorConversion) {
922 assert!(matches!(joining.await, Err(Error::Closed)));
923 } else {
924 if !matches!(cleanup, JoinCleanup::Unpolled) {
925 assert!(poll!(&mut joining).is_pending());
926 }
927 drop(joining);
928 }
929 assert_eq!(*observed.lock(), Some(true));
930 });
931 }
932
933 #[test]
934 fn abort_on_drop_completion_leaves_producer_running() {
935 deterministic::Runner::default().start(|context| async move {
936 let (release, wait) = oneshot::channel::<()>();
937 let (sender, receiver) = oneshot::channel();
938 let mut producer = context.child("producer").spawn(move |_| async move {
939 wait.await.unwrap();
940 let _ = sender.send(Ok(()));
941 });
942
943 Handle::from_receiver(receiver)
944 .abort_on_drop()
945 .abort()
946 .await;
947 assert!(poll!(&mut producer).is_pending());
948 release.send(()).unwrap();
949 producer.await.unwrap();
950 });
951 }
952
953 #[test]
954 fn tasks_running_decreased_after_blocking_completion() {
955 const LABEL: &str = "tasks_running_after_blocking_completion";
956
957 let runner = deterministic::Runner::default();
958 runner.start(|context| async move {
959 let blocking_handle = context.child(LABEL).shared(true).spawn(|_| async move {
960 42
962 });
963
964 let metrics = context.encode();
965 assert_eq!(
966 running_tasks_for_label(&metrics, LABEL),
967 Some(1),
968 "expected tasks_running gauge to be 1 while blocking task runs: {metrics}",
969 );
970
971 let result = blocking_handle.await.expect("blocking task failed");
972 assert_eq!(result, 42);
973
974 let metrics = context.encode();
975 assert_eq!(
976 running_tasks_for_label(&metrics, LABEL),
977 Some(0),
978 "expected tasks_running gauge to return to 0 after blocking task completes: {metrics}",
979 );
980 });
981 }
982
983 #[test]
984 fn tasks_running_decreased_immediately_on_abort_via_aborter() {
985 const LABEL: &str = "tasks_running_abort_via_aborter";
986
987 let runner = deterministic::Runner::default();
988 runner.start(|context| async move {
989 let handle = context.child(LABEL).spawn(|_| async move {
990 future::pending::<()>().await;
991 });
992
993 let metrics = context.encode();
994 assert_eq!(
995 running_tasks_for_label(&metrics, LABEL),
996 Some(1),
997 "expected tasks_running gauge to be 1 before abort: {metrics}",
998 );
999
1000 let aborter = handle.aborter().unwrap();
1001 aborter.abort();
1002
1003 let metrics = context.encode();
1004 assert_eq!(
1005 running_tasks_for_label(&metrics, LABEL),
1006 Some(0),
1007 "expected tasks_running gauge to return to 0 after abort: {metrics}",
1008 );
1009 });
1010 }
1011
1012 #[test]
1014 fn interrupt_observes_panic_sent_while_completing() {
1015 let (panicker, panicked) = Panicker::new(false);
1016 let result = catch_unwind(AssertUnwindSafe(|| {
1017 panicked
1018 .interrupt(async move {
1019 panicker.notify(Box::new("late"));
1020 7
1021 })
1022 .now_or_never()
1023 }));
1024 let panic = result.expect_err("panic sent while completing was dropped");
1025 assert_eq!(extract_panic_message(&*panic), "late");
1026 }
1027
1028 #[test]
1031 fn init_registers_before_publication() {
1032 let tree = Tree::root();
1034 tree.abort();
1035
1036 let (panicker, _panicked) = Panicker::new(false);
1038 let gauge = Gauge::default();
1039 let polled = Arc::new(AtomicBool::new(false));
1040 let observed = polled.clone();
1041 let (task, handle) = Handle::init(
1042 async move { observed.store(true, Ordering::SeqCst) },
1043 MetricHandle::new(gauge.clone()),
1044 panicker,
1045 tree,
1046 );
1047 assert_eq!(gauge.get(), 0);
1048
1049 assert_eq!(task.now_or_never(), Some(()));
1051 assert!(!polled.load(Ordering::SeqCst));
1052 assert!(matches!(handle.now_or_never(), Some(Err(Error::Closed))));
1053 }
1054
1055 #[commonware_macros::test_group("miri")]
1057 #[test]
1058 fn task_keeps_self_borrow_across_suspension() {
1059 deterministic::Runner::default().start(|context| async move {
1060 let handle = context.child("task").spawn(|_| async move {
1061 let mut value = 0u8;
1062 let borrow = &mut value;
1063 *borrow += 1;
1064 reschedule().await;
1065 *borrow += 1;
1066 value
1067 });
1068 assert_eq!(handle.await.unwrap(), 2);
1069 });
1070 }
1071}