forked from mirror/async-std
Add utility type WakerSet to the sync module (#390)
* Add utility type Registry to the sync module * Remove unused import * Split unregister into complete and cancel * Refactoring and renaming * Split remove() into complete() and cancel() * Rename to WakerSet * Ignore clippy warning * Ignore another clippy warning * Use stronger SeqCst orderingpoc-serde-support
parent
3dd59d7056
commit
87de4e1598
@ -0,0 +1,42 @@
|
|||||||
|
#![feature(test)]
|
||||||
|
|
||||||
|
extern crate test;
|
||||||
|
|
||||||
|
use std::sync::Arc;
|
||||||
|
|
||||||
|
use async_std::sync::Mutex;
|
||||||
|
use async_std::task;
|
||||||
|
use test::Bencher;
|
||||||
|
|
||||||
|
#[bench]
|
||||||
|
fn create(b: &mut Bencher) {
|
||||||
|
b.iter(|| Mutex::new(()));
|
||||||
|
}
|
||||||
|
|
||||||
|
#[bench]
|
||||||
|
fn contention(b: &mut Bencher) {
|
||||||
|
b.iter(|| task::block_on(run(10, 1000)));
|
||||||
|
}
|
||||||
|
|
||||||
|
#[bench]
|
||||||
|
fn no_contention(b: &mut Bencher) {
|
||||||
|
b.iter(|| task::block_on(run(1, 10000)));
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn run(task: usize, iter: usize) {
|
||||||
|
let m = Arc::new(Mutex::new(()));
|
||||||
|
let mut tasks = Vec::new();
|
||||||
|
|
||||||
|
for _ in 0..task {
|
||||||
|
let m = m.clone();
|
||||||
|
tasks.push(task::spawn(async move {
|
||||||
|
for _ in 0..iter {
|
||||||
|
let _ = m.lock().await;
|
||||||
|
}
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
|
||||||
|
for t in tasks {
|
||||||
|
t.await;
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,11 @@
|
|||||||
|
#![feature(test)]
|
||||||
|
|
||||||
|
extern crate test;
|
||||||
|
|
||||||
|
use async_std::task;
|
||||||
|
use test::Bencher;
|
||||||
|
|
||||||
|
#[bench]
|
||||||
|
fn block_on(b: &mut Bencher) {
|
||||||
|
b.iter(|| task::block_on(async {}));
|
||||||
|
}
|
@ -0,0 +1,200 @@
|
|||||||
|
//! A common utility for building synchronization primitives.
|
||||||
|
//!
|
||||||
|
//! When an async operation is blocked, it needs to register itself somewhere so that it can be
|
||||||
|
//! notified later on. The `WakerSet` type helps with keeping track of such async operations and
|
||||||
|
//! notifying them when they may make progress.
|
||||||
|
|
||||||
|
use std::cell::UnsafeCell;
|
||||||
|
use std::ops::{Deref, DerefMut};
|
||||||
|
use std::sync::atomic::{AtomicUsize, Ordering};
|
||||||
|
|
||||||
|
use crossbeam_utils::Backoff;
|
||||||
|
use slab::Slab;
|
||||||
|
|
||||||
|
use crate::task::{Context, Waker};
|
||||||
|
|
||||||
|
/// Set when the entry list is locked.
|
||||||
|
#[allow(clippy::identity_op)]
|
||||||
|
const LOCKED: usize = 1 << 0;
|
||||||
|
|
||||||
|
/// Set when there are tasks for `notify_one()` to wake.
|
||||||
|
const NOTIFY_ONE: usize = 1 << 1;
|
||||||
|
|
||||||
|
/// Set when there are tasks for `notify_all()` to wake.
|
||||||
|
const NOTIFY_ALL: usize = 1 << 2;
|
||||||
|
|
||||||
|
/// Inner representation of `WakerSet`.
|
||||||
|
struct Inner {
|
||||||
|
/// A list of entries in the set.
|
||||||
|
///
|
||||||
|
/// Each entry has an optional waker associated with the task that is executing the operation.
|
||||||
|
/// If the waker is set to `None`, that means the task has been woken up but hasn't removed
|
||||||
|
/// itself from the `WakerSet` yet.
|
||||||
|
///
|
||||||
|
/// The key of each entry is its index in the `Slab`.
|
||||||
|
entries: Slab<Option<Waker>>,
|
||||||
|
|
||||||
|
/// The number of entries that have the waker set to `None`.
|
||||||
|
none_count: usize,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// A set holding wakers.
|
||||||
|
pub struct WakerSet {
|
||||||
|
/// Holds three bits: `LOCKED`, `NOTIFY_ONE`, and `NOTIFY_ALL`.
|
||||||
|
flag: AtomicUsize,
|
||||||
|
|
||||||
|
/// A set holding wakers.
|
||||||
|
inner: UnsafeCell<Inner>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl WakerSet {
|
||||||
|
/// Creates a new `WakerSet`.
|
||||||
|
#[inline]
|
||||||
|
pub fn new() -> WakerSet {
|
||||||
|
WakerSet {
|
||||||
|
flag: AtomicUsize::new(0),
|
||||||
|
inner: UnsafeCell::new(Inner {
|
||||||
|
entries: Slab::new(),
|
||||||
|
none_count: 0,
|
||||||
|
}),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Inserts a waker for a blocked operation and returns a key associated with it.
|
||||||
|
pub fn insert(&self, cx: &Context<'_>) -> usize {
|
||||||
|
let w = cx.waker().clone();
|
||||||
|
self.lock().entries.insert(Some(w))
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Updates the waker of a previously inserted entry.
|
||||||
|
pub fn update(&self, key: usize, cx: &Context<'_>) {
|
||||||
|
let mut inner = self.lock();
|
||||||
|
|
||||||
|
match &mut inner.entries[key] {
|
||||||
|
None => {
|
||||||
|
// Fill in the waker.
|
||||||
|
let w = cx.waker().clone();
|
||||||
|
inner.entries[key] = Some(w);
|
||||||
|
inner.none_count -= 1;
|
||||||
|
}
|
||||||
|
Some(w) => {
|
||||||
|
// Replace the waker if the existing one is different.
|
||||||
|
if !w.will_wake(cx.waker()) {
|
||||||
|
*w = cx.waker().clone();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Removes the waker of a completed operation.
|
||||||
|
pub fn complete(&self, key: usize) {
|
||||||
|
let mut inner = self.lock();
|
||||||
|
if inner.entries.remove(key).is_none() {
|
||||||
|
inner.none_count -= 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Removes the waker of a cancelled operation.
|
||||||
|
pub fn cancel(&self, key: usize) {
|
||||||
|
let mut inner = self.lock();
|
||||||
|
if inner.entries.remove(key).is_none() {
|
||||||
|
inner.none_count -= 1;
|
||||||
|
|
||||||
|
// The operation was cancelled and notified so notify another operation instead.
|
||||||
|
if let Some((_, opt_waker)) = inner.entries.iter_mut().next() {
|
||||||
|
// If there is no waker in this entry, that means it was already woken.
|
||||||
|
if let Some(w) = opt_waker.take() {
|
||||||
|
w.wake();
|
||||||
|
inner.none_count += 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Notifies one blocked operation.
|
||||||
|
#[inline]
|
||||||
|
pub fn notify_one(&self) {
|
||||||
|
// Use `SeqCst` ordering to synchronize with `Lock::drop()`.
|
||||||
|
if self.flag.load(Ordering::SeqCst) & NOTIFY_ONE != 0 {
|
||||||
|
self.notify(false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Notifies all blocked operations.
|
||||||
|
// TODO: Delete this attribute when `crate::sync::channel()` is stabilized.
|
||||||
|
#[cfg(feature = "unstable")]
|
||||||
|
#[inline]
|
||||||
|
pub fn notify_all(&self) {
|
||||||
|
// Use `SeqCst` ordering to synchronize with `Lock::drop()`.
|
||||||
|
if self.flag.load(Ordering::SeqCst) & NOTIFY_ALL != 0 {
|
||||||
|
self.notify(true);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Notifies blocked operations, either one or all of them.
|
||||||
|
fn notify(&self, all: bool) {
|
||||||
|
let mut inner = &mut *self.lock();
|
||||||
|
|
||||||
|
for (_, opt_waker) in inner.entries.iter_mut() {
|
||||||
|
// If there is no waker in this entry, that means it was already woken.
|
||||||
|
if let Some(w) = opt_waker.take() {
|
||||||
|
w.wake();
|
||||||
|
inner.none_count += 1;
|
||||||
|
}
|
||||||
|
if !all {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Locks the list of entries.
|
||||||
|
#[cold]
|
||||||
|
fn lock(&self) -> Lock<'_> {
|
||||||
|
let backoff = Backoff::new();
|
||||||
|
while self.flag.fetch_or(LOCKED, Ordering::Acquire) & LOCKED != 0 {
|
||||||
|
backoff.snooze();
|
||||||
|
}
|
||||||
|
Lock { waker_set: self }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// A guard holding a `WakerSet` locked.
|
||||||
|
struct Lock<'a> {
|
||||||
|
waker_set: &'a WakerSet,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Drop for Lock<'_> {
|
||||||
|
#[inline]
|
||||||
|
fn drop(&mut self) {
|
||||||
|
let mut flag = 0;
|
||||||
|
|
||||||
|
// If there is at least one entry and all are `Some`, then `notify_one()` has work to do.
|
||||||
|
if !self.entries.is_empty() && self.none_count == 0 {
|
||||||
|
flag |= NOTIFY_ONE;
|
||||||
|
}
|
||||||
|
|
||||||
|
// If there is at least one `Some` entry, then `notify_all()` has work to do.
|
||||||
|
if self.entries.len() - self.none_count > 0 {
|
||||||
|
flag |= NOTIFY_ALL;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Use `SeqCst` ordering to synchronize with `WakerSet::lock_to_notify()`.
|
||||||
|
self.waker_set.flag.store(flag, Ordering::SeqCst);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Deref for Lock<'_> {
|
||||||
|
type Target = Inner;
|
||||||
|
|
||||||
|
#[inline]
|
||||||
|
fn deref(&self) -> &Inner {
|
||||||
|
unsafe { &*self.waker_set.inner.get() }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl DerefMut for Lock<'_> {
|
||||||
|
#[inline]
|
||||||
|
fn deref_mut(&mut self) -> &mut Inner {
|
||||||
|
unsafe { &mut *self.waker_set.inner.get() }
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in New Issue