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            // `Duration::MAX` is no bound: the poll waits for the socket or
119            // the wake alone (SDK-023 R2).
120            let timeout = if budget == Duration::MAX {
121                None
122            } else {
123                let remaining = budget.saturating_sub(started.elapsed());
124                Some(
125                    Timespec::try_from(remaining).map_err(|error| SdkError::Connection {
126                        description: format!("idle wake deadline cannot be represented: {error}"),
127                    })?,
128                )
129            };
130            #[cfg(test)]
131            self.observe(WaitStep::Polling {
132                pending: self.inner.pending.load(Ordering::Acquire),
133            });
134            let mut descriptors = [
135                PollFd::new(&socket, PollFlags::IN),
136                PollFd::new(&self.inner.read, PollFlags::IN),
137            ];
138            match poll(&mut descriptors, timeout.as_ref()) {
139                Ok(0) => return Ok(Ready::TimedOut),
140                Err(rustix::io::Errno::INTR) => continue,
141                Err(error) => return Err(connection_error(&io::Error::from(error))),
142                Ok(_) => {}
143            }
144            if !descriptors[1].revents().is_empty() {
145                // Clear the flag BEFORE taking the byte. A wake landing between
146                // the two steps then finds it clear and writes a byte of its own,
147                // so the worst case is one spurious interrupt of a later receive;
148                // the other order absorbs that wake into this one and loses it.
149                self.inner.pending.swap(false, Ordering::AcqRel);
150                #[cfg(test)]
151                self.observe(WaitStep::Claiming);
152                let mut byte = [0];
153                (&self.inner.read)
154                    .read_exact(&mut byte)
155                    .map_err(|error| connection_error(&error))?;
156                return Err(SdkError::ReadInterrupted);
157            }
158            if !descriptors[0].revents().is_empty() {
159                return Ok(Ready::Socket);
160            }
161        }
162    }
163}
164
165/// Where a reader stands inside [`ReadWake::wait`], reported to a test probe.
166#[cfg(test)]
167pub(super) enum WaitStep {
168    /// About to block in `poll`; `pending` is the wake flag as it enters.
169    Polling { pending: bool },
170    /// Between the two steps that claim an observed wake.
171    Claiming,
172}
173
174#[cfg(test)]
175type ProbeFn = Box<dyn FnMut(WaitStep, &ReadWake) + Send>;
176
177/// A test's view into the wait, so a test proves where the reader stands
178/// instead of sleeping and hoping it got there.
179#[cfg(test)]
180#[derive(Default)]
181struct Probe(std::sync::Mutex<Option<ProbeFn>>);
182
183#[cfg(test)]
184impl std::fmt::Debug for Probe {
185    fn fmt(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
186        formatter.write_str("Probe")
187    }
188}
189
190#[cfg(test)]
191impl ReadWake {
192    /// Reports every step of every later wait on this pipe to `probe`, on the
193    /// reader's own thread.
194    pub(super) fn probe_waits(&self, probe: impl FnMut(WaitStep, &Self) + Send + 'static) {
195        if let Ok(mut installed) = self.inner.probe.0.lock() {
196            *installed = Some(Box::new(probe));
197        }
198    }
199
200    fn observe(&self, step: WaitStep) {
201        if let Ok(mut installed) = self.inner.probe.0.lock() {
202            if let Some(probe) = installed.as_mut() {
203                probe(step, self);
204            }
205        }
206    }
207}
208
209fn connection_error(error: &io::Error) -> SdkError {
210    SdkError::Connection {
211        description: format!("idle receive wake pipe: {error}"),
212    }
213}