use std::future::Future;
use std::pin::Pin;
use std::sync::atomic::AtomicUsize;
use std::sync::{Condvar, Mutex};
use std::task::Waker;
use crate::Executor;
#[derive(Debug)]
pub struct ExecutorDrain {
pub(crate) executor: Executor,
pub(crate) waiter_id: Option<usize>,
}
impl Future for ExecutorDrain {
type Output = ();
fn poll(
self: Pin<&mut Self>,
cx: &mut std::task::Context<'_>,
) -> std::task::Poll<Self::Output> {
let this = self.get_mut();
let drain_notify = this.executor.imp.drain_notify();
drain_notify.register(&mut this.waiter_id, cx.waker());
let running_tasks = drain_notify
.running_tasks
.load(std::sync::atomic::Ordering::Acquire);
#[cfg(feature = "logwise-forensic")]
logwise::forensic!(
"some_global_executor.drain.polled",
running_tasks = support(running_tasks as u64),
);
if running_tasks == 0 {
if let Some(waiter_id) = this.waiter_id.take() {
drain_notify.unregister(waiter_id);
}
std::task::Poll::Ready(())
} else {
std::task::Poll::Pending
}
}
}
#[derive(Debug)]
pub(crate) struct DrainNotify {
pub(crate) running_tasks: AtomicUsize,
pub(crate) next_waiter_id: AtomicUsize,
pub(crate) waiters: Mutex<Vec<(usize, Waker)>>,
pub(crate) completed: Condvar,
}
impl DrainNotify {
pub(crate) fn new() -> Self {
Self {
running_tasks: AtomicUsize::new(0),
next_waiter_id: AtomicUsize::new(0),
waiters: Mutex::new(Vec::new()),
completed: Condvar::new(),
}
}
pub(crate) fn lock_waiters(&self) -> std::sync::MutexGuard<'_, Vec<(usize, Waker)>> {
self.waiters
.lock()
.unwrap_or_else(std::sync::PoisonError::into_inner)
}
pub(crate) fn register(&self, waiter_id: &mut Option<usize>, waker: &Waker) {
let waiter_id = *waiter_id.get_or_insert_with(|| {
self.next_waiter_id
.fetch_add(1, std::sync::atomic::Ordering::Relaxed)
});
let mut waiters = self.lock_waiters();
if let Some((_, registered)) = waiters.iter_mut().find(|(id, _)| *id == waiter_id) {
if !registered.will_wake(waker) {
*registered = waker.clone();
}
} else {
waiters.push((waiter_id, waker.clone()));
}
}
pub(crate) fn unregister(&self, waiter_id: usize) {
self.lock_waiters().retain(|(id, _)| *id != waiter_id);
}
pub(crate) fn task_finished(&self) -> usize {
let old = self
.running_tasks
.fetch_sub(1, std::sync::atomic::Ordering::Release);
debug_assert!(old > 0, "running task counter underflow");
if old == 1 {
let waiters = {
let mut registered = self.lock_waiters();
self.completed.notify_all();
std::mem::take(&mut *registered)
};
for (_, waker) in waiters {
if std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| waker.wake())).is_err()
{
logwise::log!("Drain waker panicked; ignoring it to keep the executor alive");
}
}
}
old
}
#[cfg(not(target_arch = "wasm32"))]
pub(crate) fn wait_for_completion(&self) {
let mut waiters = self.lock_waiters();
while self
.running_tasks
.load(std::sync::atomic::Ordering::Acquire)
!= 0
{
waiters = self
.completed
.wait(waiters)
.unwrap_or_else(std::sync::PoisonError::into_inner);
}
}
}
impl From<ExecutorDrain> for Executor {
fn from(mut drain: ExecutorDrain) -> Self {
if let Some(waiter_id) = drain.waiter_id.take() {
drain.executor.imp.drain_notify().unregister(waiter_id);
}
drain.executor.clone()
}
}
impl From<Executor> for ExecutorDrain {
fn from(executor: Executor) -> Self {
ExecutorDrain {
executor,
waiter_id: None,
}
}
}
impl Drop for ExecutorDrain {
fn drop(&mut self) {
if let Some(waiter_id) = self.waiter_id.take() {
self.executor.imp.drain_notify().unregister(waiter_id);
}
}
}
impl AsRef<Executor> for ExecutorDrain {
fn as_ref(&self) -> &Executor {
&self.executor
}
}
impl AsMut<Executor> for ExecutorDrain {
fn as_mut(&mut self) -> &mut Executor {
&mut self.executor
}
}