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
|
||||
|
||||
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