use std::sync::atomic::{AtomicBool, Ordering};
use std::sync::Arc;
use tokio::sync::Notify;
#[derive(Default)]
pub struct Gate {
notify: Notify,
}
impl Gate {
pub fn new() -> Self {
Self::default()
}
pub fn notify(&self) {
self.notify.notify_waiters();
}
pub async fn wait_until<T>(&self, mut check: impl FnMut() -> Option<T>) -> T {
loop {
let notified = self.notify.notified();
tokio::pin!(notified);
notified.as_mut().enable();
if let Some(value) = check() {
return value;
}
notified.await;
}
}
pub async fn wait_until_cancellable<T>(
&self,
token: &CancelToken,
mut check: impl FnMut() -> Option<T>,
) -> Result<T, Cancelled> {
loop {
let notified = self.notify.notified();
let cancelled = token.inner.notify.notified();
tokio::pin!(notified);
tokio::pin!(cancelled);
notified.as_mut().enable();
cancelled.as_mut().enable();
if token.is_cancelled() {
return Err(Cancelled);
}
if let Some(value) = check() {
return Ok(value);
}
tokio::select! {
_ = notified => {}
_ = cancelled => {}
}
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct Cancelled;
impl Cancelled {
pub fn into_error(self, context: &str) -> structfs_core_store::Error {
structfs_core_store::Error::cancelled(context.to_string())
}
}
#[derive(Default)]
struct CancelInner {
flag: AtomicBool,
notify: Notify,
}
#[derive(Clone, Default)]
pub struct CancelToken {
inner: Arc<CancelInner>,
}
impl CancelToken {
pub fn new() -> Self {
Self::default()
}
pub fn cancel(&self) {
self.inner.flag.store(true, Ordering::SeqCst);
self.inner.notify.notify_waiters();
}
pub fn is_cancelled(&self) -> bool {
self.inner.flag.load(Ordering::SeqCst)
}
pub async fn cancelled(&self) {
loop {
let notified = self.inner.notify.notified();
tokio::pin!(notified);
notified.as_mut().enable();
if self.is_cancelled() {
return;
}
notified.await;
}
}
}
#[cfg(test)]
mod tests {
use super::*;
use std::sync::Mutex;
#[tokio::test]
async fn wait_resolves_when_predicate_holds() {
let gate = Arc::new(Gate::new());
let slot: Arc<Mutex<Option<i32>>> = Arc::new(Mutex::new(None));
let waiter = {
let gate = gate.clone();
let slot = slot.clone();
tokio::spawn(async move { gate.wait_until(|| *slot.lock().unwrap()).await })
};
tokio::task::yield_now().await;
*slot.lock().unwrap() = Some(7);
gate.notify();
assert_eq!(waiter.await.unwrap(), 7);
}
#[tokio::test]
async fn notify_racing_check_is_not_lost() {
for _ in 0..100 {
let gate = Arc::new(Gate::new());
let flag = Arc::new(AtomicBool::new(false));
let waiter = {
let gate = gate.clone();
let flag = flag.clone();
tokio::spawn(async move {
gate.wait_until(|| flag.load(Ordering::SeqCst).then_some(()))
.await
})
};
let notifier = {
let gate = gate.clone();
let flag = flag.clone();
tokio::spawn(async move {
flag.store(true, Ordering::SeqCst);
gate.notify();
})
};
tokio::time::timeout(std::time::Duration::from_secs(5), waiter)
.await
.expect("lost wakeup")
.unwrap();
notifier.await.unwrap();
}
}
#[tokio::test]
async fn cancellation_wakes_parked_wait() {
let gate = Arc::new(Gate::new());
let token = CancelToken::new();
let waiter = {
let gate = gate.clone();
let token = token.clone();
tokio::spawn(async move { gate.wait_until_cancellable(&token, || None::<()>).await })
};
tokio::task::yield_now().await;
token.cancel();
assert_eq!(waiter.await.unwrap(), Err(Cancelled));
}
#[tokio::test]
async fn cancel_before_wait_resolves_immediately() {
let gate = Gate::new();
let token = CancelToken::new();
token.cancel();
assert_eq!(
gate.wait_until_cancellable(&token, || None::<()>).await,
Err(Cancelled)
);
}
#[tokio::test]
async fn predicate_wins_over_no_cancel() {
let gate = Gate::new();
let token = CancelToken::new();
assert_eq!(gate.wait_until_cancellable(&token, || Some(1)).await, Ok(1));
}
#[tokio::test]
async fn cancelled_future_resolves() {
let token = CancelToken::new();
let t2 = token.clone();
let waiter = tokio::spawn(async move { t2.cancelled().await });
tokio::task::yield_now().await;
token.cancel();
waiter.await.unwrap();
}
}