Skip to main content

async_rt/
tracker.rs

1use crate::{Executor, ExecutorBlockOn, ExecutorBlocking, ExecutorTimeout, JoinHandle};
2use std::fmt::Debug;
3use std::future::Future;
4use std::pin::Pin;
5use std::sync::Arc;
6use std::sync::atomic::AtomicUsize;
7use std::task::{Context, Poll};
8
9/// Track running tasks.
10///
11/// Note that there is no guarantee that the runtime would drop the future after it is done.
12/// Therefore, this should only be used for purely approx statistics and not actual numbers.
13/// Additionally, it does not track any tasks spawned directly by the runtime but only by
14/// [`Executor::spawn`] through this implementation against [`Executor`].
15pub struct TrackerExecutor<E> {
16    executor: E,
17    counter: Arc<AtomicUsize>,
18}
19
20impl<E> Debug for TrackerExecutor<E> {
21    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
22        f.debug_struct("TrackerExecutor").finish()
23    }
24}
25
26impl<E: Executor> TrackerExecutor<E> {
27    pub fn new(executor: E) -> Self {
28        Self {
29            executor,
30            counter: Arc::default(),
31        }
32    }
33
34    /// Number of active tasks.
35    pub fn count(&self) -> usize {
36        self.counter.load(std::sync::atomic::Ordering::Relaxed)
37    }
38}
39
40struct FutureCounter<F> {
41    future: F,
42    counter: Arc<AtomicUsize>,
43}
44
45impl<F> FutureCounter<F> {
46    pub fn new(future: F, counter: Arc<AtomicUsize>) -> Self {
47        counter.fetch_add(1, std::sync::atomic::Ordering::Relaxed);
48        Self { future, counter }
49    }
50}
51
52impl<F> Future for FutureCounter<F>
53where
54    F: Future + 'static + Unpin,
55{
56    type Output = F::Output;
57    fn poll(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
58        Pin::new(&mut self.future).poll(cx)
59    }
60}
61
62impl<F> Drop for FutureCounter<F> {
63    fn drop(&mut self) {
64        self.counter
65            .fetch_sub(1, std::sync::atomic::Ordering::Relaxed);
66    }
67}
68
69impl<E: Executor> Executor for TrackerExecutor<E> {
70    fn runtime_type(&self) -> Option<&'static str> {
71        self.executor.runtime_type()
72    }
73
74    fn spawn<F>(&self, future: F) -> JoinHandle<F::Output>
75    where
76        F: Future + Send + 'static,
77        F::Output: Send + 'static,
78    {
79        let counter = self.counter.clone();
80        let future = Box::pin(future);
81        let future = FutureCounter::new(future, counter);
82        self.executor.spawn(future)
83    }
84}
85
86impl<E: ExecutorBlocking> ExecutorBlocking for TrackerExecutor<E> {
87    fn spawn_blocking<F, R>(&self, f: F) -> JoinHandle<R>
88    where
89        F: FnOnce() -> R + Send + 'static,
90        R: Send + 'static,
91    {
92        struct AtomicCounterDrop(Arc<AtomicUsize>);
93
94        impl AtomicCounterDrop {
95            pub fn new(counter: Arc<AtomicUsize>) -> Self {
96                counter.fetch_add(1, std::sync::atomic::Ordering::Relaxed);
97                Self(counter)
98            }
99        }
100
101        impl Drop for AtomicCounterDrop {
102            fn drop(&mut self) {
103                self.0.fetch_sub(1, std::sync::atomic::Ordering::Relaxed);
104            }
105        }
106
107        let counter = AtomicCounterDrop::new(self.counter.clone());
108
109        self.executor.spawn_blocking(move || {
110            let _counter = counter;
111            f()
112        })
113    }
114}
115
116impl<E: ExecutorTimeout> ExecutorTimeout for TrackerExecutor<E> {}
117
118impl<E: ExecutorBlockOn> ExecutorBlockOn for TrackerExecutor<E> {
119    fn block_on<F: Future>(&self, future: F) -> F::Output {
120        self.executor.block_on(future)
121    }
122}
123
124#[cfg(test)]
125mod tests {
126
127    #[cfg(feature = "threadpool")]
128    #[test]
129    fn test_tracker_threadpool_executor() {
130        use super::TrackerExecutor;
131        use crate::Executor;
132        use crate::rt::threadpool::ThreadPoolExecutor;
133        futures::executor::block_on(async {
134            let executor = TrackerExecutor::new(ThreadPoolExecutor);
135            let handle = executor.spawn(futures::future::pending::<()>());
136            assert_eq!(executor.count(), 1);
137            handle.abort();
138            let _ = handle.await;
139            assert_eq!(executor.count(), 0);
140        });
141    }
142
143    #[cfg(feature = "tokio")]
144    #[tokio::test]
145    async fn test_tracker_tokio_executor() {
146        use super::TrackerExecutor;
147        use crate::Executor;
148        use crate::rt::tokio::TokioRuntimeExecutor;
149
150        let executor = TrackerExecutor::new(TokioRuntimeExecutor::from_current_handle().unwrap());
151        let handle = executor.spawn(futures::future::pending::<()>());
152        assert_eq!(executor.count(), 1);
153        handle.abort();
154        let _ = handle.await;
155        assert_eq!(executor.count(), 0);
156    }
157}