Skip to main content

liminal_sdk/remote/
read_wake.rs

1//! An explicit wake for an idle socket read. It never closes the TCP socket.
2
3use std::io::{self, Read, Write};
4use std::os::fd::BorrowedFd;
5use std::os::unix::net::UnixStream;
6use std::sync::Arc;
7use std::sync::atomic::{AtomicBool, Ordering};
8use std::time::{Duration, Instant};
9
10use rustix::event::{PollFd, PollFlags, Timespec, poll};
11
12use crate::SdkError;
13
14/// Wakes a participant receive that is waiting on the socket with a budget.
15///
16/// # Which reads it interrupts
17///
18/// A wake ends, with [`SdkError::ReadInterrupted`], a read made through a
19/// budgeted participant door while it waits for bytes:
20/// `RemoteParticipantHandle::receive_within` with a nonzero budget, whatever
21/// the budget (including one at or above the 60 s response deadline that a
22/// caller uses to await a correlated answer), and the wait for the liveness
23/// `Pong` inside `RemoteParticipantHandle::try_receive`. A frame already
24/// decoded is returned without waiting and so without consuming the wake, and
25/// a zero budget never waits at all. The interrupted read keeps any
26/// partly-arrived frame and the socket; a `Pong` still owed to an interrupted
27/// `try_receive` is consumed by a later read.
28///
29/// The reply-owed door `RemoteParticipantHandle::receive`, and every
30/// request/response exchange the transport makes for itself (publish,
31/// subscribe, conversations, the handshake), never observe a wake. "Idle"
32/// here means the door the caller chose, not whether a reply is owed: a caller
33/// that awaits a reply through `receive_within` can be interrupted.
34///
35/// # Sharing
36///
37/// There is one `ReadWake` per transport, not per handle. Every clone of it,
38/// every `RemoteConfig` sharing the transport, and every participant built
39/// over that transport reach the same wake, and it carries over a participant
40/// reconnect. A wake from any of them interrupts whichever interruptible read
41/// on that transport observes it first, through whichever handle is reading;
42/// it is not addressed to the caller that sent it.
43///
44/// Wake requests coalesce: a wake sent while no interruptible read is waiting
45/// stays pending and interrupts the next one, and several wakes before that
46/// read are one interrupt. A wake that lands while a reader is claiming an
47/// earlier one is never lost; at worst it costs one spurious interrupt of a
48/// later read. The handle holds only the wake pipe, so retaining it does not
49/// retain the transport socket or participant binding.
50#[derive(Clone, Debug)]
51pub struct ReadWake {
52    inner: Arc<Pipe>,
53}
54
55#[derive(Debug)]
56struct Pipe {
57    read: UnixStream,
58    write: UnixStream,
59    pending: AtomicBool,
60    #[cfg(test)]
61    probe: Probe,
62}
63
64pub(super) enum Ready {
65    Socket,
66    TimedOut,
67}
68
69impl ReadWake {
70    pub(super) fn new() -> Result<Self, SdkError> {
71        let (read, write) = UnixStream::pair().map_err(|error| connection_error(&error))?;
72        read.set_nonblocking(true)
73            .map_err(|error| connection_error(&error))?;
74        write
75            .set_nonblocking(true)
76            .map_err(|error| connection_error(&error))?;
77        Ok(Self {
78            inner: Arc::new(Pipe {
79                read,
80                write,
81                pending: AtomicBool::new(false),
82                #[cfg(test)]
83                probe: Probe::default(),
84            }),
85        })
86    }
87
88    /// Wake the interruptible read waiting now on this transport, or the next
89    /// one to wait (see the type's documentation). This never waits for the
90    /// reader.
91    ///
92    /// # Errors
93    /// Returns a connection error if the local wake pipe cannot be written.
94    pub fn wake(&self) -> Result<(), SdkError> {
95        if self.inner.pending.swap(true, Ordering::AcqRel) {
96            return Ok(());
97        }
98        match (&self.inner.write).write(&[1]) {
99            Ok(1) => Ok(()),
100            Err(error) if error.kind() == io::ErrorKind::WouldBlock => Ok(()),
101            Ok(_) => {
102                self.inner.pending.store(false, Ordering::Release);
103                Err(connection_error(&io::Error::new(
104                    io::ErrorKind::WriteZero,
105                    "empty wake write",
106                )))
107            }
108            Err(error) => {
109                self.inner.pending.store(false, Ordering::Release);
110                Err(connection_error(&error))
111            }
112        }
113    }
114
115    pub(super) fn wait(&self, socket: BorrowedFd<'_>, budget: Duration) -> Result<Ready, SdkError> {
116        let started = Instant::now();
117        loop {
118            let remaining = budget.saturating_sub(started.elapsed());
119            let timeout = Timespec::try_from(remaining).map_err(|error| SdkError::Connection {
120                description: format!("idle wake deadline cannot be represented: {error}"),
121            })?;
122            #[cfg(test)]
123            self.observe(WaitStep::Polling {
124                pending: self.inner.pending.load(Ordering::Acquire),
125            });
126            let mut descriptors = [
127                PollFd::new(&socket, PollFlags::IN),
128                PollFd::new(&self.inner.read, PollFlags::IN),
129            ];
130            match poll(&mut descriptors, Some(&timeout)) {
131                Ok(0) => return Ok(Ready::TimedOut),
132                Err(rustix::io::Errno::INTR) => continue,
133                Err(error) => return Err(connection_error(&io::Error::from(error))),
134                Ok(_) => {}
135            }
136            if !descriptors[1].revents().is_empty() {
137                // Clear the flag BEFORE taking the byte. A wake landing between
138                // the two steps then finds it clear and writes a byte of its own,
139                // so the worst case is one spurious interrupt of a later receive;
140                // the other order absorbs that wake into this one and loses it.
141                self.inner.pending.swap(false, Ordering::AcqRel);
142                #[cfg(test)]
143                self.observe(WaitStep::Claiming);
144                let mut byte = [0];
145                (&self.inner.read)
146                    .read_exact(&mut byte)
147                    .map_err(|error| connection_error(&error))?;
148                return Err(SdkError::ReadInterrupted);
149            }
150            if !descriptors[0].revents().is_empty() {
151                return Ok(Ready::Socket);
152            }
153        }
154    }
155}
156
157/// Where a reader stands inside [`ReadWake::wait`], reported to a test probe.
158#[cfg(test)]
159pub(super) enum WaitStep {
160    /// About to block in `poll`; `pending` is the wake flag as it enters.
161    Polling { pending: bool },
162    /// Between the two steps that claim an observed wake.
163    Claiming,
164}
165
166#[cfg(test)]
167type ProbeFn = Box<dyn FnMut(WaitStep, &ReadWake) + Send>;
168
169/// A test's view into the wait, so a test proves where the reader stands
170/// instead of sleeping and hoping it got there.
171#[cfg(test)]
172#[derive(Default)]
173struct Probe(std::sync::Mutex<Option<ProbeFn>>);
174
175#[cfg(test)]
176impl std::fmt::Debug for Probe {
177    fn fmt(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
178        formatter.write_str("Probe")
179    }
180}
181
182#[cfg(test)]
183impl ReadWake {
184    /// Reports every step of every later wait on this pipe to `probe`, on the
185    /// reader's own thread.
186    pub(super) fn probe_waits(&self, probe: impl FnMut(WaitStep, &Self) + Send + 'static) {
187        if let Ok(mut installed) = self.inner.probe.0.lock() {
188            *installed = Some(Box::new(probe));
189        }
190    }
191
192    fn observe(&self, step: WaitStep) {
193        if let Ok(mut installed) = self.inner.probe.0.lock() {
194            if let Some(probe) = installed.as_mut() {
195                probe(step, self);
196            }
197        }
198    }
199}
200
201fn connection_error(error: &io::Error) -> SdkError {
202    SdkError::Connection {
203        description: format!("idle receive wake pipe: {error}"),
204    }
205}