use std::pin::Pin;
use std::sync::atomic::{AtomicUsize, Ordering};
use std::task::{Context, Poll};
use std::time::Duration;
use futures::Future;
use pin_project_lite::pin_project;
use tracing::{info, trace};
pub static PENDING_HANDLES: AtomicUsize = AtomicUsize::new(0);
#[track_caller]
pub fn spawn_counted<F>(fut: F) -> tokio::task::JoinHandle<F::Output>
where
F: Future + Send + 'static,
F::Output: Send,
{
tokio::spawn(CountedFut::new(fut, &PENDING_HANDLES))
}
#[track_caller]
pub fn spawn_counted_w_handle<F>(
fut: F,
h: &tokio::runtime::Handle,
) -> tokio::task::JoinHandle<F::Output>
where
F: Future + Send + 'static,
F::Output: Send,
{
h.spawn(CountedFut::new(fut, &PENDING_HANDLES))
}
#[track_caller]
pub fn spawn_blocking_counted_w_handle<F, R>(
func: F,
h: &tokio::runtime::Handle,
) -> tokio::task::JoinHandle<R>
where
F: FnOnce() -> R + Send + 'static,
R: Send + 'static,
{
h.spawn_blocking(move || {
let _guard = CountedGuard::new(&PENDING_HANDLES);
func()
})
}
pub struct CountedGuard {
pendings: &'static AtomicUsize,
}
impl CountedGuard {
pub fn new(pendings: &'static AtomicUsize) -> Self {
pendings.fetch_add(1, Ordering::SeqCst);
Self { pendings }
}
}
impl Drop for CountedGuard {
fn drop(&mut self) {
let count = self.pendings.fetch_sub(1, Ordering::SeqCst);
trace!("dropping counted guard, count: {}", count - 1);
}
}
pin_project! {
#[project = CountedProj]
pub struct CountedFut<F> {
#[pin]
fut: F,
pendings: &'static AtomicUsize,
}
impl<F> PinnedDrop for CountedFut<F> {
fn drop(this: Pin<&mut Self>) {
let count = this.pendings.fetch_sub(1, Ordering::SeqCst);
trace!("dropping counted future, count: {}", count - 1);
}
}
}
impl<F> CountedFut<F> {
pub fn new(fut: F, pendings: &'static AtomicUsize) -> Self {
pendings.fetch_add(1, Ordering::SeqCst);
Self { fut, pendings }
}
}
impl<F> Future for CountedFut<F>
where
F: Future,
{
type Output = F::Output;
fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<F::Output> {
trace!("polling counted future");
let this = self.project();
this.fut.poll(cx)
}
}
pub async fn wait_for_all_pending_handles() {
let mut rounds = 0;
for _ in 0..600 {
match PENDING_HANDLES.load(Ordering::SeqCst) {
0 => {
info!("All spawned futures done!");
break;
}
n => {
rounds += 1;
if rounds % 10 == 0 {
info!("Waiting on {n} spawned futures before exiting")
}
}
}
tokio::time::sleep(Duration::from_millis(100)).await;
}
}