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::{Mutex, Once},
9};
10use futures::{
11 FutureExt as _,
12 future::{Either, poll_fn, select},
13 pin_mut,
14 stream::{AbortHandle, Abortable, Aborted},
15};
16use std::{
17 any::Any,
18 future::Future,
19 panic::{AssertUnwindSafe, resume_unwind},
20 pin::Pin,
21 sync::Arc,
22 task::{Context, Poll},
23};
24use tracing::error;
25
26pub struct Handle<T>
32where
33 T: Send + 'static,
34{
35 state: HandleState<T>,
36}
37
38enum HandleState<T>
40where
41 T: Send + 'static,
42{
43 Task {
44 receiver: oneshot::Receiver<Result<T, Error>>,
45 abort_handle: AbortHandle,
46 metric: MetricHandle,
47 },
48 Completion {
49 future: Abortable<Completion<T>>,
50 abort_handle: AbortHandle,
51 },
52}
53
54struct HandleGroup<T>(Vec<Handle<T>>)
56where
57 T: Send + 'static;
58
59impl<T> Drop for HandleGroup<T>
60where
61 T: Send + 'static,
62{
63 fn drop(&mut self) {
64 for handle in &self.0 {
65 handle.abort();
66 }
67 }
68}
69
70enum Completion<T>
72where
73 T: Send + 'static,
74{
75 Receiver(oneshot::Receiver<Result<T, Error>>),
76 Future(Pin<Box<dyn Future<Output = Result<T, Error>> + Send + 'static>>),
77}
78
79impl<T> Unpin for Completion<T> where T: Send + 'static {}
80
81impl<T> Future for Completion<T>
82where
83 T: Send + 'static,
84{
85 type Output = Result<T, Error>;
86
87 fn poll(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
88 match &mut *self {
89 Self::Receiver(receiver) => Pin::new(receiver)
90 .poll(cx)
91 .map(|result| result.unwrap_or(Err(Error::Closed))),
92 Self::Future(future) => future.as_mut().poll(cx),
93 }
94 }
95}
96
97impl<T> Handle<T>
98where
99 T: Send + 'static,
100{
101 #[inline(always)]
102 pub(crate) fn init<F>(
103 f: F,
104 metric: MetricHandle,
105 panicker: Panicker,
106 tree: Arc<Tree>,
107 ) -> (impl Future<Output = ()>, Self)
108 where
109 F: Future<Output = T> + Send + 'static,
110 {
111 let (sender, receiver) = oneshot::channel();
113 let (abort_handle, abort_registration) = AbortHandle::new_pair();
114
115 let metric_handle = metric.clone();
121 let task = async move {
122 let result =
124 Abortable::new(AssertUnwindSafe(f).catch_unwind(), abort_registration).await;
125
126 match result {
128 Ok(Ok(result)) => {
129 let _ = sender.send(Ok(result));
130 }
131 Ok(Err(panic)) => {
132 panicker.notify(panic);
133 let _ = sender.send(Err(Error::Exited));
134 }
135 Err(Aborted) => {}
136 }
137
138 tree.abort();
140
141 metric_handle.finish();
143 };
144
145 (
146 task,
147 Self {
148 state: HandleState::Task {
149 receiver,
150 abort_handle,
151 metric,
152 },
153 },
154 )
155 }
156
157 pub fn from_receiver(receiver: oneshot::Receiver<Result<T, Error>>) -> Self {
159 let (abort_handle, abort_registration) = AbortHandle::new_pair();
160 Self {
161 state: HandleState::Completion {
162 future: Abortable::new(Completion::Receiver(receiver), abort_registration),
163 abort_handle,
164 },
165 }
166 }
167
168 pub fn from_future<F>(future: F) -> Self
170 where
171 F: Future<Output = Result<T, Error>> + Send + 'static,
172 {
173 let (abort_handle, abort_registration) = AbortHandle::new_pair();
174 Self {
175 state: HandleState::Completion {
176 future: Abortable::new(Completion::Future(Box::pin(future)), abort_registration),
177 abort_handle,
178 },
179 }
180 }
181
182 pub fn ready(result: Result<T, Error>) -> Self {
184 let (sender, receiver) = oneshot::channel();
185 let _ = sender.send(result);
186 Self::from_receiver(receiver)
187 }
188
189 pub fn select(
216 handles: impl IntoIterator<Item = Self>,
217 ) -> impl Future<Output = Result<T, Error>> + Send + 'static {
218 let mut handles = HandleGroup(handles.into_iter().collect::<Vec<_>>());
221
222 async move {
223 if handles.0.is_empty() {
224 return Err(Error::Closed);
225 }
226
227 poll_fn(move |cx| {
228 for handle in &mut handles.0 {
229 if let Poll::Ready(result) = Pin::new(handle).poll(cx) {
230 return Poll::Ready(result);
231 }
232 }
233 Poll::Pending
234 })
235 .await
236 }
237 }
238
239 pub(crate) fn closed(metric: MetricHandle) -> Self {
241 metric.finish();
243
244 let (sender, receiver) = oneshot::channel();
246 drop(sender);
247
248 Self::from_receiver(receiver)
249 }
250
251 pub fn abort(&self) {
253 match &self.state {
254 HandleState::Task {
255 abort_handle,
256 metric,
257 ..
258 } => {
259 abort_handle.abort();
260
261 metric.finish();
264 }
265 HandleState::Completion { abort_handle, .. } => {
266 abort_handle.abort();
267 }
268 }
269 }
270
271 pub(crate) fn aborter(&self) -> Option<Aborter> {
273 match &self.state {
274 HandleState::Task {
275 abort_handle,
276 metric,
277 ..
278 } => Some(Aborter::new(abort_handle.clone(), metric.clone())),
279 HandleState::Completion { .. } => None,
280 }
281 }
282}
283
284impl<T> Future for Handle<T>
285where
286 T: Send + 'static,
287{
288 type Output = Result<T, Error>;
289
290 fn poll(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
291 match &mut self.state {
292 HandleState::Task { receiver, .. } => Pin::new(receiver)
293 .poll(cx)
294 .map(|result| result.unwrap_or_else(|_| Err(Error::Closed))),
295 HandleState::Completion { future, .. } => Pin::new(future)
296 .poll(cx)
297 .map(|result| result.unwrap_or(Err(Error::Aborted))),
298 }
299 }
300}
301
302#[derive(Clone)]
304pub(crate) struct MetricHandle {
305 gauge: Gauge,
306 finished: Arc<Once>,
307}
308
309impl MetricHandle {
310 pub(crate) fn new(gauge: Gauge) -> Self {
313 gauge.inc();
314
315 Self {
316 gauge,
317 finished: Arc::new(Once::new()),
318 }
319 }
320
321 pub(crate) fn finish(&self) {
326 let gauge = self.gauge.clone();
327 self.finished.call_once(move || {
328 gauge.dec();
329 });
330 }
331}
332
333pub type Panic = Box<dyn Any + Send + 'static>;
335
336#[derive(Clone)]
338pub(crate) struct Panicker {
339 catch: bool,
340 sender: Arc<Mutex<Option<oneshot::Sender<Panic>>>>,
341}
342
343impl Panicker {
344 pub(crate) fn new(catch: bool) -> (Self, Panicked) {
346 let (sender, receiver) = oneshot::channel();
347 let panicker = Self {
348 catch,
349 sender: Arc::new(Mutex::new(Some(sender))),
350 };
351 let panicked = Panicked { receiver };
352 (panicker, panicked)
353 }
354
355 #[commonware_macros::stability(ALPHA)]
357 pub(crate) const fn catch(&self) -> bool {
358 self.catch
359 }
360
361 pub(crate) fn notify(&self, panic: Box<dyn Any + Send + 'static>) {
363 let err = extract_panic_message(&*panic);
365 error!(?err, "task panicked");
366
367 if self.catch {
369 return;
370 }
371
372 let mut sender = self.sender.lock();
374 let Some(sender) = sender.take() else {
375 return;
376 };
377
378 let _ = sender.send(panic);
380 }
381}
382
383pub(crate) struct Panicked {
385 receiver: oneshot::Receiver<Panic>,
386}
387
388impl Panicked {
389 pub(crate) async fn interrupt<Fut>(self, task: Fut) -> Fut::Output
391 where
392 Fut: Future,
393 {
394 let panicked = self.receiver;
396 pin_mut!(panicked);
397 pin_mut!(task);
398 match select(panicked, task).await {
399 Either::Left((panic, task)) => match panic {
400 Ok(panic) => {
402 resume_unwind(panic);
403 }
404 Err(_) => task.await,
407 },
408 Either::Right((output, _)) => {
409 output
411 }
412 }
413 }
414}
415
416pub(crate) struct Aborter {
418 inner: AbortHandle,
419 metric: MetricHandle,
420}
421
422impl Aborter {
423 pub(crate) const fn new(inner: AbortHandle, metric: MetricHandle) -> Self {
425 Self { inner, metric }
426 }
427
428 pub(crate) fn abort(self) {
430 self.inner.abort();
431
432 self.metric.finish();
435 }
436}
437
438#[cfg(test)]
439mod tests {
440 use super::Handle;
441 use crate::{Error, Metrics as _, Runner, Spawner, Supervisor as _, deterministic};
442 use commonware_utils::channel::oneshot;
443 use futures::future;
444
445 const METRIC_PREFIX: &str = "runtime_tasks_running{";
446
447 fn running_tasks_for_label(metrics: &str, label: &str) -> Option<u64> {
448 let label_fragment = format!("name=\"{label}\"");
449 metrics.lines().find_map(|line| {
450 if line.starts_with(METRIC_PREFIX) && line.contains(&label_fragment) {
451 line.rsplit_once(' ')
452 .and_then(|(_, value)| value.trim().parse::<u64>().ok())
453 } else {
454 None
455 }
456 })
457 }
458
459 #[test]
460 fn tasks_running_decreased_after_completion() {
461 const LABEL: &str = "tasks_running_after_completion";
462
463 let runner = deterministic::Runner::default();
464 runner.start(|context| async move {
465 let handle = context.child(LABEL).spawn(|_| async move { "done" });
466
467 let metrics = context.encode();
468 assert_eq!(
469 running_tasks_for_label(&metrics, LABEL),
470 Some(1),
471 "expected tasks_running gauge to be 1 before completion: {metrics}",
472 );
473
474 let output = handle.await.expect("task failed");
475 assert_eq!(output, "done");
476
477 let metrics = context.encode();
478 assert_eq!(
479 running_tasks_for_label(&metrics, LABEL),
480 Some(0),
481 "expected tasks_running gauge to return to 0 after completion: {metrics}",
482 );
483 });
484 }
485
486 #[test]
487 fn tasks_running_unchanged_when_handle_dropped() {
488 const LABEL: &str = "tasks_running_unchanged";
489
490 let runner = deterministic::Runner::default();
491 runner.start(|context| async move {
492 let handle = context.child(LABEL).spawn(|_| async move {
493 future::pending::<()>().await;
494 });
495
496 let metrics = context.encode();
497 assert_eq!(
498 running_tasks_for_label(&metrics, LABEL),
499 Some(1),
500 "expected tasks_running gauge to be 1 before dropping handle: {metrics}",
501 );
502
503 drop(handle);
504
505 let metrics = context.encode();
506 assert_eq!(
507 running_tasks_for_label(&metrics, LABEL),
508 Some(1),
509 "dropping handle should not finish metrics: {metrics}",
510 );
511 });
512 }
513
514 #[test]
515 fn tasks_running_decreased_immediately_on_abort_via_handle() {
516 const LABEL: &str = "tasks_running_abort_via_handle";
517
518 let runner = deterministic::Runner::default();
519 runner.start(|context| async move {
520 let handle = context.child(LABEL).spawn(|_| async move {
521 future::pending::<()>().await;
522 });
523
524 let metrics = context.encode();
525 assert_eq!(
526 running_tasks_for_label(&metrics, LABEL),
527 Some(1),
528 "expected tasks_running gauge to be 1 before abort: {metrics}",
529 );
530
531 handle.abort();
532
533 let metrics = context.encode();
534 assert_eq!(
535 running_tasks_for_label(&metrics, LABEL),
536 Some(0),
537 "expected tasks_running gauge to return to 0 after abort: {metrics}",
538 );
539 });
540 }
541
542 #[test]
543 fn select_aborts_remaining_tasks() {
544 const LABEL: &str = "tasks_running_select_remaining";
545
546 deterministic::Runner::default().start(|context| async move {
547 let completed = context.child("select_completed").spawn(|_| async {});
548 let pending = context.child(LABEL).spawn(|_| future::pending());
549
550 Handle::select([completed, pending])
551 .await
552 .expect("task failed");
553
554 let metrics = context.encode();
555 assert_eq!(
556 running_tasks_for_label(&metrics, LABEL),
557 Some(0),
558 "select should abort remaining tasks: {metrics}",
559 );
560 });
561 }
562
563 #[test]
564 fn select_empty_returns_closed() {
565 deterministic::Runner::default().start(|_| async move {
566 assert!(matches!(Handle::<()>::select([]).await, Err(Error::Closed)));
567 });
568 }
569
570 #[test]
571 fn dropping_select_aborts_tasks_before_polling() {
572 const FIRST_LABEL: &str = "tasks_running_select_drop_first";
573 const SECOND_LABEL: &str = "tasks_running_select_drop_second";
574
575 deterministic::Runner::default().start(|context| async move {
576 let first = context
577 .child(FIRST_LABEL)
578 .spawn(|_| future::pending::<()>());
579 let second = context
580 .child(SECOND_LABEL)
581 .spawn(|_| future::pending::<()>());
582
583 drop(Handle::select([first, second]));
584
585 let metrics = context.encode();
586 assert_eq!(running_tasks_for_label(&metrics, FIRST_LABEL), Some(0));
587 assert_eq!(running_tasks_for_label(&metrics, SECOND_LABEL), Some(0));
588 });
589 }
590
591 #[test]
592 fn completion_handle_abort_stops_waiting() {
593 deterministic::Runner::default().start(|_| async move {
594 let (sender, receiver) = oneshot::channel();
595 let handle = Handle::from_receiver(receiver);
596
597 handle.abort();
598
599 assert!(sender.send(Ok(())).is_ok());
600 assert!(matches!(handle.await, Err(Error::Aborted)));
601 });
602 }
603
604 #[test]
605 fn tasks_running_decreased_after_blocking_completion() {
606 const LABEL: &str = "tasks_running_after_blocking_completion";
607
608 let runner = deterministic::Runner::default();
609 runner.start(|context| async move {
610 let blocking_handle = context.child(LABEL).shared(true).spawn(|_| async move {
611 42
613 });
614
615 let metrics = context.encode();
616 assert_eq!(
617 running_tasks_for_label(&metrics, LABEL),
618 Some(1),
619 "expected tasks_running gauge to be 1 while blocking task runs: {metrics}",
620 );
621
622 let result = blocking_handle.await.expect("blocking task failed");
623 assert_eq!(result, 42);
624
625 let metrics = context.encode();
626 assert_eq!(
627 running_tasks_for_label(&metrics, LABEL),
628 Some(0),
629 "expected tasks_running gauge to return to 0 after blocking task completes: {metrics}",
630 );
631 });
632 }
633
634 #[test]
635 fn tasks_running_decreased_immediately_on_abort_via_aborter() {
636 const LABEL: &str = "tasks_running_abort_via_aborter";
637
638 let runner = deterministic::Runner::default();
639 runner.start(|context| async move {
640 let handle = context.child(LABEL).spawn(|_| async move {
641 future::pending::<()>().await;
642 });
643
644 let metrics = context.encode();
645 assert_eq!(
646 running_tasks_for_label(&metrics, LABEL),
647 Some(1),
648 "expected tasks_running gauge to be 1 before abort: {metrics}",
649 );
650
651 let aborter = handle.aborter().unwrap();
652 aborter.abort();
653
654 let metrics = context.encode();
655 assert_eq!(
656 running_tasks_for_label(&metrics, LABEL),
657 Some(0),
658 "expected tasks_running gauge to return to 0 after abort: {metrics}",
659 );
660 });
661 }
662}