Skip to main content

ntex_rt/
task.rs

1use std::{future::Future, sync::Arc, sync::atomic::AtomicUsize, sync::atomic::Ordering};
2
3// The Callbacks static holds a pointer to the global callbacks. It is protected by
4// the STATE static which determines whether `CBS` has been initialized yet.
5static mut CBS: Option<Arc<dyn CallbacksApi>> = None;
6
7static STATE: AtomicUsize = AtomicUsize::new(0);
8
9// There are three different states that we care about: the callback's
10// uninitialized, the callback's initializing (set_cbs's been called but
11// CBS hasn't actually been set yet), or the callbacks's active.
12const UNINITIALIZED: usize = 0;
13const INITIALIZING: usize = 1;
14const INITIALIZED: usize = 2;
15
16trait CallbacksApi {
17    fn before(&self) -> Option<*const ()>;
18    fn enter(&self, _: *const ()) -> *const ();
19    fn exit(&self, _: *const ());
20    fn after(&self, _: *const ());
21}
22
23#[allow(clippy::struct_field_names)]
24struct Callbacks<A, B, C, D> {
25    f_before: A,
26    f_enter: B,
27    f_exit: C,
28    f_after: D,
29}
30
31impl<A, B, C, D> CallbacksApi for Callbacks<A, B, C, D>
32where
33    A: Fn() -> Option<*const ()> + 'static,
34    B: Fn(*const ()) -> *const () + 'static,
35    C: Fn(*const ()) + 'static,
36    D: Fn(*const ()) + 'static,
37{
38    fn before(&self) -> Option<*const ()> {
39        (self.f_before)()
40    }
41    fn enter(&self, d: *const ()) -> *const () {
42        (self.f_enter)(d)
43    }
44    fn exit(&self, d: *const ()) {
45        (self.f_exit)(d);
46    }
47    fn after(&self, d: *const ()) {
48        (self.f_after)(d);
49    }
50}
51
52pub(crate) struct Data {
53    cb: &'static dyn CallbacksApi,
54    ptr: *const (),
55}
56
57impl Data {
58    #[allow(clippy::if_not_else)]
59    pub(crate) fn load() -> Option<Data> {
60        // `Acquire` pairs with the `Release` store in `set_cbs`, so `CBS` is visible
61        let cb = if STATE.load(Ordering::Acquire) != INITIALIZED {
62            None
63        } else {
64            #[allow(static_mut_refs)]
65            unsafe {
66                Some(CBS.as_ref().map(AsRef::as_ref).unwrap())
67            }
68        };
69
70        if let Some(cb) = cb
71            && let Some(ptr) = cb.before()
72        {
73            return Some(Data { cb, ptr });
74        }
75        None
76    }
77
78    pub(crate) fn run<F, R>(&mut self, f: F) -> R
79    where
80        F: FnOnce() -> R,
81    {
82        struct Exit(&'static dyn CallbacksApi, *const ());
83
84        impl Drop for Exit {
85            fn drop(&mut self) {
86                self.0.exit(self.1);
87            }
88        }
89
90        // `exit` also runs if `f` panics, so the context set by `enter` is restored
91        let _exit = Exit(self.cb, self.cb.enter(self.ptr));
92        f()
93    }
94}
95
96/// Wraps a future so that task callbacks, if registered, run around each poll.
97///
98/// Always returns the same future type, so spawning code is generated once
99/// per future rather than once per callback mode.
100pub(crate) fn wrap<F: Future>(fut: F) -> impl Future<Output = F::Output> {
101    let mut data = Data::load();
102    async move {
103        let mut f = std::pin::pin!(fut);
104        std::future::poll_fn(|cx| match data.as_mut() {
105            Some(data) => data.run(|| f.as_mut().poll(cx)),
106            None => f.as_mut().poll(cx),
107        })
108        .await
109    }
110}
111
112impl Drop for Data {
113    fn drop(&mut self) {
114        self.cb.after(self.ptr);
115    }
116}
117
118/// Spawns a local future and returns a `Send` future for its result.
119///
120/// The task is spawned immediately on the current runtime, using
121/// [`crate::spawn_detached`] and its registered task callbacks. The spawned
122/// future need not be `Send`, but its output must be. Only the returned future
123/// may be moved to another thread; the task stays on its original runtime,
124/// which must keep being driven.
125///
126/// Dropping the returned future requests cancellation, even before its first
127/// poll. The task's future is dropped on its runtime thread when the runtime
128/// next processes the task, or during shutdown. Cancellation does not wait for
129/// cleanup or interrupt an active poll.
130///
131/// Awaiting the returned future yields the output or [`crate::JoinError`] if
132/// the task ends without one, for example on panic or runtime shutdown. Task
133/// panics follow the runtime's usual behavior.
134///
135/// # Panics
136///
137/// Panics outside a runtime context that supports [`crate::spawn_detached`].
138#[track_caller]
139#[must_use = "dropping the returned future cancels the task"]
140pub fn spawn_sendable<F>(
141    fut: F,
142) -> impl Future<Output = Result<F::Output, crate::JoinError>> + Send + 'static
143where
144    F: Future + 'static,
145    F::Output: Send,
146{
147    let (tx, rx) = oneshot::async_channel();
148    let (cancel, canceled) = oneshot::async_channel::<()>();
149    crate::spawn_detached(async move {
150        let output = {
151            let mut fut = std::pin::pin!(fut);
152            let mut canceled = std::pin::pin!(canceled);
153            std::future::poll_fn(|cx| {
154                if canceled.as_mut().poll(cx).is_ready() {
155                    std::task::Poll::Ready(None)
156                } else {
157                    fut.as_mut().poll(cx).map(Some)
158                }
159            })
160            .await
161        };
162        if let Some(output) = output {
163            let _ = tx.send(output);
164        }
165    });
166    async move {
167        let _cancel = cancel;
168        rx.await.map_err(|_| crate::JoinError)
169    }
170}
171
172/// Installs process-wide callbacks for instrumenting spawned tasks.
173///
174/// Callbacks apply when [`crate::spawn`], [`crate::spawn_detached`],
175/// [`spawn_sendable`], [`crate::rt::spawn`], or [`crate::rt::spawn_detached`] wraps a future, not
176/// retroactively to existing tasks. Direct [`crate::rt::Runtime`] spawning
177/// methods do not apply them.
178///
179/// - `f_before` runs synchronously when the future is wrapped. Returning `None`
180///   disables the remaining callbacks for that future.
181/// - `f_enter` receives the task's pointer before each poll and returns a pointer
182///   representing the previous context.
183/// - `f_exit` receives that previous-context pointer after the poll, including
184///   when polling unwinds.
185/// - `f_after` receives the task's original pointer when its callback state is
186///   dropped, including cancellation before the first poll.
187///
188/// Registration is permanent and first-wins. Does nothing if callbacks were
189/// already installed; use [`task_opt_callbacks`] to observe registration success.
190///
191/// # Safety
192///
193/// Pointers are opaque to the runtime. The caller must keep the state returned
194/// by `f_before` valid until `f_after`, and the state returned by `f_enter` valid
195/// until its matching `f_exit`. Callbacks must safely handle concurrent use by
196/// different runtime threads and any nested task activity they trigger.
197pub unsafe fn task_callbacks<FBefore, FEnter, FExit, FAfter>(
198    f_before: FBefore,
199    f_enter: FEnter,
200    f_exit: FExit,
201    f_after: FAfter,
202) where
203    FBefore: Fn() -> Option<*const ()> + 'static + Sync,
204    FEnter: Fn(*const ()) -> *const () + 'static + Sync,
205    FExit: Fn(*const ()) + 'static + Sync,
206    FAfter: Fn(*const ()) + 'static + Sync,
207{
208    let new = Arc::new(Callbacks {
209        f_before,
210        f_enter,
211        f_exit,
212        f_after,
213    });
214    let _ = set_cbs(new);
215}
216
217/// Attempts to install process-wide task callbacks.
218///
219/// Uses the callback lifecycle described by [`task_callbacks`]. Returns `true`
220/// if this call installs the callbacks, or `false` if another registration has
221/// already won. Installed callbacks cannot be replaced or removed.
222///
223/// # Safety
224///
225/// The pointer lifetimes and callback thread-safety requirements of
226/// [`task_callbacks`] apply.
227pub unsafe fn task_opt_callbacks<FBefore, FEnter, FExit, FAfter>(
228    f_before: FBefore,
229    f_enter: FEnter,
230    f_exit: FExit,
231    f_after: FAfter,
232) -> bool
233where
234    FBefore: Fn() -> Option<*const ()> + Sync + 'static,
235    FEnter: Fn(*const ()) -> *const () + Sync + 'static,
236    FExit: Fn(*const ()) + Sync + 'static,
237    FAfter: Fn(*const ()) + Sync + 'static,
238{
239    let new = Arc::new(Callbacks {
240        f_before,
241        f_enter,
242        f_exit,
243        f_after,
244    });
245    set_cbs(new).is_ok()
246}
247
248fn set_cbs(cbs: Arc<dyn CallbacksApi>) -> Result<(), ()> {
249    match STATE.compare_exchange(
250        UNINITIALIZED,
251        INITIALIZING,
252        Ordering::Acquire,
253        Ordering::Relaxed,
254    ) {
255        Ok(UNINITIALIZED) => {
256            unsafe {
257                CBS = Some(cbs);
258            }
259            STATE.store(INITIALIZED, Ordering::Release);
260            Ok(())
261        }
262        Err(INITIALIZING) => {
263            while STATE.load(Ordering::Relaxed) == INITIALIZING {
264                std::hint::spin_loop();
265            }
266            Err(())
267        }
268        _ => Err(()),
269    }
270}
271
272#[cfg(test)]
273#[path = "../tests/unit/task.rs"]
274mod tests;