use parking_lot::Mutex;
use std::{fmt::Debug, sync::Arc};
use tokio::sync::watch;
#[derive(Clone)]
pub struct Signal<T> {
data: Arc<Mutex<T>>,
is_closed: Arc<Mutex<bool>>,
closed: watch::Receiver<bool>,
tx: watch::Sender<bool>,
}
impl<T: Clone> Signal<T> {
pub fn new(v: T) -> Self {
let (tx, closed) = watch::channel(false);
Self {
data: Arc::new(Mutex::new(v)),
is_closed: Arc::new(Mutex::new(false)),
closed,
tx,
}
}
pub fn send(&self, v: T) {
{
let mut data = self.data.lock();
let mut is_closed = self.is_closed.lock();
if *is_closed {
return;
}
*data = v;
*is_closed = true;
}
let _ = self.tx.send(true);
}
pub fn close(&self) {
{
let mut is_closed = self.is_closed.lock();
if *is_closed {
return;
}
*is_closed = true;
}
let _ = self.tx.send(true);
}
pub fn data(&self) -> T {
let data = self.data.lock();
data.clone()
}
pub fn update<F: Fn(&mut T)>(&self, f: F)
where
T: Debug,
{
let mut data = self.data.lock();
if *self.is_closed.lock() {
return;
}
f(&mut data);
}
pub fn double(&self) -> (Self, Self) {
(self.clone(), self.clone())
}
pub fn triple(&self) -> (Self, Self, Self) {
(self.clone(), self.clone(), self.clone())
}
pub async fn recv(&self) -> T {
loop {
let mut closed = self.closed.clone();
if *self.is_closed.lock() {
return self.data();
}
match closed.changed().await {
Ok(()) => continue,
Err(_) => return self.data(),
}
}
}
pub async fn timeout(&self, millis: u64) -> T {
tokio::time::sleep(tokio::time::Duration::from_millis(millis)).await;
self.data()
}
}
#[cfg(test)]
mod tests {
use crate::Signal;
use serial_test::serial;
use std::{sync::Arc, time::Duration};
use tokio::sync::Barrier;
use tokio::time::timeout;
fn spawn_waiters<T>(
sig: &Signal<T>,
n: usize,
) -> (Arc<Barrier>, Vec<tokio::task::JoinHandle<T>>)
where
T: Clone + Send + 'static,
{
let barrier = Arc::new(Barrier::new(n + 1));
let mut handles = Vec::with_capacity(n);
for _ in 0..n {
let sig = sig.clone();
let barrier = barrier.clone();
handles.push(tokio::spawn(async move {
barrier.wait().await;
sig.recv().await
}));
}
(barrier, handles)
}
async fn collect<T>(handles: Vec<tokio::task::JoinHandle<T>>) -> Vec<T> {
let mut rets = Vec::with_capacity(handles.len());
for h in handles {
rets.push(h.await.unwrap());
}
rets
}
#[test]
fn engine_signal_new() {
let s = Signal::new(5);
assert_eq!(s.data(), 5);
let s = Signal::new("abc");
assert_eq!(s.data(), "abc");
}
#[tokio::test(flavor = "multi_thread")]
#[serial]
async fn engine_signal_send() {
let s = Signal::new(0);
let s2 = s.clone();
tokio::spawn(async move {
s.send(10);
});
let ret = s2.recv().await;
assert_eq!(ret, 10);
}
#[tokio::test(flavor = "multi_thread")]
#[serial]
async fn engine_signal_close() {
let s = Signal::new(0);
let s2 = s.clone();
tokio::spawn(async move {
s.close();
});
let ret = s2.recv().await;
assert_eq!(ret, 0);
}
#[tokio::test(flavor = "multi_thread")]
#[serial]
async fn engine_signal_update() {
let s = Signal::new(0);
let s2 = s.clone();
tokio::spawn(async move {
s.update(|data| *data = 100);
s.close();
});
let ret = s2.recv().await;
assert_eq!(ret, 100);
}
#[tokio::test(flavor = "multi_thread")]
#[serial]
async fn engine_signal_timeout() {
let s = Signal::new(0);
let s2 = s.clone();
tokio::spawn(async move {
s.update(|data| *data = 100);
});
let ret = s2.timeout(10).await;
assert_eq!(ret, 100);
}
#[tokio::test(flavor = "multi_thread")]
#[serial]
async fn engine_signal_double() {
let (s1, s2) = Signal::new(0).double();
tokio::spawn(async move {
s1.send(10);
});
let ret = s2.recv().await;
assert_eq!(ret, 10);
}
#[tokio::test(flavor = "multi_thread")]
#[serial]
async fn engine_signal_triple() {
let (s1, s2, s3) = Signal::new(0).triple();
tokio::spawn(async move {
s1.update(|data| *data = 100);
s2.close();
});
let ret = s3.recv().await;
assert_eq!(ret, 100);
}
#[tokio::test(flavor = "multi_thread")]
#[serial]
async fn engine_signal_update_close_reentrant() {
let sig = Signal::new(Vec::new());
let s2 = sig.clone();
let sig2 = sig.clone();
tokio::spawn(async move {
s2.update(|data| {
data.push(1);
data.push(2);
s2.close();
});
});
let ret = timeout(Duration::from_secs(2), sig2.recv())
.await
.expect("close inside update must not deadlock");
assert_eq!(ret, vec![1, 2]);
}
#[tokio::test(flavor = "multi_thread")]
#[serial]
async fn engine_signal_receiver_send_broadcast() {
let sig = Signal::new(0);
let (barrier, handles) = spawn_waiters(&sig, 8);
barrier.wait().await;
sig.send(10);
let rets = timeout(Duration::from_secs(2), collect(handles))
.await
.expect("send must release every waiting receiver");
assert_eq!(rets, vec![10; 8]);
}
#[tokio::test(flavor = "multi_thread")]
#[serial]
async fn engine_signal_receiver_close_broadcast() {
let sig = Signal::new(3);
let (barrier, handles) = spawn_waiters(&sig, 8);
barrier.wait().await;
sig.close();
let rets = timeout(Duration::from_secs(2), collect(handles))
.await
.expect("close must release every waiting receiver");
assert_eq!(rets, vec![3; 8]);
}
#[tokio::test(flavor = "multi_thread")]
#[serial]
async fn engine_signal_receiver_update_close_broadcast() {
let sig = Signal::new(0);
sig.update(|v| *v = 100);
let (barrier, handles) = spawn_waiters(&sig, 8);
barrier.wait().await;
sig.close();
let rets = timeout(Duration::from_secs(2), collect(handles))
.await
.expect("close after update must release every waiting receiver");
assert_eq!(rets, vec![100; 8]);
}
#[tokio::test(flavor = "multi_thread")]
#[serial]
async fn engine_signal_receiver_late_join() {
let sig = Signal::new(0);
sig.send(7);
let handles: Vec<_> = (0..8)
.map(|_| {
let sig = sig.clone();
tokio::spawn(async move { sig.recv().await })
})
.collect();
let rets = timeout(Duration::from_secs(2), collect(handles))
.await
.expect("recv after send must return immediately");
assert_eq!(rets, vec![7; 8]);
}
}