use std::io::{self, PipeReader, PipeWriter, Read as _, Write as _};
use std::os::fd::{AsRawFd as _, RawFd};
use std::sync::atomic::{AtomicBool, Ordering};
use crate::waker::WakeSignal;
#[derive(Debug)]
pub struct WakePipe {
reader: PipeReader,
writer: PipeWriter,
rung: AtomicBool,
}
impl WakePipe {
pub fn new() -> io::Result<Self> {
let (reader, writer) = io::pipe()?;
Ok(Self {
reader,
writer,
rung: AtomicBool::new(false),
})
}
pub fn fd(&self) -> RawFd {
self.reader.as_raw_fd()
}
pub fn clear(&self) {
if self.rung.swap(false, Ordering::AcqRel) {
let mut byte = [0];
if let Err(error) = (&self.reader).read_exact(&mut byte) {
crcbl_core::log::warn!("the shell's wake pipe could not be read: {error}");
}
}
}
}
impl WakeSignal for WakePipe {
fn wake(&self) {
if self.rung.swap(true, Ordering::AcqRel) {
return;
}
if let Err(error) = (&self.writer).write_all(&[1]) {
self.rung.store(false, Ordering::Release);
crcbl_core::log::warn!("the shell's wake pipe could not be written: {error}");
}
}
}
#[cfg(test)]
mod tests {
use super::WakePipe;
use crate::waker::WakeSignal as _;
fn readable(fd: std::os::fd::RawFd) -> bool {
crate::x11::ffi::poll_readable(fd, 0)
}
#[test]
fn a_ring_makes_the_pipe_readable_and_a_clear_empties_it() {
let bell = WakePipe::new().expect("a pipe");
assert!(!readable(bell.fd()), "a new bell is silent");
bell.wake();
assert!(readable(bell.fd()), "a ring is a readable byte");
bell.clear();
assert!(!readable(bell.fd()), "a clear takes the byte");
}
#[test]
fn rings_coalesce_into_one_byte() {
let bell = WakePipe::new().expect("a pipe");
for _ in 0..1000 {
bell.wake();
}
bell.clear();
assert!(
!readable(bell.fd()),
"a thousand rings left more than one byte"
);
bell.clear();
}
#[test]
fn a_ring_from_another_thread_wakes_a_poll() {
let bell = std::sync::Arc::new(WakePipe::new().expect("a pipe"));
let ringer = std::sync::Arc::clone(&bell);
let thread = std::thread::spawn(move || {
std::thread::sleep(std::time::Duration::from_millis(50));
ringer.wake();
});
assert!(
crate::x11::ffi::poll_readable(bell.fd(), 60_000),
"the ring did not wake the poll"
);
thread.join().expect("the ringer");
bell.clear();
}
}