Skip to main content

apple_log/
async_api.rs

1//! Executor-agnostic async instrumentation helpers for [`OSActivity`](crate::OSActivity).
2//!
3//! Enable this module with the `async` Cargo feature:
4//!
5//! ```toml
6//! [dependencies]
7//! apple-log = { version = "0.6", features = ["async"] }
8//! ```
9//!
10//! [`ActivityFuture`] wraps any `Future` and enters an `OSActivity` around each
11//! `poll`. This keeps activity scoping aligned with executor wakeups without
12//! holding a scope guard across suspension points.
13//!
14//! # Example
15//!
16//! ```rust,no_run
17//! use apple_log::{OSActivity, OSActivityFlags};
18//!
19//! # async fn run() -> Result<(), Box<dyn std::error::Error>> {
20//! let bytes = OSActivity::new(
21//!     "download asset",
22//!     Some(&OSActivity::current()),
23//!     OSActivityFlags::DEFAULT,
24//! )?
25//! .instrument_future(async { 42_usize })
26//! .await?;
27//!
28//! assert_eq!(bytes, 42);
29//! # Ok(())
30//! # }
31//! ```
32
33#![cfg(feature = "async")]
34#![allow(clippy::module_name_repetitions)]
35
36use std::future::Future;
37use std::pin::Pin;
38use std::task::{Context, Poll};
39
40use crate::{LogError, OSActivity};
41
42/// Future wrapper that re-enters an [`OSActivity`] every time the executor polls it.
43#[must_use = "futures do nothing unless awaited or polled"]
44pub struct ActivityFuture<F> {
45    activity: OSActivity,
46    future: F,
47}
48
49impl<F> ActivityFuture<F> {
50    /// Wrap `future` so each poll executes inside `activity`.
51    pub const fn new(activity: OSActivity, future: F) -> Self {
52        Self { activity, future }
53    }
54
55    /// Borrow the activity that will be entered around each poll.
56    #[must_use]
57    pub const fn activity(&self) -> &OSActivity {
58        &self.activity
59    }
60}
61
62impl<F> ActivityFuture<F>
63where
64    F: Future,
65{
66    /// Unwrap the wrapper and return the owned activity plus inner future.
67    #[must_use]
68    pub fn into_parts(self) -> (OSActivity, F) {
69        (self.activity, self.future)
70    }
71}
72
73impl<F> Future for ActivityFuture<F>
74where
75    F: Future,
76{
77    type Output = Result<F::Output, LogError>;
78
79    fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
80        // SAFETY: projecting `future` is sound because the outer `ActivityFuture`
81        // is pinned for the duration of this call and we never move `future`
82        // after obtaining the pinned mutable reference.
83        let this = unsafe { self.get_unchecked_mut() };
84        let _scope = match this.activity.enter() {
85            Ok(scope) => scope,
86            Err(error) => return Poll::Ready(Err(error)),
87        };
88
89        // SAFETY: see comment above for the pinned projection rationale.
90        unsafe { Pin::new_unchecked(&mut this.future) }
91            .poll(cx)
92            .map(Ok)
93    }
94}
95
96/// Wrap `future` so each poll executes inside `activity`.
97pub const fn instrument_future<F>(activity: OSActivity, future: F) -> ActivityFuture<F> {
98    ActivityFuture::new(activity, future)
99}