use std::{
fmt,
sync::{
Arc,
atomic::{AtomicU8, Ordering},
},
};
use event_listener::{Event as EventLib, IntoNotification};
const WAIT_ERR_STR: &str = "No notifier available";
pub struct WaitError;
impl fmt::Display for WaitError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{self:?}")
}
}
impl fmt::Debug for WaitError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.write_str(WAIT_ERR_STR)
}
}
impl std::error::Error for WaitError {}
const NOTIFY_ERR_STR: &str = "No waiter available";
pub struct NotifyError;
impl fmt::Display for NotifyError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{self:?}")
}
}
impl fmt::Debug for NotifyError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.write_str(NOTIFY_ERR_STR)
}
}
impl std::error::Error for NotifyError {}
struct EventInner {
event: EventLib,
flag: AtomicU8,
}
const UNSET: u8 = 0;
const OK: u8 = 1 << 0;
const ERR: u8 = 1 << 1;
#[repr(u8)]
enum EventCheck {
Unset = UNSET,
Ok = OK,
Err = ERR,
}
#[repr(u8)]
enum EventSet {
Ok = OK,
Err = ERR,
}
impl EventInner {
fn check(&self) -> EventCheck {
let f = self.flag.fetch_and(!OK, Ordering::AcqRel);
if f & ERR != 0 {
return EventCheck::Err;
}
if f == OK {
return EventCheck::Ok;
}
EventCheck::Unset
}
fn set(&self) -> EventSet {
let f = self.flag.fetch_or(OK, Ordering::AcqRel);
if f & ERR != 0 {
return EventSet::Err;
}
EventSet::Ok
}
fn err(&self) {
self.flag.store(ERR, Ordering::Release);
self.event.notify(1);
}
}
pub(crate) fn new() -> (Notifier, Waiter) {
let inner = Arc::new(EventInner {
event: EventLib::new(),
flag: AtomicU8::new(UNSET),
});
(Notifier(inner.clone()), Waiter(inner))
}
#[repr(transparent)]
pub(crate) struct Notifier(Arc<EventInner>);
impl Notifier {
#[inline]
pub(crate) fn notify(&self) -> Result<(), NotifyError> {
match self.0.set() {
EventSet::Ok => {
self.0.event.notify(1.additional().relaxed());
Ok(())
}
EventSet::Err => Err(NotifyError),
}
}
}
impl Drop for Notifier {
fn drop(&mut self) {
self.0.err();
}
}
#[repr(transparent)]
pub struct Waiter(Arc<EventInner>);
impl Waiter {
#[inline]
pub(crate) async fn wait(&self) -> Result<(), WaitError> {
loop {
match self.0.check() {
EventCheck::Ok => return Ok(()),
EventCheck::Unset => {}
EventCheck::Err => return Err(WaitError),
}
let listener = self.0.event.listen();
match self.0.check() {
EventCheck::Ok => return Ok(()),
EventCheck::Unset => {}
EventCheck::Err => return Err(WaitError),
}
listener.await;
}
}
}
impl Drop for Waiter {
fn drop(&mut self) {
self.0.err();
}
}
#[cfg(test)]
mod tests {
use std::time::Duration;
use super::*;
#[test]
fn test_basic_notify_wait() {
let (notifier, waiter) = new();
let t = std::thread::spawn(move || {
std::thread::sleep(Duration::from_millis(10));
notifier.notify().unwrap();
notifier
});
pollster::block_on(waiter.wait()).unwrap();
drop(t.join().unwrap()); }
#[test]
fn test_notify_before_wait() {
let (notifier, waiter) = new();
notifier.notify().unwrap();
pollster::block_on(waiter.wait()).unwrap();
}
#[test]
fn test_drop_all_notifiers() {
let (notifier, waiter) = new();
let t = std::thread::spawn(move || {
std::thread::sleep(Duration::from_millis(10));
drop(notifier);
});
let result = pollster::block_on(waiter.wait());
assert!(result.is_err());
t.join().unwrap();
}
#[test]
fn test_drop_all_waiters() {
let (notifier, waiter) = new();
drop(waiter);
let result = notifier.notify();
assert!(result.is_err());
}
#[test]
fn test_notification_preserved() {
let (notifier, waiter) = new();
notifier.notify().unwrap();
pollster::block_on(waiter.wait()).unwrap();
}
}