use core::sync::atomic::{AtomicBool, Ordering};
pub struct Block {
thread: std::thread::Thread,
wake: AtomicBool,
}
impl Block {
pub fn new() -> Self {
Self {
thread: std::thread::current(),
wake: AtomicBool::new(false),
}
}
pub fn split(&self) -> (Signal, Lock<'_>) {
(Signal(unsafe { core::mem::transmute(self) }), Lock(self))
}
}
pub struct Signal(&'static Block);
impl Signal {
pub fn signal(self) -> bool {
let wake = !self.0.wake.compare_and_swap(false, true, Ordering::SeqCst);
if wake {
self.0.thread.unpark();
}
wake
}
}
impl Drop for Signal {
fn drop(&mut self) {
assert!(self.0.wake.load(Ordering::Relaxed), "UNPARK BEFORE DROPPING!");
}
}
pub struct Lock<'a>(&'a Block);
impl Lock<'_> {
pub fn wait(self) {
debug_assert_eq!(self.0.thread.id(), std::thread::current().id());
while !self.0.wake.load(Ordering::SeqCst) {
std::thread::park();
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
#[should_panic]
fn blocking_panics_when_not_signaled() {
Block::new().split();
}
#[test]
fn blocking_ok_after_signal() {
let block = Block::new();
let (signal, lock) = block.split();
std::thread::spawn(move || {
signal.signal();
});
lock.wait();
}
}