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
112#[cfg(any(feature = "tokio", feature = "compio"))]
113pub(crate) use remote::RemoteJoin;
114
115#[cfg(any(feature = "tokio", feature = "compio"))]
116mod remote {
117 use std::sync::atomic::{AtomicBool, Ordering};
118 use std::task::{Context, Poll};
119 use std::{future::Future, future::poll_fn, pin::Pin, pin::pin, sync::Arc};
120
121 use async_channel::Sender;
122 use atomic_waker::AtomicWaker;
123
124 use crate::arbiter::ArbiterCommand;
125
126 #[derive(Debug, Default)]
127 struct Cancel {
128 cancelled: AtomicBool,
129 waker: AtomicWaker,
130 }
131
132 #[derive(Debug)]
135 pub(crate) struct RemoteJoin<T> {
136 rx: oneshot::AsyncReceiver<T>,
137 cancel: Arc<Cancel>,
138 }
139
140 impl<T: Send + 'static> RemoteJoin<T> {
141 pub(crate) fn spawn<F>(sender: &Sender<ArbiterCommand>, future: F) -> Self
142 where
143 F: Future<Output = T> + Send + 'static,
144 {
145 let (tx, rx) = oneshot::async_channel();
146 let cancel = Arc::new(Cancel::default());
147 let state = cancel.clone();
148 let _ = sender.try_send(ArbiterCommand::Execute(Box::pin(async move {
149 let mut fut = pin!(future);
150 let res = poll_fn(|cx| {
151 state.waker.register(cx.waker());
152 if state.cancelled.load(Ordering::Acquire) {
153 Poll::Ready(None)
154 } else {
155 fut.as_mut().poll(cx).map(Some)
156 }
157 })
158 .await;
159 if let Some(res) = res {
160 let _ = tx.send(res);
161 }
162 })));
163 Self { rx, cancel }
164 }
165 }
166
167 impl<T> RemoteJoin<T> {
168 pub(crate) fn cancel(self) {
170 self.cancel.cancelled.store(true, Ordering::Release);
171 self.cancel.waker.wake();
172 }
173
174 pub(crate) fn is_finished(&self) -> bool {
175 self.rx.is_closed()
176 }
177 }
178
179 impl<T> Future for RemoteJoin<T> {
180 type Output = Result<T, oneshot::RecvError>;
181
182 fn poll(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
183 Pin::new(&mut self.rx).poll(cx)
184 }
185 }
186}
187
188impl Drop for Data {
189 fn drop(&mut self) {
190 self.cb.after(self.ptr);
191 }
192}
193
194pub unsafe fn task_callbacks<FBefore, FEnter, FExit, FAfter>(
204 f_before: FBefore,
205 f_enter: FEnter,
206 f_exit: FExit,
207 f_after: FAfter,
208) where
209 FBefore: Fn() -> Option<*const ()> + 'static + Sync,
210 FEnter: Fn(*const ()) -> *const () + 'static + Sync,
211 FExit: Fn(*const ()) + 'static + Sync,
212 FAfter: Fn(*const ()) + 'static + Sync,
213{
214 let new = Arc::new(Callbacks {
215 f_before,
216 f_enter,
217 f_exit,
218 f_after,
219 });
220 let _ = set_cbs(new);
221}
222
223pub unsafe fn task_opt_callbacks<FBefore, FEnter, FExit, FAfter>(
232 f_before: FBefore,
233 f_enter: FEnter,
234 f_exit: FExit,
235 f_after: FAfter,
236) -> bool
237where
238 FBefore: Fn() -> Option<*const ()> + Sync + 'static,
239 FEnter: Fn(*const ()) -> *const () + Sync + 'static,
240 FExit: Fn(*const ()) + Sync + 'static,
241 FAfter: Fn(*const ()) + Sync + 'static,
242{
243 let new = Arc::new(Callbacks {
244 f_before,
245 f_enter,
246 f_exit,
247 f_after,
248 });
249 set_cbs(new).is_ok()
250}
251
252fn set_cbs(cbs: Arc<dyn CallbacksApi>) -> Result<(), ()> {
253 match STATE.compare_exchange(
254 UNINITIALIZED,
255 INITIALIZING,
256 Ordering::Acquire,
257 Ordering::Relaxed,
258 ) {
259 Ok(UNINITIALIZED) => {
260 unsafe {
261 CBS = Some(cbs);
262 }
263 STATE.store(INITIALIZED, Ordering::Release);
264 Ok(())
265 }
266 Err(INITIALIZING) => {
267 while STATE.load(Ordering::Relaxed) == INITIALIZING {
268 std::hint::spin_loop();
269 }
270 Err(())
271 }
272 _ => Err(()),
273 }
274}
275
276#[cfg(test)]
277mod tests {
278 use super::*;
279
280 #[test]
281 fn load_callbacks_set_by_other_thread() {
282 let hnd = std::thread::spawn(|| unsafe {
283 task_opt_callbacks(|| Some(std::ptr::null()), |p| p, |_| {}, |_| {})
284 });
285 while Data::load().is_none() {
287 std::hint::spin_loop();
288 }
289 assert!(hnd.join().unwrap());
290 }
291
292 #[test]
293 fn exit_runs_when_poll_panics() {
294 use std::{cell::Cell, panic, pin::pin, task::Context, task::Waker};
295
296 const CHILD: &str = "NTEX_RT_TASK_EXIT_CHILD";
298 if std::env::var_os(CHILD).is_none() {
299 let out = std::process::Command::new(std::env::current_exe().unwrap())
300 .args(["--exact", "task::tests::exit_runs_when_poll_panics"])
301 .env(CHILD, "1")
302 .output()
303 .unwrap();
304 assert!(
305 out.status.success(),
306 "{}",
307 String::from_utf8_lossy(&out.stdout)
308 );
309 return;
310 }
311
312 thread_local! {
313 static DEPTH: Cell<isize> = const { Cell::new(0) };
314 static AFTER: Cell<usize> = const { Cell::new(0) };
315 }
316 assert!(unsafe {
317 task_opt_callbacks(
318 || Some(std::ptr::null()),
319 |p| {
320 DEPTH.set(DEPTH.get() + 1);
321 p
322 },
323 |_| DEPTH.set(DEPTH.get() - 1),
324 |_| AFTER.set(AFTER.get() + 1),
325 )
326 });
327
328 let mut fut = Box::pin(wrap(async { panic!("poll panic") }));
329 let res = panic::catch_unwind(panic::AssertUnwindSafe(|| {
330 let _ = fut.as_mut().poll(&mut Context::from_waker(Waker::noop()));
331 }));
332 assert!(res.is_err());
333 assert_eq!(DEPTH.get(), 0);
334 drop(fut);
335 assert_eq!(AFTER.get(), 1);
336
337 let mut fut = pin!(wrap(async { 1 }));
338 let res = fut.as_mut().poll(&mut Context::from_waker(Waker::noop()));
339 assert_eq!(res, std::task::Poll::Ready(1));
340 assert_eq!(DEPTH.get(), 0);
341 }
342
343 #[test]
344 fn callbacks_set_once_and_wrap_spawned_tasks() {
345 use std::{cell::Cell, sync::atomic::AtomicUsize};
346
347 use crate::{System, testing::TestRunner};
348
349 const CHILD: &str = "NTEX_RT_TASK_CALLBACKS_CHILD";
350 static DATA: u8 = 0;
351 static CTX: u8 = 0;
352 static BEFORE: AtomicUsize = AtomicUsize::new(0);
353 static AFTER: AtomicUsize = AtomicUsize::new(0);
354 thread_local! {
355 static TRACK: Cell<bool> = const { Cell::new(false) };
356 static INSIDE: Cell<bool> = const { Cell::new(false) };
357 }
358 fn data() -> *const () {
359 (&raw const DATA).cast()
360 }
361 fn ctx() -> *const () {
362 (&raw const CTX).cast()
363 }
364
365 if std::env::var_os(CHILD).is_none() {
367 let out = std::process::Command::new(std::env::current_exe().unwrap())
368 .args([
369 "--exact",
370 "task::tests::callbacks_set_once_and_wrap_spawned_tasks",
371 ])
372 .env(CHILD, "1")
373 .output()
374 .unwrap();
375 assert!(
376 out.status.success(),
377 "{}",
378 String::from_utf8_lossy(&out.stdout)
379 );
380 return;
381 }
382
383 unsafe {
384 task_callbacks(
385 || {
386 TRACK.get().then(|| {
387 BEFORE.fetch_add(1, Ordering::Relaxed);
388 data()
389 })
390 },
391 |p| {
392 assert_eq!(p, data());
393 INSIDE.set(true);
394 ctx()
395 },
396 |p| {
397 assert_eq!(p, ctx());
399 INSIDE.set(false);
400 },
401 |p| {
402 assert_eq!(p, data());
403 AFTER.fetch_add(1, Ordering::Relaxed);
404 },
405 );
406 }
407 unsafe {
409 task_callbacks(
410 || unreachable!(),
411 |_| unreachable!(),
412 |_| unreachable!(),
413 |_| unreachable!(),
414 );
415 assert!(!task_opt_callbacks(
416 || unreachable!(),
417 |_| unreachable!(),
418 |_| unreachable!(),
419 |_| unreachable!(),
420 ));
421 }
422
423 System::new("callbacks", TestRunner).block_on(async {
424 TRACK.set(true);
425 let hnd = crate::spawn(async { INSIDE.get() });
426 TRACK.set(false);
427 assert_eq!(BEFORE.load(Ordering::Relaxed), 1);
428 assert!(hnd.await.unwrap());
430 assert!(!INSIDE.get());
431 assert_eq!(AFTER.load(Ordering::Relaxed), 1);
432
433 assert!(!crate::spawn(async { INSIDE.get() }).await.unwrap());
435 assert_eq!(BEFORE.load(Ordering::Relaxed), 1);
436 assert_eq!(AFTER.load(Ordering::Relaxed), 1);
437 });
438 }
439}