use std::io;
#[derive(Default)]
struct BindStatus {
succeeded: std::sync::atomic::AtomicUsize,
failed: std::sync::atomic::AtomicUsize,
first_err: std::sync::Mutex<Option<io::Error>>,
notify: tokio::sync::Notify,
}
#[derive(Clone, Default)]
pub struct PerThreadShutdown {
pub(crate) inner: tokio_util::sync::CancellationToken,
bind_status: std::sync::Arc<BindStatus>,
}
impl PerThreadShutdown {
#[must_use]
pub fn new() -> Self {
Self::default()
}
pub fn trigger(&self) {
self.inner.cancel();
}
pub async fn notified(&self) {
self.inner.cancelled().await;
}
pub(crate) fn report_bind_success(&self) {
self
.bind_status
.succeeded
.fetch_add(1, std::sync::atomic::Ordering::SeqCst);
self.bind_status.notify.notify_waiters();
}
pub(crate) fn report_bind_failure(&self, err: io::Error) {
{
let mut guard = self.bind_status.first_err.lock().unwrap();
if guard.is_none() {
*guard = Some(err);
}
}
self
.bind_status
.failed
.fetch_add(1, std::sync::atomic::Ordering::SeqCst);
self.bind_status.notify.notify_waiters();
}
pub async fn wait_for_bind_outcome(&self, total: usize) -> io::Result<()> {
use std::sync::atomic::Ordering;
loop {
let notified = self.bind_status.notify.notified();
tokio::pin!(notified);
notified.as_mut().enable();
let succ = self.bind_status.succeeded.load(Ordering::SeqCst);
let fail = self.bind_status.failed.load(Ordering::SeqCst);
if succ > 0 {
return Ok(());
}
if succ + fail >= total {
let err = self
.bind_status
.first_err
.lock()
.unwrap()
.take()
.unwrap_or_else(|| {
io::Error::other(format!("all {total} per-thread workers failed to bind"))
});
return Err(err);
}
notified.await;
}
}
}