1use std::{future::Future, sync::Arc, sync::atomic::AtomicUsize, sync::atomic::Ordering};
2
3static mut CBS: Option<Arc<dyn CallbacksApi>> = None;
6
7static STATE: AtomicUsize = AtomicUsize::new(0);
8
9const 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 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 let _exit = Exit(self.cb, self.cb.enter(self.ptr));
92 f()
93 }
94}
95
96pub(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
118pub unsafe fn task_callbacks<FBefore, FEnter, FExit, FAfter>(
128 f_before: FBefore,
129 f_enter: FEnter,
130 f_exit: FExit,
131 f_after: FAfter,
132) where
133 FBefore: Fn() -> Option<*const ()> + 'static + Sync,
134 FEnter: Fn(*const ()) -> *const () + 'static + Sync,
135 FExit: Fn(*const ()) + 'static + Sync,
136 FAfter: Fn(*const ()) + 'static + Sync,
137{
138 let new = Arc::new(Callbacks {
139 f_before,
140 f_enter,
141 f_exit,
142 f_after,
143 });
144 let _ = set_cbs(new);
145}
146
147pub unsafe fn task_opt_callbacks<FBefore, FEnter, FExit, FAfter>(
156 f_before: FBefore,
157 f_enter: FEnter,
158 f_exit: FExit,
159 f_after: FAfter,
160) -> bool
161where
162 FBefore: Fn() -> Option<*const ()> + Sync + 'static,
163 FEnter: Fn(*const ()) -> *const () + Sync + 'static,
164 FExit: Fn(*const ()) + Sync + 'static,
165 FAfter: Fn(*const ()) + Sync + 'static,
166{
167 let new = Arc::new(Callbacks {
168 f_before,
169 f_enter,
170 f_exit,
171 f_after,
172 });
173 set_cbs(new).is_ok()
174}
175
176fn set_cbs(cbs: Arc<dyn CallbacksApi>) -> Result<(), ()> {
177 match STATE.compare_exchange(
178 UNINITIALIZED,
179 INITIALIZING,
180 Ordering::Acquire,
181 Ordering::Relaxed,
182 ) {
183 Ok(UNINITIALIZED) => {
184 unsafe {
185 CBS = Some(cbs);
186 }
187 STATE.store(INITIALIZED, Ordering::Release);
188 Ok(())
189 }
190 Err(INITIALIZING) => {
191 while STATE.load(Ordering::Relaxed) == INITIALIZING {
192 std::hint::spin_loop();
193 }
194 Err(())
195 }
196 _ => Err(()),
197 }
198}
199
200#[cfg(test)]
201mod tests {
202 use super::*;
203
204 #[test]
205 fn load_callbacks_set_by_other_thread() {
206 let hnd = std::thread::spawn(|| unsafe {
207 task_opt_callbacks(|| Some(std::ptr::null()), |p| p, |_| {}, |_| {})
208 });
209 while Data::load().is_none() {
211 std::hint::spin_loop();
212 }
213 assert!(hnd.join().unwrap());
214 }
215
216 #[test]
217 fn exit_runs_when_poll_panics() {
218 use std::{cell::Cell, panic, pin::pin, task::Context, task::Waker};
219
220 const CHILD: &str = "NTEX_RT_TASK_EXIT_CHILD";
222 if std::env::var_os(CHILD).is_none() {
223 let out = std::process::Command::new(std::env::current_exe().unwrap())
224 .args(["--exact", "task::tests::exit_runs_when_poll_panics"])
225 .env(CHILD, "1")
226 .output()
227 .unwrap();
228 assert!(
229 out.status.success(),
230 "{}",
231 String::from_utf8_lossy(&out.stdout)
232 );
233 return;
234 }
235
236 thread_local! {
237 static DEPTH: Cell<isize> = const { Cell::new(0) };
238 static AFTER: Cell<usize> = const { Cell::new(0) };
239 }
240 assert!(unsafe {
241 task_opt_callbacks(
242 || Some(std::ptr::null()),
243 |p| {
244 DEPTH.set(DEPTH.get() + 1);
245 p
246 },
247 |_| DEPTH.set(DEPTH.get() - 1),
248 |_| AFTER.set(AFTER.get() + 1),
249 )
250 });
251
252 let mut fut = Box::pin(wrap(async { panic!("poll panic") }));
253 let res = panic::catch_unwind(panic::AssertUnwindSafe(|| {
254 let _ = fut.as_mut().poll(&mut Context::from_waker(Waker::noop()));
255 }));
256 assert!(res.is_err());
257 assert_eq!(DEPTH.get(), 0);
258 drop(fut);
259 assert_eq!(AFTER.get(), 1);
260
261 let mut fut = pin!(wrap(async { 1 }));
262 let res = fut.as_mut().poll(&mut Context::from_waker(Waker::noop()));
263 assert_eq!(res, std::task::Poll::Ready(1));
264 assert_eq!(DEPTH.get(), 0);
265 }
266
267 #[test]
268 fn callbacks_set_once_and_wrap_spawned_tasks() {
269 use std::{cell::Cell, sync::atomic::AtomicUsize};
270
271 use crate::{System, testing::TestRunner};
272
273 const CHILD: &str = "NTEX_RT_TASK_CALLBACKS_CHILD";
274 static DATA: u8 = 0;
275 static CTX: u8 = 0;
276 static BEFORE: AtomicUsize = AtomicUsize::new(0);
277 static AFTER: AtomicUsize = AtomicUsize::new(0);
278 thread_local! {
279 static TRACK: Cell<bool> = const { Cell::new(false) };
280 static INSIDE: Cell<bool> = const { Cell::new(false) };
281 }
282 fn data() -> *const () {
283 (&raw const DATA).cast()
284 }
285 fn ctx() -> *const () {
286 (&raw const CTX).cast()
287 }
288
289 if std::env::var_os(CHILD).is_none() {
291 let out = std::process::Command::new(std::env::current_exe().unwrap())
292 .args([
293 "--exact",
294 "task::tests::callbacks_set_once_and_wrap_spawned_tasks",
295 ])
296 .env(CHILD, "1")
297 .output()
298 .unwrap();
299 assert!(
300 out.status.success(),
301 "{}",
302 String::from_utf8_lossy(&out.stdout)
303 );
304 return;
305 }
306
307 unsafe {
308 task_callbacks(
309 || {
310 TRACK.get().then(|| {
311 BEFORE.fetch_add(1, Ordering::Relaxed);
312 data()
313 })
314 },
315 |p| {
316 assert_eq!(p, data());
317 INSIDE.set(true);
318 ctx()
319 },
320 |p| {
321 assert_eq!(p, ctx());
323 INSIDE.set(false);
324 },
325 |p| {
326 assert_eq!(p, data());
327 AFTER.fetch_add(1, Ordering::Relaxed);
328 },
329 );
330 }
331 unsafe {
333 task_callbacks(
334 || unreachable!(),
335 |_| unreachable!(),
336 |_| unreachable!(),
337 |_| unreachable!(),
338 );
339 assert!(!task_opt_callbacks(
340 || unreachable!(),
341 |_| unreachable!(),
342 |_| unreachable!(),
343 |_| unreachable!(),
344 ));
345 }
346
347 System::new("callbacks", TestRunner).block_on(async {
348 TRACK.set(true);
349 let hnd = crate::spawn(async { INSIDE.get() });
350 TRACK.set(false);
351 assert_eq!(BEFORE.load(Ordering::Relaxed), 1);
352 assert!(hnd.await.unwrap());
354 assert!(!INSIDE.get());
355 assert_eq!(AFTER.load(Ordering::Relaxed), 1);
356
357 assert!(!crate::spawn(async { INSIDE.get() }).await.unwrap());
359 assert_eq!(BEFORE.load(Ordering::Relaxed), 1);
360 assert_eq!(AFTER.load(Ordering::Relaxed), 1);
361 });
362 }
363}