Implement async_std::sync::Condvar (#369)
* Implement async_std::sync::Condvar Part of #217 * More rigourous detection of notification for condvar * Use state of Waker instead of AtomicUsize to keep track of if task was notified. * Add test for notify_all * Implement wait_timeout_until And add warnings about spurious wakeups to wait and wait_timeout * Use WakerSet for Condvar This should also address concerns about spurious wakeups. * Add test for wait_timeout with no lock held * Add comments describing AwaitNotify struct And remove an unnneded comment in a Debug implementationpull/540/merge
parent
aebba2bd95
commit
db438abb8f
@ -0,0 +1,417 @@
|
||||
use std::fmt;
|
||||
use std::pin::Pin;
|
||||
use std::time::Duration;
|
||||
|
||||
use super::mutex::{guard_lock, MutexGuard};
|
||||
use crate::future::{timeout, Future};
|
||||
use crate::sync::WakerSet;
|
||||
use crate::task::{Context, Poll};
|
||||
|
||||
#[derive(Debug, PartialEq, Eq, Copy, Clone)]
|
||||
pub struct WaitTimeoutResult(bool);
|
||||
|
||||
/// A type indicating whether a timed wait on a condition variable returned due to a time out or
|
||||
/// not
|
||||
impl WaitTimeoutResult {
|
||||
/// Returns `true` if the wait was known to have timed out.
|
||||
pub fn timed_out(self) -> bool {
|
||||
self.0
|
||||
}
|
||||
}
|
||||
|
||||
/// A Condition Variable
|
||||
///
|
||||
/// This type is an async version of [`std::sync::Mutex`].
|
||||
///
|
||||
/// [`std::sync::Condvar`]: https://doc.rust-lang.org/std/sync/struct.Condvar.html
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
/// ```
|
||||
/// # async_std::task::block_on(async {
|
||||
/// #
|
||||
/// use std::sync::Arc;
|
||||
///
|
||||
/// use async_std::sync::{Mutex, Condvar};
|
||||
/// use async_std::task;
|
||||
///
|
||||
/// let pair = Arc::new((Mutex::new(false), Condvar::new()));
|
||||
/// let pair2 = pair.clone();
|
||||
///
|
||||
/// // Inside of our lock, spawn a new thread, and then wait for it to start.
|
||||
/// task::spawn(async move {
|
||||
/// let (lock, cvar) = &*pair2;
|
||||
/// let mut started = lock.lock().await;
|
||||
/// *started = true;
|
||||
/// // We notify the condvar that the value has changed.
|
||||
/// cvar.notify_one();
|
||||
/// });
|
||||
///
|
||||
/// // Wait for the thread to start up.
|
||||
/// let (lock, cvar) = &*pair;
|
||||
/// let mut started = lock.lock().await;
|
||||
/// while !*started {
|
||||
/// started = cvar.wait(started).await;
|
||||
/// }
|
||||
///
|
||||
/// # })
|
||||
/// ```
|
||||
pub struct Condvar {
|
||||
wakers: WakerSet,
|
||||
}
|
||||
|
||||
unsafe impl Send for Condvar {}
|
||||
unsafe impl Sync for Condvar {}
|
||||
|
||||
impl Default for Condvar {
|
||||
fn default() -> Self {
|
||||
Condvar::new()
|
||||
}
|
||||
}
|
||||
|
||||
impl Condvar {
|
||||
/// Creates a new condition variable
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
/// ```
|
||||
/// use async_std::sync::Condvar;
|
||||
///
|
||||
/// let cvar = Condvar::new();
|
||||
/// ```
|
||||
pub fn new() -> Self {
|
||||
Condvar {
|
||||
wakers: WakerSet::new(),
|
||||
}
|
||||
}
|
||||
|
||||
/// Blocks the current task until this condition variable receives a notification.
|
||||
///
|
||||
/// Unlike the std equivalent, this does not check that a single mutex is used at runtime.
|
||||
/// However, as a best practice avoid using with multiple mutexes.
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
/// ```
|
||||
/// # async_std::task::block_on(async {
|
||||
/// use std::sync::Arc;
|
||||
///
|
||||
/// use async_std::sync::{Mutex, Condvar};
|
||||
/// use async_std::task;
|
||||
///
|
||||
/// let pair = Arc::new((Mutex::new(false), Condvar::new()));
|
||||
/// let pair2 = pair.clone();
|
||||
///
|
||||
/// task::spawn(async move {
|
||||
/// let (lock, cvar) = &*pair2;
|
||||
/// let mut started = lock.lock().await;
|
||||
/// *started = true;
|
||||
/// // We notify the condvar that the value has changed.
|
||||
/// cvar.notify_one();
|
||||
/// });
|
||||
///
|
||||
/// // Wait for the thread to start up.
|
||||
/// let (lock, cvar) = &*pair;
|
||||
/// let mut started = lock.lock().await;
|
||||
/// while !*started {
|
||||
/// started = cvar.wait(started).await;
|
||||
/// }
|
||||
/// # })
|
||||
/// ```
|
||||
#[allow(clippy::needless_lifetimes)]
|
||||
pub async fn wait<'a, T>(&self, guard: MutexGuard<'a, T>) -> MutexGuard<'a, T> {
|
||||
let mutex = guard_lock(&guard);
|
||||
|
||||
self.await_notify(guard).await;
|
||||
|
||||
mutex.lock().await
|
||||
}
|
||||
|
||||
fn await_notify<'a, T>(&self, guard: MutexGuard<'a, T>) -> AwaitNotify<'_, 'a, T> {
|
||||
AwaitNotify {
|
||||
cond: self,
|
||||
guard: Some(guard),
|
||||
key: None,
|
||||
}
|
||||
}
|
||||
|
||||
/// Blocks the current taks until this condition variable receives a notification and the
|
||||
/// required condition is met. Spurious wakeups are ignored and this function will only
|
||||
/// return once the condition has been met.
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
/// ```
|
||||
/// # async_std::task::block_on(async {
|
||||
/// #
|
||||
/// use std::sync::Arc;
|
||||
///
|
||||
/// use async_std::sync::{Mutex, Condvar};
|
||||
/// use async_std::task;
|
||||
///
|
||||
/// let pair = Arc::new((Mutex::new(false), Condvar::new()));
|
||||
/// let pair2 = pair.clone();
|
||||
///
|
||||
/// task::spawn(async move {
|
||||
/// let (lock, cvar) = &*pair2;
|
||||
/// let mut started = lock.lock().await;
|
||||
/// *started = true;
|
||||
/// // We notify the condvar that the value has changed.
|
||||
/// cvar.notify_one();
|
||||
/// });
|
||||
///
|
||||
/// // Wait for the thread to start up.
|
||||
/// let (lock, cvar) = &*pair;
|
||||
/// // As long as the value inside the `Mutex<bool>` is `false`, we wait.
|
||||
/// let _guard = cvar.wait_until(lock.lock().await, |started| { *started }).await;
|
||||
/// #
|
||||
/// # })
|
||||
/// ```
|
||||
#[allow(clippy::needless_lifetimes)]
|
||||
pub async fn wait_until<'a, T, F>(
|
||||
&self,
|
||||
mut guard: MutexGuard<'a, T>,
|
||||
mut condition: F,
|
||||
) -> MutexGuard<'a, T>
|
||||
where
|
||||
F: FnMut(&mut T) -> bool,
|
||||
{
|
||||
while !condition(&mut *guard) {
|
||||
guard = self.wait(guard).await;
|
||||
}
|
||||
guard
|
||||
}
|
||||
|
||||
/// Waits on this condition variable for a notification, timing out after a specified duration.
|
||||
///
|
||||
/// For these reasons `Condvar::wait_timeout_until` is recommended in most cases.
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
/// ```
|
||||
/// # async_std::task::block_on(async {
|
||||
/// #
|
||||
/// use std::sync::Arc;
|
||||
/// use std::time::Duration;
|
||||
///
|
||||
/// use async_std::sync::{Mutex, Condvar};
|
||||
/// use async_std::task;
|
||||
///
|
||||
/// let pair = Arc::new((Mutex::new(false), Condvar::new()));
|
||||
/// let pair2 = pair.clone();
|
||||
///
|
||||
/// task::spawn(async move {
|
||||
/// let (lock, cvar) = &*pair2;
|
||||
/// let mut started = lock.lock().await;
|
||||
/// *started = true;
|
||||
/// // We notify the condvar that the value has changed.
|
||||
/// cvar.notify_one();
|
||||
/// });
|
||||
///
|
||||
/// // wait for the thread to start up
|
||||
/// let (lock, cvar) = &*pair;
|
||||
/// let mut started = lock.lock().await;
|
||||
/// loop {
|
||||
/// let result = cvar.wait_timeout(started, Duration::from_millis(10)).await;
|
||||
/// started = result.0;
|
||||
/// if *started == true {
|
||||
/// // We received the notification and the value has been updated, we can leave.
|
||||
/// break
|
||||
/// }
|
||||
/// }
|
||||
/// #
|
||||
/// # })
|
||||
/// ```
|
||||
#[allow(clippy::needless_lifetimes)]
|
||||
pub async fn wait_timeout<'a, T>(
|
||||
&self,
|
||||
guard: MutexGuard<'a, T>,
|
||||
dur: Duration,
|
||||
) -> (MutexGuard<'a, T>, WaitTimeoutResult) {
|
||||
let mutex = guard_lock(&guard);
|
||||
match timeout(dur, self.wait(guard)).await {
|
||||
Ok(guard) => (guard, WaitTimeoutResult(false)),
|
||||
Err(_) => (mutex.lock().await, WaitTimeoutResult(true)),
|
||||
}
|
||||
}
|
||||
|
||||
/// Waits on this condition variable for a notification, timing out after a specified duration.
|
||||
/// Spurious wakes will not cause this function to return.
|
||||
///
|
||||
/// # Examples
|
||||
/// ```
|
||||
/// # async_std::task::block_on(async {
|
||||
/// use std::sync::Arc;
|
||||
/// use std::time::Duration;
|
||||
///
|
||||
/// use async_std::sync::{Mutex, Condvar};
|
||||
/// use async_std::task;
|
||||
///
|
||||
/// let pair = Arc::new((Mutex::new(false), Condvar::new()));
|
||||
/// let pair2 = pair.clone();
|
||||
///
|
||||
/// task::spawn(async move {
|
||||
/// let (lock, cvar) = &*pair2;
|
||||
/// let mut started = lock.lock().await;
|
||||
/// *started = true;
|
||||
/// // We notify the condvar that the value has changed.
|
||||
/// cvar.notify_one();
|
||||
/// });
|
||||
///
|
||||
/// // wait for the thread to start up
|
||||
/// let (lock, cvar) = &*pair;
|
||||
/// let result = cvar.wait_timeout_until(
|
||||
/// lock.lock().await,
|
||||
/// Duration::from_millis(100),
|
||||
/// |&mut started| started,
|
||||
/// ).await;
|
||||
/// if result.1.timed_out() {
|
||||
/// // timed-out without the condition ever evaluating to true.
|
||||
/// }
|
||||
/// // access the locked mutex via result.0
|
||||
/// # });
|
||||
/// ```
|
||||
#[allow(clippy::needless_lifetimes)]
|
||||
pub async fn wait_timeout_until<'a, T, F>(
|
||||
&self,
|
||||
guard: MutexGuard<'a, T>,
|
||||
dur: Duration,
|
||||
condition: F,
|
||||
) -> (MutexGuard<'a, T>, WaitTimeoutResult)
|
||||
where
|
||||
F: FnMut(&mut T) -> bool,
|
||||
{
|
||||
let mutex = guard_lock(&guard);
|
||||
match timeout(dur, self.wait_until(guard, condition)).await {
|
||||
Ok(guard) => (guard, WaitTimeoutResult(false)),
|
||||
Err(_) => (mutex.lock().await, WaitTimeoutResult(true)),
|
||||
}
|
||||
}
|
||||
|
||||
/// Wakes up one blocked task on this condvar.
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
/// ```
|
||||
/// # fn main() { async_std::task::block_on(async {
|
||||
/// use std::sync::Arc;
|
||||
///
|
||||
/// use async_std::sync::{Mutex, Condvar};
|
||||
/// use async_std::task;
|
||||
///
|
||||
/// let pair = Arc::new((Mutex::new(false), Condvar::new()));
|
||||
/// let pair2 = pair.clone();
|
||||
///
|
||||
/// task::spawn(async move {
|
||||
/// let (lock, cvar) = &*pair2;
|
||||
/// let mut started = lock.lock().await;
|
||||
/// *started = true;
|
||||
/// // We notify the condvar that the value has changed.
|
||||
/// cvar.notify_one();
|
||||
/// });
|
||||
///
|
||||
/// // Wait for the thread to start up.
|
||||
/// let (lock, cvar) = &*pair;
|
||||
/// let mut started = lock.lock().await;
|
||||
/// while !*started {
|
||||
/// started = cvar.wait(started).await;
|
||||
/// }
|
||||
/// # }) }
|
||||
/// ```
|
||||
pub fn notify_one(&self) {
|
||||
self.wakers.notify_one();
|
||||
}
|
||||
|
||||
/// Wakes up all blocked tasks on this condvar.
|
||||
///
|
||||
/// # Examples
|
||||
/// ```
|
||||
/// # fn main() { async_std::task::block_on(async {
|
||||
/// #
|
||||
/// use std::sync::Arc;
|
||||
///
|
||||
/// use async_std::sync::{Mutex, Condvar};
|
||||
/// use async_std::task;
|
||||
///
|
||||
/// let pair = Arc::new((Mutex::new(false), Condvar::new()));
|
||||
/// let pair2 = pair.clone();
|
||||
///
|
||||
/// task::spawn(async move {
|
||||
/// let (lock, cvar) = &*pair2;
|
||||
/// let mut started = lock.lock().await;
|
||||
/// *started = true;
|
||||
/// // We notify the condvar that the value has changed.
|
||||
/// cvar.notify_all();
|
||||
/// });
|
||||
///
|
||||
/// // Wait for the thread to start up.
|
||||
/// let (lock, cvar) = &*pair;
|
||||
/// let mut started = lock.lock().await;
|
||||
/// // As long as the value inside the `Mutex<bool>` is `false`, we wait.
|
||||
/// while !*started {
|
||||
/// started = cvar.wait(started).await;
|
||||
/// }
|
||||
/// #
|
||||
/// # }) }
|
||||
/// ```
|
||||
pub fn notify_all(&self) {
|
||||
self.wakers.notify_all();
|
||||
}
|
||||
}
|
||||
|
||||
impl fmt::Debug for Condvar {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
f.pad("Condvar { .. }")
|
||||
}
|
||||
}
|
||||
|
||||
/// A future that waits for another task to notify the condition variable.
|
||||
///
|
||||
/// This is an internal future that `wait` and `wait_until` await on.
|
||||
struct AwaitNotify<'a, 'b, T> {
|
||||
/// The condition variable that we are waiting on
|
||||
cond: &'a Condvar,
|
||||
/// The lock used with `cond`.
|
||||
/// This will be released the first time the future is polled,
|
||||
/// after registering the context to be notified.
|
||||
guard: Option<MutexGuard<'b, T>>,
|
||||
/// A key into the conditions variable's `WakerSet`.
|
||||
/// This is set to the index of the `Waker` for the context each time
|
||||
/// the future is polled and not completed.
|
||||
key: Option<usize>,
|
||||
}
|
||||
|
||||
impl<'a, 'b, T> Future for AwaitNotify<'a, 'b, T> {
|
||||
type Output = ();
|
||||
|
||||
fn poll(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
|
||||
match self.guard.take() {
|
||||
Some(_) => {
|
||||
self.key = Some(self.cond.wakers.insert(cx));
|
||||
// the guard is dropped when we return, which frees the lock
|
||||
Poll::Pending
|
||||
}
|
||||
None => {
|
||||
if let Some(key) = self.key {
|
||||
if self.cond.wakers.remove_if_notified(key, cx) {
|
||||
self.key = None;
|
||||
Poll::Ready(())
|
||||
} else {
|
||||
Poll::Pending
|
||||
}
|
||||
} else {
|
||||
// This should only happen if it is polled twice after receiving a notification
|
||||
Poll::Ready(())
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a, 'b, T> Drop for AwaitNotify<'a, 'b, T> {
|
||||
fn drop(&mut self) {
|
||||
if let Some(key) = self.key {
|
||||
self.cond.wakers.cancel(key);
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,91 @@
|
||||
#![cfg(feature = "unstable")]
|
||||
use std::sync::Arc;
|
||||
use std::time::Duration;
|
||||
|
||||
use async_std::sync::{Condvar, Mutex};
|
||||
use async_std::task::{self, JoinHandle};
|
||||
|
||||
#[test]
|
||||
fn wait_timeout_with_lock() {
|
||||
task::block_on(async {
|
||||
let pair = Arc::new((Mutex::new(false), Condvar::new()));
|
||||
let pair2 = pair.clone();
|
||||
|
||||
task::spawn(async move {
|
||||
let (m, c) = &*pair2;
|
||||
let _g = m.lock().await;
|
||||
task::sleep(Duration::from_millis(20)).await;
|
||||
c.notify_one();
|
||||
});
|
||||
|
||||
let (m, c) = &*pair;
|
||||
let (_, wait_result) = c
|
||||
.wait_timeout(m.lock().await, Duration::from_millis(10))
|
||||
.await;
|
||||
assert!(wait_result.timed_out());
|
||||
})
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn wait_timeout_without_lock() {
|
||||
task::block_on(async {
|
||||
let m = Mutex::new(false);
|
||||
let c = Condvar::new();
|
||||
|
||||
let (_, wait_result) = c
|
||||
.wait_timeout(m.lock().await, Duration::from_millis(10))
|
||||
.await;
|
||||
assert!(wait_result.timed_out());
|
||||
})
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn wait_timeout_until_timed_out() {
|
||||
task::block_on(async {
|
||||
let m = Mutex::new(false);
|
||||
let c = Condvar::new();
|
||||
|
||||
let (_, wait_result) = c
|
||||
.wait_timeout_until(m.lock().await, Duration::from_millis(10), |&mut started| {
|
||||
started
|
||||
})
|
||||
.await;
|
||||
assert!(wait_result.timed_out());
|
||||
})
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn notify_all() {
|
||||
task::block_on(async {
|
||||
let mut tasks: Vec<JoinHandle<()>> = Vec::new();
|
||||
let pair = Arc::new((Mutex::new(0u32), Condvar::new()));
|
||||
|
||||
for _ in 0..10 {
|
||||
let pair = pair.clone();
|
||||
tasks.push(task::spawn(async move {
|
||||
let (m, c) = &*pair;
|
||||
let mut count = m.lock().await;
|
||||
while *count == 0 {
|
||||
count = c.wait(count).await;
|
||||
}
|
||||
*count += 1;
|
||||
}));
|
||||
}
|
||||
|
||||
// Give some time for tasks to start up
|
||||
task::sleep(Duration::from_millis(5)).await;
|
||||
|
||||
let (m, c) = &*pair;
|
||||
{
|
||||
let mut count = m.lock().await;
|
||||
*count += 1;
|
||||
c.notify_all();
|
||||
}
|
||||
|
||||
for t in tasks {
|
||||
t.await;
|
||||
}
|
||||
let count = m.lock().await;
|
||||
assert_eq!(11, *count);
|
||||
})
|
||||
}
|
Loading…
Reference in New Issue