/* This file is part of DarkFi (https://dark.fi)
*
* Copyright (C) 2020-2023 Dyne.org foundation
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see .
*/
use std::{
future::Future,
pin::Pin,
sync::Mutex,
task::{Context, Poll, Waker},
};
/// Condition variables allow you to block a task while waiting for an event to occur.
/// Condition variables are typically associated with a boolean predicate (a condition).
/// ```rust
/// let cv = Arc::new(CondVar::new());
///
/// let cv_ = cv.clone();
/// executor_
/// .spawn(async move {
/// // Waits here until notify() is called
/// cv_.wait().await;
/// // Check for some condition...
/// })
/// .detach();
///
/// // Allow above code to continue
/// cv.notify();
/// ```
/// After the condition variable is woken up, the user may `wait` again for another `notify`
/// signal by first calling `cv_.reset()`.
pub struct CondVar {
state: Mutex,
}
struct CondVarState {
is_awake: bool,
waker: Option,
}
impl CondVar {
pub fn new() -> Self {
Self { state: Mutex::new(CondVarState { is_awake: false, waker: None }) }
}
/// Wakeup the waiting task. Subsequent calls to this do nothing until `wait()` is called.
pub fn notify(&self) {
let mut state = self.state.lock().unwrap();
state.is_awake = true;
// Notify the executor that the pending future from wait() is to be polled again.
if let Some(waker) = state.waker.take() {
waker.wake()
}
}
/// Reset the condition variable and wait for a notification
pub fn wait(&self) -> CondVarWait {
CondVarWait { state: &self.state }
}
/// Reset self ready to wait() again.
/// The reason this is separate from `wait()` is that usually
/// on the first `wait()` we want to catch any `notify()` calls that
/// happened before we started. For example,
/// ```rust
/// loop {
/// // Wait for signal
/// cv.wait().await;
///
/// // Do stuff...
///
/// cv.reset();
/// }
/// ```
pub fn reset(&self) {
let mut state = self.state.lock().unwrap();
state.is_awake = false;
}
}
impl Default for CondVar {
fn default() -> Self {
Self::new()
}
}
/// Awaitable futures object returned by `condvar.wait()`
pub struct CondVarWait<'a> {
state: &'a Mutex,
}
impl<'a> Future for CondVarWait<'a> {
type Output = ();
fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll {
let mut state = self.state.lock().unwrap();
// Avoid cloning wherever possible.
// This code below is equivalent to:
//
// state.waker = Some(cx.waker().clone());
//
// However checking whether the waker we have wakes up the same task
// as the one in the context cx, means we don't have to re-clone if
// we already have it.
//
// It's a minor thing which is basically recommended in the docs on
// creating pollable futures.
let new_waker = match state.waker.take() {
Some(waker) => {
let cx_waker = cx.waker();
if cx_waker.will_wake(&waker) {
waker
} else {
cx_waker.clone()
}
}
None => cx.waker().clone(),
};
state.waker = Some(new_waker);
match state.is_awake {
true => Poll::Ready(()),
false => Poll::Pending,
}
}
}
#[cfg(test)]
mod tests {
use super::*;
use smol::Executor;
use std::sync::Arc;
#[test]
fn condvar_test() {
let executor = Arc::new(Executor::new());
let executor_ = executor.clone();
smol::block_on(executor.run(async move {
let cv = Arc::new(CondVar::new());
let cv_ = cv.clone();
executor_
.spawn(async move {
// Waits here until notify() is called
cv_.wait().await;
})
.detach();
// Allow above code to continue
cv.notify();
}))
}
#[test]
fn condvar_reset() {
let executor = Arc::new(Executor::new());
let executor_ = executor.clone();
smol::block_on(executor.run(async move {
let cv = Arc::new(CondVar::new());
let cv_ = cv.clone();
executor_
.spawn(async move {
cv_.wait().await;
})
.detach();
// #1 send signal
cv.notify();
// Multiple calls to notify do nothing until we call reset()
cv.notify();
// Without calling reset(), then the wait() will return instantly
cv.reset();
let cv_ = cv.clone();
executor_
.spawn(async move {
cv_.wait().await;
})
.detach();
// #2 send signal again
cv.notify();
}))
}
}