implement
This commit is contained in:
parent
d58b347140
commit
050eedc7c8
7 changed files with 341 additions and 0 deletions
22
Cargo.toml
Normal file
22
Cargo.toml
Normal file
|
@ -0,0 +1,22 @@
|
||||||
|
[package]
|
||||||
|
name = "notifies"
|
||||||
|
version = "0.1.0"
|
||||||
|
edition = "2021"
|
||||||
|
authors = ["nerodono <nerodono0@gmail.com>"]
|
||||||
|
description = "various efficient async notifies"
|
||||||
|
homepage = "https://git.viende.su/VienDesu/notifies"
|
||||||
|
repository = "https://git.viende.su/VienDesu/notifies"
|
||||||
|
|
||||||
|
license = "MIT"
|
||||||
|
keywords = ["async", "atomic", "waker"]
|
||||||
|
|
||||||
|
[dependencies]
|
||||||
|
|
||||||
|
[dev-dependencies]
|
||||||
|
tokio = { version = "1.44.2", features = [
|
||||||
|
"rt",
|
||||||
|
"rt-multi-thread",
|
||||||
|
"sync",
|
||||||
|
"macros",
|
||||||
|
"time",
|
||||||
|
] }
|
|
@ -1,2 +1,5 @@
|
||||||
# notifies
|
# notifies
|
||||||
|
|
||||||
|
Various efficient notifies for your async code.
|
||||||
|
|
||||||
|
|
||||||
|
|
10
src/lib.rs
Normal file
10
src/lib.rs
Normal file
|
@ -0,0 +1,10 @@
|
||||||
|
//! # Notifies
|
||||||
|
//!
|
||||||
|
//! - [`spmc`] - single producer, multi consumer
|
||||||
|
//! - [`spsc`] - single producer, single consumer
|
||||||
|
|
||||||
|
pub mod spmc;
|
||||||
|
pub mod spsc;
|
||||||
|
|
||||||
|
mod utils;
|
||||||
|
mod volmutex;
|
125
src/spmc.rs
Normal file
125
src/spmc.rs
Normal file
|
@ -0,0 +1,125 @@
|
||||||
|
//! # Single producer, multi-consumer notify
|
||||||
|
//!
|
||||||
|
//! See [`Master`] for more info on it.
|
||||||
|
|
||||||
|
use crate::spsc;
|
||||||
|
|
||||||
|
/// Single producer, multi-consumer notify. Essentially just collection of
|
||||||
|
/// [`spsc`] masters.
|
||||||
|
#[derive(Default)]
|
||||||
|
pub struct Master {
|
||||||
|
masters: Vec<spsc::Master>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Master {
|
||||||
|
/// Create master.
|
||||||
|
pub const fn new() -> Self {
|
||||||
|
Self {
|
||||||
|
masters: Vec::new(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Preallocate storage for `cap` slaves.
|
||||||
|
pub fn with_capacity(cap: usize) -> Self {
|
||||||
|
Self {
|
||||||
|
masters: Vec::with_capacity(cap),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Reserve space for at least `additional` more slaves.
|
||||||
|
pub fn reserve(&mut self, additional: usize) {
|
||||||
|
self.masters.reserve(additional);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Master {
|
||||||
|
/// Register slave and return handle to it. Dropping slave
|
||||||
|
/// does nothing, as for now, space reclamation is not implemented.
|
||||||
|
pub fn make_slave(&mut self) -> spsc::Slave {
|
||||||
|
let (master, slave) = spsc::make();
|
||||||
|
self.masters.push(master);
|
||||||
|
|
||||||
|
slave
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Get number of currently registered slaves.
|
||||||
|
pub fn len(&self) -> usize {
|
||||||
|
self.masters.len()
|
||||||
|
}
|
||||||
|
|
||||||
|
/// if [`Master::len`] == 0.
|
||||||
|
pub fn is_empty(&self) -> bool {
|
||||||
|
self.masters.is_empty()
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Notify every registered slave. In tokio terms, this method stores
|
||||||
|
/// permit for every slave, thus next await of [`spsc::Slave`] will wake-up
|
||||||
|
/// immediately. Only one permit is stored.
|
||||||
|
pub fn notify_waiters(&mut self) {
|
||||||
|
for master in self.masters.iter_mut() {
|
||||||
|
master.notify();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
use std::time::Duration;
|
||||||
|
|
||||||
|
use tokio::{sync::mpsc, time::timeout};
|
||||||
|
|
||||||
|
use super::*;
|
||||||
|
|
||||||
|
type Rx = mpsc::UnboundedReceiver<usize>;
|
||||||
|
|
||||||
|
async fn task(mut slave: spsc::Slave, tx: impl Fn() + Send + 'static) {
|
||||||
|
loop {
|
||||||
|
(&mut slave).await;
|
||||||
|
tx();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tokio::test(flavor = "multi_thread", worker_threads = 4)]
|
||||||
|
async fn test_wakeups() {
|
||||||
|
const WORKERS: usize = 2000;
|
||||||
|
const TRIES: usize = 4;
|
||||||
|
const TIMEOUT: Duration = Duration::from_millis(500);
|
||||||
|
|
||||||
|
let correct: Vec<usize> = (0..WORKERS).collect();
|
||||||
|
let mut buf = Vec::with_capacity(WORKERS);
|
||||||
|
|
||||||
|
async fn case(rx: &mut Rx, correct: &[usize], buf: &mut Vec<usize>) {
|
||||||
|
unsafe { buf.set_len(0) };
|
||||||
|
for _ in 0..correct.len() {
|
||||||
|
let item = timeout(TIMEOUT, rx.recv())
|
||||||
|
.await
|
||||||
|
.expect("timed out")
|
||||||
|
.unwrap();
|
||||||
|
buf.push(item);
|
||||||
|
}
|
||||||
|
buf.sort_unstable();
|
||||||
|
|
||||||
|
assert_eq!(buf, correct);
|
||||||
|
}
|
||||||
|
|
||||||
|
let mut master = Master::default();
|
||||||
|
let (tx, mut rx) = mpsc::unbounded_channel();
|
||||||
|
|
||||||
|
for worker_id in 0..WORKERS {
|
||||||
|
let slave = master.make_slave();
|
||||||
|
let tx = tx.clone();
|
||||||
|
tokio::spawn(task(slave, move || tx.send(worker_id).unwrap()));
|
||||||
|
}
|
||||||
|
|
||||||
|
for try_no in 0..TRIES {
|
||||||
|
master.notify_waiters();
|
||||||
|
eprint!("try#{try_no}...");
|
||||||
|
case(&mut rx, &correct, &mut buf).await;
|
||||||
|
eprintln!(" success");
|
||||||
|
}
|
||||||
|
|
||||||
|
assert!(timeout(Duration::from_millis(100), rx.recv())
|
||||||
|
.await
|
||||||
|
.is_err());
|
||||||
|
}
|
||||||
|
}
|
85
src/spsc.rs
Normal file
85
src/spsc.rs
Normal file
|
@ -0,0 +1,85 @@
|
||||||
|
use std::{
|
||||||
|
future::Future,
|
||||||
|
pin::Pin,
|
||||||
|
sync::{
|
||||||
|
atomic::{AtomicBool, Ordering::Relaxed},
|
||||||
|
Arc,
|
||||||
|
},
|
||||||
|
task::{Context, Poll, Waker},
|
||||||
|
};
|
||||||
|
|
||||||
|
use crate::{utils::try_ready, volmutex::VolMutex};
|
||||||
|
|
||||||
|
/// Notification receiver. Await it to wait for notification.
|
||||||
|
pub struct Slave(Arc<State>);
|
||||||
|
|
||||||
|
struct State {
|
||||||
|
should_wake_up: AtomicBool,
|
||||||
|
waker: VolMutex<Option<Waker>>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Future for Slave {
|
||||||
|
type Output = ();
|
||||||
|
|
||||||
|
fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
|
||||||
|
let state = &*self.get_mut().0;
|
||||||
|
if state.should_wake_up.swap(false, Relaxed) {
|
||||||
|
return Poll::Ready(());
|
||||||
|
}
|
||||||
|
|
||||||
|
{
|
||||||
|
let Some(mut locked) = state.waker.try_lock() else {
|
||||||
|
state.should_wake_up.store(false, Relaxed);
|
||||||
|
return Poll::Ready(());
|
||||||
|
};
|
||||||
|
|
||||||
|
let maybe_waker = &mut *locked;
|
||||||
|
if let Some(waker) = maybe_waker {
|
||||||
|
waker.clone_from(cx.waker());
|
||||||
|
} else {
|
||||||
|
*maybe_waker = Some(cx.waker().clone());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
try_ready(&state.should_wake_up)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct Master {
|
||||||
|
slave: Arc<State>,
|
||||||
|
stored_waker: Option<Waker>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Master {
|
||||||
|
/// Notify waiter. If [`Slave`] wasn't listening to the notification right now,
|
||||||
|
/// next await would return immediately. In tokio terms, wake-up permit is stored,
|
||||||
|
/// only one wake-up permit is kept.
|
||||||
|
pub fn notify(&mut self) {
|
||||||
|
self.slave.should_wake_up.store(true, Relaxed);
|
||||||
|
{
|
||||||
|
let Some(locked) = self.slave.waker.try_lock() else {
|
||||||
|
return;
|
||||||
|
};
|
||||||
|
self.stored_waker.clone_from(&*locked);
|
||||||
|
}
|
||||||
|
|
||||||
|
if let Some(waker) = &self.stored_waker {
|
||||||
|
waker.wake_by_ref();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Make master/slave pair.
|
||||||
|
pub fn make() -> (Master, Slave) {
|
||||||
|
let state = Arc::new(State {
|
||||||
|
should_wake_up: AtomicBool::new(false),
|
||||||
|
waker: VolMutex::new(None),
|
||||||
|
});
|
||||||
|
let master = Master {
|
||||||
|
slave: Arc::clone(&state),
|
||||||
|
stored_waker: None,
|
||||||
|
};
|
||||||
|
let slave = Slave(state);
|
||||||
|
|
||||||
|
(master, slave)
|
||||||
|
}
|
13
src/utils.rs
Normal file
13
src/utils.rs
Normal file
|
@ -0,0 +1,13 @@
|
||||||
|
use std::{
|
||||||
|
sync::atomic::{AtomicBool, Ordering::Relaxed},
|
||||||
|
task::Poll,
|
||||||
|
};
|
||||||
|
|
||||||
|
#[inline]
|
||||||
|
pub fn try_ready(flag: &AtomicBool) -> Poll<()> {
|
||||||
|
if flag.swap(false, Relaxed) {
|
||||||
|
Poll::Ready(())
|
||||||
|
} else {
|
||||||
|
Poll::Pending
|
||||||
|
}
|
||||||
|
}
|
83
src/volmutex.rs
Normal file
83
src/volmutex.rs
Normal file
|
@ -0,0 +1,83 @@
|
||||||
|
//! # Volunary mutex
|
||||||
|
//!
|
||||||
|
//! Simple mutex which can be acquired only in non-waiting manner.
|
||||||
|
|
||||||
|
use std::{
|
||||||
|
cell::UnsafeCell,
|
||||||
|
ops::{Deref, DerefMut},
|
||||||
|
sync::atomic::{
|
||||||
|
AtomicBool,
|
||||||
|
Ordering::{Acquire, Relaxed, Release},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
pub struct VolMutex<T> {
|
||||||
|
mutex: RawVolMutex,
|
||||||
|
data: UnsafeCell<T>,
|
||||||
|
}
|
||||||
|
|
||||||
|
unsafe impl<T: Send> Send for VolMutex<T> {}
|
||||||
|
unsafe impl<T: Send> Sync for VolMutex<T> {}
|
||||||
|
|
||||||
|
impl<T> VolMutex<T> {
|
||||||
|
pub const fn new(data: T) -> Self {
|
||||||
|
Self {
|
||||||
|
mutex: RawVolMutex::new(),
|
||||||
|
data: UnsafeCell::new(data),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn try_lock(&self) -> Option<VolGuard<'_, T>> {
|
||||||
|
self.mutex.try_lock().map(|guard| VolGuard {
|
||||||
|
_guard: guard,
|
||||||
|
data: &self.data,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct VolGuard<'a, T> {
|
||||||
|
_guard: RawVolGuard<'a>,
|
||||||
|
data: &'a UnsafeCell<T>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<T> DerefMut for VolGuard<'_, T> {
|
||||||
|
fn deref_mut(&mut self) -> &mut Self::Target {
|
||||||
|
unsafe { &mut *self.data.get() }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<T> Deref for VolGuard<'_, T> {
|
||||||
|
type Target = T;
|
||||||
|
|
||||||
|
fn deref(&self) -> &Self::Target {
|
||||||
|
unsafe { &*self.data.get() }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Default)]
|
||||||
|
pub struct RawVolMutex {
|
||||||
|
flag: AtomicBool,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl RawVolMutex {
|
||||||
|
pub const fn new() -> Self {
|
||||||
|
Self {
|
||||||
|
flag: AtomicBool::new(false),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn try_lock(&self) -> Option<RawVolGuard<'_>> {
|
||||||
|
self.flag
|
||||||
|
.compare_exchange(false, true, Acquire, Relaxed)
|
||||||
|
.is_ok()
|
||||||
|
.then_some(RawVolGuard(&self.flag))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct RawVolGuard<'a>(&'a AtomicBool);
|
||||||
|
|
||||||
|
impl Drop for RawVolGuard<'_> {
|
||||||
|
fn drop(&mut self) {
|
||||||
|
self.0.store(false, Release);
|
||||||
|
}
|
||||||
|
}
|
Loading…
Add table
Add a link
Reference in a new issue