use std::io::{self, Read, Write};
use std::os::fd::BorrowedFd;
use std::os::unix::net::UnixStream;
use std::sync::Arc;
use std::sync::atomic::{AtomicBool, Ordering};
use std::time::{Duration, Instant};
use rustix::event::{PollFd, PollFlags, Timespec, poll};
use crate::SdkError;
#[derive(Clone, Debug)]
pub struct ReadWake {
inner: Arc<Pipe>,
}
#[derive(Debug)]
struct Pipe {
read: UnixStream,
write: UnixStream,
pending: AtomicBool,
#[cfg(test)]
probe: Probe,
}
pub(super) enum Ready {
Socket,
TimedOut,
}
impl ReadWake {
pub(super) fn new() -> Result<Self, SdkError> {
let (read, write) = UnixStream::pair().map_err(|error| connection_error(&error))?;
read.set_nonblocking(true)
.map_err(|error| connection_error(&error))?;
write
.set_nonblocking(true)
.map_err(|error| connection_error(&error))?;
Ok(Self {
inner: Arc::new(Pipe {
read,
write,
pending: AtomicBool::new(false),
#[cfg(test)]
probe: Probe::default(),
}),
})
}
pub fn wake(&self) -> Result<(), SdkError> {
if self.inner.pending.swap(true, Ordering::AcqRel) {
return Ok(());
}
match (&self.inner.write).write(&[1]) {
Ok(1) => Ok(()),
Err(error) if error.kind() == io::ErrorKind::WouldBlock => Ok(()),
Ok(_) => {
self.inner.pending.store(false, Ordering::Release);
Err(connection_error(&io::Error::new(
io::ErrorKind::WriteZero,
"empty wake write",
)))
}
Err(error) => {
self.inner.pending.store(false, Ordering::Release);
Err(connection_error(&error))
}
}
}
pub(super) fn wait(&self, socket: BorrowedFd<'_>, budget: Duration) -> Result<Ready, SdkError> {
let started = Instant::now();
loop {
let remaining = budget.saturating_sub(started.elapsed());
let timeout = Timespec::try_from(remaining).map_err(|error| SdkError::Connection {
description: format!("idle wake deadline cannot be represented: {error}"),
})?;
#[cfg(test)]
self.observe(WaitStep::Polling {
pending: self.inner.pending.load(Ordering::Acquire),
});
let mut descriptors = [
PollFd::new(&socket, PollFlags::IN),
PollFd::new(&self.inner.read, PollFlags::IN),
];
match poll(&mut descriptors, Some(&timeout)) {
Ok(0) => return Ok(Ready::TimedOut),
Err(rustix::io::Errno::INTR) => continue,
Err(error) => return Err(connection_error(&io::Error::from(error))),
Ok(_) => {}
}
if !descriptors[1].revents().is_empty() {
self.inner.pending.swap(false, Ordering::AcqRel);
#[cfg(test)]
self.observe(WaitStep::Claiming);
let mut byte = [0];
(&self.inner.read)
.read_exact(&mut byte)
.map_err(|error| connection_error(&error))?;
return Err(SdkError::ReadInterrupted);
}
if !descriptors[0].revents().is_empty() {
return Ok(Ready::Socket);
}
}
}
}
#[cfg(test)]
pub(super) enum WaitStep {
Polling { pending: bool },
Claiming,
}
#[cfg(test)]
type ProbeFn = Box<dyn FnMut(WaitStep, &ReadWake) + Send>;
#[cfg(test)]
#[derive(Default)]
struct Probe(std::sync::Mutex<Option<ProbeFn>>);
#[cfg(test)]
impl std::fmt::Debug for Probe {
fn fmt(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
formatter.write_str("Probe")
}
}
#[cfg(test)]
impl ReadWake {
pub(super) fn probe_waits(&self, probe: impl FnMut(WaitStep, &Self) + Send + 'static) {
if let Ok(mut installed) = self.inner.probe.0.lock() {
*installed = Some(Box::new(probe));
}
}
fn observe(&self, step: WaitStep) {
if let Ok(mut installed) = self.inner.probe.0.lock() {
if let Some(probe) = installed.as_mut() {
probe(step, self);
}
}
}
}
fn connection_error(error: &io::Error) -> SdkError {
SdkError::Connection {
description: format!("idle receive wake pipe: {error}"),
}
}