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}