commonware_runtime/utils/
handle.rs1use crate::{
2 telemetry::metrics::raw::Gauge,
3 utils::{extract_panic_message, supervision::Tree},
4 Error,
5};
6use commonware_utils::{
7 channel::oneshot,
8 sync::{Mutex, Once},
9};
10use futures::{
11 future::{select, Either},
12 pin_mut,
13 stream::{AbortHandle, Abortable, Aborted},
14 FutureExt as _,
15};
16use std::{
17 any::Any,
18 future::Future,
19 panic::{resume_unwind, AssertUnwindSafe},
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
54enum Completion<T>
56where
57 T: Send + 'static,
58{
59 Receiver(oneshot::Receiver<Result<T, Error>>),
60 Future(Pin<Box<dyn Future<Output = Result<T, Error>> + Send + 'static>>),
61}
62
63impl<T> Unpin for Completion<T> where T: Send + 'static {}
64
65impl<T> Future for Completion<T>
66where
67 T: Send + 'static,
68{
69 type Output = Result<T, Error>;
70
71 fn poll(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
72 match &mut *self {
73 Self::Receiver(receiver) => Pin::new(receiver)
74 .poll(cx)
75 .map(|result| result.unwrap_or(Err(Error::Closed))),
76 Self::Future(future) => future.as_mut().poll(cx),
77 }
78 }
79}
80
81impl<T> Handle<T>
82where
83 T: Send + 'static,
84{
85 #[inline(always)]
86 pub(crate) fn init<F>(
87 f: F,
88 metric: MetricHandle,
89 panicker: Panicker,
90 tree: Arc<Tree>,
91 ) -> (impl Future<Output = ()>, Self)
92 where
93 F: Future<Output = T> + Send + 'static,
94 {
95 let (sender, receiver) = oneshot::channel();
97 let (abort_handle, abort_registration) = AbortHandle::new_pair();
98
99 let metric_handle = metric.clone();
105 let task = async move {
106 let result =
108 Abortable::new(AssertUnwindSafe(f).catch_unwind(), abort_registration).await;
109
110 match result {
112 Ok(Ok(result)) => {
113 let _ = sender.send(Ok(result));
114 }
115 Ok(Err(panic)) => {
116 panicker.notify(panic);
117 let _ = sender.send(Err(Error::Exited));
118 }
119 Err(Aborted) => {}
120 }
121
122 tree.abort();
124
125 metric_handle.finish();
127 };
128
129 (
130 task,
131 Self {
132 state: HandleState::Task {
133 receiver,
134 abort_handle,
135 metric,
136 },
137 },
138 )
139 }
140
141 pub fn from_receiver(receiver: oneshot::Receiver<Result<T, Error>>) -> Self {
143 let (abort_handle, abort_registration) = AbortHandle::new_pair();
144 Self {
145 state: HandleState::Completion {
146 future: Abortable::new(Completion::Receiver(receiver), abort_registration),
147 abort_handle,
148 },
149 }
150 }
151
152 pub fn from_future<F>(future: F) -> Self
154 where
155 F: Future<Output = Result<T, Error>> + Send + 'static,
156 {
157 let (abort_handle, abort_registration) = AbortHandle::new_pair();
158 Self {
159 state: HandleState::Completion {
160 future: Abortable::new(Completion::Future(Box::pin(future)), abort_registration),
161 abort_handle,
162 },
163 }
164 }
165
166 pub fn ready(result: Result<T, Error>) -> Self {
168 let (sender, receiver) = oneshot::channel();
169 let _ = sender.send(result);
170 Self::from_receiver(receiver)
171 }
172
173 pub(crate) fn closed(metric: MetricHandle) -> Self {
175 metric.finish();
177
178 let (sender, receiver) = oneshot::channel();
180 drop(sender);
181
182 Self::from_receiver(receiver)
183 }
184
185 pub fn abort(&self) {
187 match &self.state {
188 HandleState::Task {
189 abort_handle,
190 metric,
191 ..
192 } => {
193 abort_handle.abort();
194
195 metric.finish();
198 }
199 HandleState::Completion { abort_handle, .. } => {
200 abort_handle.abort();
201 }
202 }
203 }
204
205 pub(crate) fn aborter(&self) -> Option<Aborter> {
207 match &self.state {
208 HandleState::Task {
209 abort_handle,
210 metric,
211 ..
212 } => Some(Aborter::new(abort_handle.clone(), metric.clone())),
213 HandleState::Completion { .. } => None,
214 }
215 }
216}
217
218impl<T> Future for Handle<T>
219where
220 T: Send + 'static,
221{
222 type Output = Result<T, Error>;
223
224 fn poll(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
225 match &mut self.state {
226 HandleState::Task { receiver, .. } => Pin::new(receiver)
227 .poll(cx)
228 .map(|result| result.unwrap_or_else(|_| Err(Error::Closed))),
229 HandleState::Completion { future, .. } => Pin::new(future)
230 .poll(cx)
231 .map(|result| result.unwrap_or(Err(Error::Aborted))),
232 }
233 }
234}
235
236#[derive(Clone)]
238pub(crate) struct MetricHandle {
239 gauge: Gauge,
240 finished: Arc<Once>,
241}
242
243impl MetricHandle {
244 pub(crate) fn new(gauge: Gauge) -> Self {
247 gauge.inc();
248
249 Self {
250 gauge,
251 finished: Arc::new(Once::new()),
252 }
253 }
254
255 pub(crate) fn finish(&self) {
260 let gauge = self.gauge.clone();
261 self.finished.call_once(move || {
262 gauge.dec();
263 });
264 }
265}
266
267pub type Panic = Box<dyn Any + Send + 'static>;
269
270#[derive(Clone)]
272pub(crate) struct Panicker {
273 catch: bool,
274 sender: Arc<Mutex<Option<oneshot::Sender<Panic>>>>,
275}
276
277impl Panicker {
278 pub(crate) fn new(catch: bool) -> (Self, Panicked) {
280 let (sender, receiver) = oneshot::channel();
281 let panicker = Self {
282 catch,
283 sender: Arc::new(Mutex::new(Some(sender))),
284 };
285 let panicked = Panicked { receiver };
286 (panicker, panicked)
287 }
288
289 #[commonware_macros::stability(ALPHA)]
291 pub(crate) const fn catch(&self) -> bool {
292 self.catch
293 }
294
295 pub(crate) fn notify(&self, panic: Box<dyn Any + Send + 'static>) {
297 let err = extract_panic_message(&*panic);
299 error!(?err, "task panicked");
300
301 if self.catch {
303 return;
304 }
305
306 let mut sender = self.sender.lock();
308 let Some(sender) = sender.take() else {
309 return;
310 };
311
312 let _ = sender.send(panic);
314 }
315}
316
317pub(crate) struct Panicked {
319 receiver: oneshot::Receiver<Panic>,
320}
321
322impl Panicked {
323 pub(crate) async fn interrupt<Fut>(self, task: Fut) -> Fut::Output
325 where
326 Fut: Future,
327 {
328 let panicked = self.receiver;
330 pin_mut!(panicked);
331 pin_mut!(task);
332 match select(panicked, task).await {
333 Either::Left((panic, task)) => match panic {
334 Ok(panic) => {
336 resume_unwind(panic);
337 }
338 Err(_) => task.await,
341 },
342 Either::Right((output, _)) => {
343 output
345 }
346 }
347 }
348}
349
350pub(crate) struct Aborter {
352 inner: AbortHandle,
353 metric: MetricHandle,
354}
355
356impl Aborter {
357 pub(crate) const fn new(inner: AbortHandle, metric: MetricHandle) -> Self {
359 Self { inner, metric }
360 }
361
362 pub(crate) fn abort(self) {
364 self.inner.abort();
365
366 self.metric.finish();
369 }
370}
371
372#[cfg(test)]
373mod tests {
374 use super::Handle;
375 use crate::{deterministic, Error, Metrics as _, Runner, Spawner, Supervisor as _};
376 use commonware_utils::channel::oneshot;
377 use futures::future;
378
379 const METRIC_PREFIX: &str = "runtime_tasks_running{";
380
381 fn running_tasks_for_label(metrics: &str, label: &str) -> Option<u64> {
382 let label_fragment = format!("name=\"{label}\"");
383 metrics.lines().find_map(|line| {
384 if line.starts_with(METRIC_PREFIX) && line.contains(&label_fragment) {
385 line.rsplit_once(' ')
386 .and_then(|(_, value)| value.trim().parse::<u64>().ok())
387 } else {
388 None
389 }
390 })
391 }
392
393 #[test]
394 fn tasks_running_decreased_after_completion() {
395 const LABEL: &str = "tasks_running_after_completion";
396
397 let runner = deterministic::Runner::default();
398 runner.start(|context| async move {
399 let handle = context.child(LABEL).spawn(|_| async move { "done" });
400
401 let metrics = context.encode();
402 assert_eq!(
403 running_tasks_for_label(&metrics, LABEL),
404 Some(1),
405 "expected tasks_running gauge to be 1 before completion: {metrics}",
406 );
407
408 let output = handle.await.expect("task failed");
409 assert_eq!(output, "done");
410
411 let metrics = context.encode();
412 assert_eq!(
413 running_tasks_for_label(&metrics, LABEL),
414 Some(0),
415 "expected tasks_running gauge to return to 0 after completion: {metrics}",
416 );
417 });
418 }
419
420 #[test]
421 fn tasks_running_unchanged_when_handle_dropped() {
422 const LABEL: &str = "tasks_running_unchanged";
423
424 let runner = deterministic::Runner::default();
425 runner.start(|context| async move {
426 let handle = context.child(LABEL).spawn(|_| async move {
427 future::pending::<()>().await;
428 });
429
430 let metrics = context.encode();
431 assert_eq!(
432 running_tasks_for_label(&metrics, LABEL),
433 Some(1),
434 "expected tasks_running gauge to be 1 before dropping handle: {metrics}",
435 );
436
437 drop(handle);
438
439 let metrics = context.encode();
440 assert_eq!(
441 running_tasks_for_label(&metrics, LABEL),
442 Some(1),
443 "dropping handle should not finish metrics: {metrics}",
444 );
445 });
446 }
447
448 #[test]
449 fn tasks_running_decreased_immediately_on_abort_via_handle() {
450 const LABEL: &str = "tasks_running_abort_via_handle";
451
452 let runner = deterministic::Runner::default();
453 runner.start(|context| async move {
454 let handle = context.child(LABEL).spawn(|_| async move {
455 future::pending::<()>().await;
456 });
457
458 let metrics = context.encode();
459 assert_eq!(
460 running_tasks_for_label(&metrics, LABEL),
461 Some(1),
462 "expected tasks_running gauge to be 1 before abort: {metrics}",
463 );
464
465 handle.abort();
466
467 let metrics = context.encode();
468 assert_eq!(
469 running_tasks_for_label(&metrics, LABEL),
470 Some(0),
471 "expected tasks_running gauge to return to 0 after abort: {metrics}",
472 );
473 });
474 }
475
476 #[test]
477 fn completion_handle_abort_stops_waiting() {
478 deterministic::Runner::default().start(|_| async move {
479 let (sender, receiver) = oneshot::channel();
480 let handle = Handle::from_receiver(receiver);
481
482 handle.abort();
483
484 assert!(sender.send(Ok(())).is_ok());
485 assert!(matches!(handle.await, Err(Error::Aborted)));
486 });
487 }
488
489 #[test]
490 fn tasks_running_decreased_after_blocking_completion() {
491 const LABEL: &str = "tasks_running_after_blocking_completion";
492
493 let runner = deterministic::Runner::default();
494 runner.start(|context| async move {
495 let blocking_handle = context.child(LABEL).shared(true).spawn(|_| async move {
496 42
498 });
499
500 let metrics = context.encode();
501 assert_eq!(
502 running_tasks_for_label(&metrics, LABEL),
503 Some(1),
504 "expected tasks_running gauge to be 1 while blocking task runs: {metrics}",
505 );
506
507 let result = blocking_handle.await.expect("blocking task failed");
508 assert_eq!(result, 42);
509
510 let metrics = context.encode();
511 assert_eq!(
512 running_tasks_for_label(&metrics, LABEL),
513 Some(0),
514 "expected tasks_running gauge to return to 0 after blocking task completes: {metrics}",
515 );
516 });
517 }
518
519 #[test]
520 fn tasks_running_decreased_immediately_on_abort_via_aborter() {
521 const LABEL: &str = "tasks_running_abort_via_aborter";
522
523 let runner = deterministic::Runner::default();
524 runner.start(|context| async move {
525 let handle = context.child(LABEL).spawn(|_| async move {
526 future::pending::<()>().await;
527 });
528
529 let metrics = context.encode();
530 assert_eq!(
531 running_tasks_for_label(&metrics, LABEL),
532 Some(1),
533 "expected tasks_running gauge to be 1 before abort: {metrics}",
534 );
535
536 let aborter = handle.aborter().unwrap();
537 aborter.abort();
538
539 let metrics = context.encode();
540 assert_eq!(
541 running_tasks_for_label(&metrics, LABEL),
542 Some(0),
543 "expected tasks_running gauge to return to 0 after abort: {metrics}",
544 );
545 });
546 }
547}