1use std::pin::Pin;
4use std::sync::atomic::{AtomicUsize, Ordering};
5use std::task::{Context, Poll};
6use std::time::Duration;
7
8use futures::Future;
9use pin_project_lite::pin_project;
10use tracing::{info, trace};
11
12pub static PENDING_HANDLES: AtomicUsize = AtomicUsize::new(0);
14
15#[cfg(all(unix, debug_assertions))]
16static BACKTRACES: parking_lot::Mutex<Vec<Option<std::backtrace::Backtrace>>> =
17 parking_lot::Mutex::new(Vec::new());
18
19#[track_caller]
21pub fn spawn_counted<F>(fut: F) -> tokio::task::JoinHandle<F::Output>
22where
23 F: Future + Send + 'static,
24 F::Output: Send,
25{
26 tokio::spawn(CountedFut::new(fut, &PENDING_HANDLES))
27}
28
29#[track_caller]
32pub fn spawn_counted_w_handle<F>(
33 fut: F,
34 h: &tokio::runtime::Handle,
35) -> tokio::task::JoinHandle<F::Output>
36where
37 F: Future + Send + 'static,
38 F::Output: Send,
39{
40 h.spawn(CountedFut::new(fut, &PENDING_HANDLES))
41}
42
43#[track_caller]
46pub fn spawn_blocking_counted_w_handle<F, R>(
47 func: F,
48 h: &tokio::runtime::Handle,
49) -> tokio::task::JoinHandle<R>
50where
51 F: FnOnce() -> R + Send + 'static,
52 R: Send + 'static,
53{
54 h.spawn_blocking(move || {
55 let _guard = CountedGuard::new(&PENDING_HANDLES);
56 func()
57 })
58}
59
60pub struct CountedGuard {
61 pendings: &'static AtomicUsize,
62}
63
64impl CountedGuard {
65 pub fn new(pendings: &'static AtomicUsize) -> Self {
66 pendings.fetch_add(1, Ordering::SeqCst);
67 Self { pendings }
68 }
69}
70
71impl Drop for CountedGuard {
72 fn drop(&mut self) {
73 let count = self.pendings.fetch_sub(1, Ordering::SeqCst);
74 trace!("dropping counted guard, count: {}", count - 1);
75 }
76}
77
78pin_project! {
79 #[project = CountedProj]
82 pub struct CountedFut<F> {
83 #[pin]
84 fut: F,
85 pendings: &'static AtomicUsize,
86 index: usize,
87 }
88
89 impl<F> PinnedDrop for CountedFut<F> {
90 fn drop(this: Pin<&mut Self>) {
91 let count = this.pendings.fetch_sub(1, Ordering::SeqCst);
92 trace!("dropping counted future, count: {}", count - 1);
93
94 #[cfg(all(unix, debug_assertions))]
95 {
96 let mut lock = BACKTRACES.lock();
97 lock[this.index] = None;
98 }
99 }
100 }
101}
102
103impl<F> CountedFut<F> {
104 pub fn new(fut: F, pendings: &'static AtomicUsize) -> Self {
106 pendings.fetch_add(1, Ordering::SeqCst);
107
108 #[cfg(all(unix, debug_assertions))]
109 let index = {
110 let mut lock = BACKTRACES.lock();
111 let i = lock.len();
112 lock.push(Some(std::backtrace::Backtrace::capture()));
113 i
114 };
115
116 #[cfg(not(all(unix, debug_assertions)))]
117 let index = 0;
118
119 Self {
120 fut,
121 pendings,
122 index,
123 }
124 }
125}
126
127impl<F> Future for CountedFut<F>
128where
129 F: Future,
130{
131 type Output = F::Output;
132
133 fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<F::Output> {
134 trace!("polling counted future");
135 let this = self.project();
136 this.fut.poll(cx)
137 }
138}
139
140pub async fn wait_for_all_pending_handles() {
144 #[cfg(all(unix, debug_assertions))]
145 {
146 let signals = signal_hook_tokio::Signals::new([signal_hook::consts::SIGTERM])
147 .expect("unable to create signals");
148 let handle = signals.handle();
149
150 let mut sig = tokio::spawn(handle_sigterm(signals));
151 let mut wait = tokio::spawn(wait_handles());
152
153 tokio::select! {
154 _ = &mut sig => {
155 eprintln!("received SIGTERM");
156 }
157 _ = &mut wait => {
158 handle.close();
159 let _ = sig.await;
160 return;
161 }
162 }
163
164 let _ = wait.await;
165 }
166
167 #[cfg(not(all(unix, debug_assertions)))]
168 {
169 wait_handles().await;
170 }
171}
172
173async fn wait_handles() {
174 let mut rounds = 0;
175
176 for _ in 0..600 {
177 match PENDING_HANDLES.load(Ordering::SeqCst) {
178 0 => {
179 info!("All spawned futures done!");
180 break;
181 }
182 n => {
183 rounds += 1;
184 if rounds % 10 == 0 {
185 info!("Waiting on {n} spawned futures before exiting")
186 }
187 }
188 }
189 tokio::time::sleep(Duration::from_millis(100)).await;
190 }
191}
192
193#[cfg(all(unix, debug_assertions))]
196async fn handle_sigterm(mut signals: signal_hook_tokio::SignalsInfo) {
197 use futures_util::StreamExt;
198 while let Some(signal) = signals.next().await {
199 if signal != signal_hook::consts::SIGTERM {
200 continue;
201 }
202
203 let lock = BACKTRACES.lock();
204
205 eprintln!("active counted futures:");
206 for (i, bt) in lock.iter().enumerate() {
207 if let Some(bt) = bt {
208 eprintln!("#{i}: {bt}");
209 }
210 }
211 }
212}