epics_libcom_rs/runtime/readiness/mod.rs
1//! Socket readiness for the targets with no mio selector — a poll thread, an
2//! armed set, and an `AsyncRead`/`AsyncWrite` adapter over a non-blocking fd.
3//!
4//! # Why this exists at all
5//!
6//! `mio` has no selector for either embedded triple. Measured against
7//! mio 1.2.3 with `-Zbuild-std`: `armv7-rtems-eabihf` and `x86_64-wrs-vxworks`
8//! both fail at `error[E0583]: file not found for module 'selector'`, because
9//! `mio/src/sys/unix/mod.rs` maps `target_os` to a selector file and neither
10//! target appears in any of its lists. Forcing mio's own escape hatches
11//! (`mio_unsupported_force_poll_poll`, `mio_unsupported_force_waker_pipe`)
12//! gets the two down to 8 and 9 errors respectively, all of them missing
13//! `libc` items (`accept4`, `SOCK_NONBLOCK`, `POLLRDNORM`, `sin_len`) — so the
14//! path through mio is a set of PRs across three upstream crates, not a build
15//! flag. Until those land this module is the readiness layer.
16//!
17//! It is deliberately **not** a mio clone. It carries exactly what a PVA
18//! server connection needs: level-triggered read/write readiness on sockets,
19//! one waker per direction, and nothing else — no edge triggering, no
20//! `Interest` bitsets, no cross-thread registration of foreign event sources.
21//!
22//! # The armed set is owned by the poll thread, and nothing else
23//!
24//! > **MUST** an armed interest is one-shot: the poll thread removes
25//! > `(fd, direction)` from the armed set *before* it wakes that direction's
26//! > waker, and only the task that then observed `WouldBlock` may arm it
27//! > again.
28//! > **MUST NOT** any path remove an fd from the armed set other than
29//! > `StreamInner`'s `Drop` and the one-shot removal above — and no fd may
30//! > be closed while the kernel is still watching it, which is why the poll
31//! > thread is the one that closes it.
32//!
33//! The first half is what keeps the poll thread from spinning: a
34//! level-triggered backend reports a readable socket on every wait until the
35//! bytes are consumed, so an interest that survived its own wake-up would
36//! re-report immediately and burn the CPU that a reactor exists to save. The
37//! second half is what keeps it correct: an fd left in a `select` set past
38//! `close(2)` is `EBADF` from the next call — at entry on Linux, on every
39//! rescan under the BSD `kern_select` libbsd carries — and a number the kernel
40//! may since have handed to a different socket. So dropping the last handle to
41//! a stream does not close its socket. It hands the socket to the poll thread,
42//! which closes it in the same `wait` that takes the fd out of the kernel's
43//! sets — after the removal and before it blocks — and a socket the kernel was
44//! never told about is closed on the spot, there being nothing to remove
45//! first. The order holds by construction rather than by convention: nothing
46//! else holds the socket once the last handle is gone, and the delete and the
47//! close travel in one changelist.
48//!
49//! One-shot arming is also what makes registering *after* a `WouldBlock` race-
50//! free. Between the failed read and the arm, the peer may have sent the byte
51//! that would have woken us; a level-triggered backend still reports the
52//! socket ready on the very next wait, so the wake-up cannot be lost. An
53//! edge-triggered backend would need the arm to precede the read attempt, and
54//! that is the ordering this module does not use. `EV_CLEAR` is therefore
55//! banned for socket interests in the kqueue backend; the one place it appears
56//! there is the `EVFILT_USER` wake, which carries no readiness to lose.
57//!
58//! # Waiting is (changes in, ready set out)
59//!
60//! This is libevent's `eventop` split — `add` and `del` accumulate, `dispatch`
61//! submits — which is what pvxs runs under on every platform it supports, and
62//! what this module now follows. `Backend::wait` takes the changes since the
63//! last call rather than the armed set, so a registration placed once stays in
64//! the kernel until this module deletes it and a wait with nothing to change
65//! submits an empty changelist. The cost of a wait is then the number of
66//! *transitions*, where the whole-set resubmission it replaces charged the
67//! number of armed fds on every call whether or not any of them had moved. On
68//! `armv7-rtems-eabihf` that is worth 3.7x to `kqueue` at 112 held
69//! connections and nothing at all to `select`, which re-registers every
70//! watched fd inside the kernel on every call because `select(2)` has nowhere
71//! to remember one between calls — a cost no changelist can reach. The kqueue
72//! backend's doc carries the table.
73//!
74//! The one-shot rule above is what produces most of those transitions:
75//! delivering `(fd, direction)` queues that direction's delete, and the task's
76//! re-arm after its next `WouldBlock` queues the add. Two changes per
77//! readiness event, and on a busy connection the only two.
78//!
79//! `Change`es accumulate under the same lock as the armed set, so the kernel
80//! hears about a transition in the order the armed set recorded it. That is
81//! what makes the awkward case — an fd closed and handed straight back out to
82//! the next connection — fall out of the design rather than need handling, and
83//! it is why both of libevent's `changelist` rules come along:
84//!
85//! - An add over a pending delete stays an add and never collapses to a
86//! no-op, because that delete may name an fd that has since closed and come
87//! back as a different socket under the same number.
88//! - A delete of an interest the kernel was never told about *is* a no-op.
89//! `Direction::registered` is what tells the two apart, and dropping the
90//! pair is what keeps kqueue from reporting a spurious event for an
91//! add-then-delete that never left this process.
92//!
93//! A delete that misses in the kernel is ordinary rather than exceptional on
94//! `kqueue`: that backend closes the socket before the `kevent` call carrying
95//! the delete, because the same call also blocks and a close after it would
96//! wait for the next wake-up; `close(2)` drops the knotes itself, so the
97//! delete then finds nothing, and the backend ignores that. `select` closes
98//! after clearing the bits and before blocking, which is the order its
99//! kernel-side scan needs.
100//!
101//! New registrations arriving while the thread is blocked in the syscall are
102//! what `Backend::interrupt` is for; it is the only reason this module needs
103//! a self-pipe (`select`) or a user event (`kqueue`).
104
105// RTEMS-EXEC-MODEL-ALLOW(7): the seven poller tests park a `tokio::spawn`ed
106// task on a `ReadyStream` half, so the property under test is that the poller
107// wakes a tokio task — the tokio flavor is the subject, not an accident. They
108// run on the driver `#[tokio::test]` builds and pass in the exec-backend suite
109// (measured: `EPICS_RS_BUILD_EXEC_BACKEND=thread cargo nextest run
110// -p epics-libcom-rs -E 'test(/runtime::readiness::/)'`, 22/22).
111
112use std::collections::HashMap;
113use std::io;
114use std::mem::ManuallyDrop;
115use std::net::TcpStream;
116use std::os::fd::{AsRawFd, RawFd};
117use std::sync::atomic::{AtomicBool, Ordering};
118use std::sync::{Arc, Mutex};
119use std::task::Waker;
120
121#[cfg(target_os = "rtems")]
122mod kqueue;
123mod select;
124
125/// Which backend a poller waits on, decided once at construction.
126///
127/// Both arms are compiled on RTEMS rather than `cfg`-ed apart, so a type error
128/// in either fails the RTEMS gate; the choice between them is a runtime one
129/// because the fact it turns on — the BSP's own RTEMS version — is only
130/// knowable at runtime. See `rtems_kqueue_usable`.
131enum ActiveBackend {
132 Select(select::SelectBackend),
133 #[cfg(target_os = "rtems")]
134 Kqueue(kqueue::KqueueBackend),
135}
136
137impl ActiveBackend {
138 /// Choose, open, and say which — on RTEMS, where there is a choice.
139 ///
140 /// The line is an `eprintln!` from the construction path for the same
141 /// reason [`report_lock_protocol`](crate::runtime::sync::report_lock_protocol)
142 /// is one: the RTEMS target has no iocsh to ask afterwards, and a
143 /// diagnostic that reports which guarantee the process got must not
144 /// itself depend on a `tracing` subscriber being installed and unfiltered.
145 /// It is printed after the backend opened, so it never names one that
146 /// failed.
147 ///
148 /// Reporting the *reason* beside the choice is what makes the line
149 /// actionable — "select (RTEMS 6.0, EPICS_RTEMS_KQUEUE unset)" names the
150 /// variable to set — and [`rtems_kqueue_usable`] returns both from one
151 /// call so the reason cannot drift from the rule that was applied.
152 #[cfg(target_os = "rtems")]
153 fn new(poller: &str) -> io::Result<Self> {
154 let (usable, why) = rtems_kqueue_usable();
155 let (backend, name) = if usable {
156 (Self::Kqueue(kqueue::KqueueBackend::new()?), "kqueue")
157 } else {
158 (Self::Select(select::SelectBackend::new()?), "select")
159 };
160 eprintln!("epics-rs: readiness poller {poller}: {name} backend ({why})");
161 Ok(backend)
162 }
163
164 /// No choice to report: `select` is the only backend off RTEMS.
165 #[cfg(not(target_os = "rtems"))]
166 fn new(_poller: &str) -> io::Result<Self> {
167 Ok(Self::Select(select::SelectBackend::new()?))
168 }
169}
170
171impl Backend for ActiveBackend {
172 fn wait(
173 &self,
174 changes: &[Change],
175 closing: &mut Vec<TcpStream>,
176 ready: &mut Vec<(RawFd, Interest)>,
177 ) -> io::Result<()> {
178 match self {
179 Self::Select(b) => b.wait(changes, closing, ready),
180 #[cfg(target_os = "rtems")]
181 Self::Kqueue(b) => b.wait(changes, closing, ready),
182 }
183 }
184
185 fn interrupt(&self) -> io::Result<()> {
186 match self {
187 Self::Select(b) => b.interrupt(),
188 #[cfg(target_os = "rtems")]
189 Self::Kqueue(b) => b.interrupt(),
190 }
191 }
192}
193
194#[cfg(target_os = "rtems")]
195unsafe extern "C" {
196 /// `rtems/version.h:90,97`. Defined in `librtemscpu.a` on both prefixes
197 /// `scripts/rtems-bsp.sh` builds (`nm --defined-only`: `T
198 /// rtems_version_major`), and absent from `libc`.
199 fn rtems_version_major() -> libc::c_int;
200 fn rtems_version_minor() -> libc::c_int;
201}
202
203/// pvxs's rule, applied to the BSP this image actually booted on.
204///
205/// pvxs gates kqueue at RTEMS 6.3 (`> 6.2`) — epics-base/pvxs#197 — because
206/// the RTEMS 5-era workaround below that version steers libevent onto a `poll`
207/// backend that never blocks on this BSP. The version is read at runtime, not
208/// at build time, because a branch build reports its series' development
209/// version (7.0.0 on main, 6.0.0 on the 6 branch) and only the running system
210/// knows which one it is.
211///
212/// `EPICS_RTEMS_KQUEUE` overrides the rule in both directions. A series-6
213/// prefix built by `scripts/rtems-bsp.sh` carries the libbsd and kernel fixes
214/// the rule is a proxy for — the script asserts they are in the tree it builds
215/// — yet reports 6.0.0, so that prefix's `epics-rs-env.sh` sets
216/// `EPICS_RTEMS_KQUEUE=1` for exactly that case. It is read here in the
217/// *target* process, so what carries it is the boot command line
218/// (`epics_rtems_boot::boot_args`); `scripts/embedded-image.sh` bakes the
219/// build environment's value into the image for that reason.
220///
221/// Returns the reason beside the answer so the line
222/// [`ActiveBackend::new`] prints names the rule that was actually applied.
223#[cfg(target_os = "rtems")]
224fn rtems_kqueue_usable() -> (bool, String) {
225 if let Some(raw) = super::env::get("EPICS_RTEMS_KQUEUE") {
226 if let Some(forced) = parse_override(&raw) {
227 return (forced, format!("EPICS_RTEMS_KQUEUE={}", raw.trim()));
228 }
229 // Not a third answer (see `parse_override`), but the operator set
230 // something and is owed the reason it did nothing.
231 eprintln!(
232 "epics-rs: EPICS_RTEMS_KQUEUE={} is neither 1/yes/true nor 0/no/false; \
233 the RTEMS version rule decides",
234 raw.trim()
235 );
236 }
237 // SAFETY: both are argument-free integer returns from librtemscpu.
238 let (major, minor) = unsafe { (rtems_version_major(), rtems_version_minor()) };
239 let usable = major > 6 || (major == 6 && minor > 2);
240 (
241 usable,
242 format!("RTEMS {major}.{minor}, EPICS_RTEMS_KQUEUE unset"),
243 )
244}
245
246/// The override's spellings. `scripts/rtems-bsp.sh` writes `1`; a value that
247/// is none of these is not a third answer, so it falls through to the version
248/// rule rather than being read as one direction or the other.
249#[cfg(target_os = "rtems")]
250fn parse_override(raw: &str) -> Option<bool> {
251 match raw.trim().to_ascii_lowercase().as_str() {
252 "1" | "yes" | "true" => Some(true),
253 "0" | "no" | "false" => Some(false),
254 _ => None,
255 }
256}
257
258/// The direction a task is waiting on. One waker per direction per fd — a PVA
259/// connection has one reader and one writer, and they are different tasks.
260#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
261pub enum Interest {
262 Read,
263 Write,
264}
265
266/// The largest fd this module can wait on, as a bit index into the `select`
267/// fd set.
268///
269/// `select` addresses fds by bit position, so the cost of a high fd is a
270/// larger bitmap and nothing else. 4096 covers both embedded triples with
271/// room: `x86_64-wrs-vxworks`'s limit is the VSB's `_WRS_CONFIG_FD_SET_SIZE`,
272/// 2048 in the SDK measured here, and RTEMS sizes its own set from
273/// `CONFIGURE_MAXIMUM_FILE_DESCRIPTORS`. An fd at or above this is refused at
274/// registration with `EMFILE` rather than silently dropped, because a dropped
275/// interest is a connection that hangs forever.
276pub const FD_CAPACITY: usize = 4096;
277
278/// One thing to tell the kernel about one direction on one fd — libevent's
279/// `EV_CHANGE_ADD` / `EV_CHANGE_DEL`, narrowed to the one direction it names.
280#[derive(Clone, Copy, Debug, PartialEq, Eq)]
281struct Change {
282 fd: RawFd,
283 interest: Interest,
284 action: Action,
285}
286
287/// Which way a [`Change`] goes.
288#[derive(Clone, Copy, Debug, PartialEq, Eq)]
289enum Action {
290 /// Start watching `(fd, interest)`, or re-affirm a watch already placed.
291 Arm,
292 /// Stop watching it.
293 Disarm,
294}
295
296/// Refuse an fd this module cannot address, rather than dropping it silently.
297///
298/// Shared by both backends because both have the same limit for the same
299/// reason: [`FD_CAPACITY`] is the `select` bitmap's width, and a poller whose
300/// two backends disagreed about which fds it accepts would fail differently
301/// depending on which one the target picked.
302fn check_fd(fd: RawFd) -> io::Result<()> {
303 if fd < 0 || (fd as usize) >= FD_CAPACITY {
304 return Err(io::Error::new(
305 io::ErrorKind::InvalidInput,
306 format!("fd {fd} is beyond the readiness poller's {FD_CAPACITY}-fd capacity"),
307 ));
308 }
309 Ok(())
310}
311
312/// What a backend must do. Two calls, both from the poll thread except
313/// [`Backend::interrupt`], which is called from whichever thread arms an
314/// interest.
315trait Backend: Send + Sync + 'static {
316 /// Apply `changes`, close every socket in `closing`, then wait until at
317 /// least one watched interest is ready and push the ready ones into
318 /// `ready`. Returns without error and with an empty `ready` when only
319 /// [`Backend::interrupt`] fired.
320 ///
321 /// > **MUST** every change is applied and every socket in `closing` is
322 /// > closed before the call can block, so a `wait` that fails with
323 /// > [`io::ErrorKind::Interrupted`] has still taken both.
324 ///
325 /// That is what lets [`poll_loop`] treat `Interrupted` as "go round
326 /// again" without re-queueing anything: a change leaves the armed set
327 /// exactly once, and a backend that deferred one past a blocking point
328 /// would strand the task that asked for it with no second chance to ask.
329 /// `select` satisfies it by keeping its fd sets in this process; `kqueue`
330 /// by submitting the changelist in the same `kevent` call, which applies
331 /// it before the wait begins.
332 ///
333 /// The sockets in `closing` are the ones whose deletes are in `changes`.
334 /// `select` closes them after the bits are cleared, which is the order its
335 /// scan needs; `kqueue` closes them before the `kevent` that carries the
336 /// deletes, which then miss harmlessly, because that call is also the one
337 /// that blocks.
338 fn wait(
339 &self,
340 changes: &[Change],
341 closing: &mut Vec<TcpStream>,
342 ready: &mut Vec<(RawFd, Interest)>,
343 ) -> io::Result<()>;
344
345 /// Break a concurrent [`Backend::wait`] out of its syscall.
346 fn interrupt(&self) -> io::Result<()>;
347}
348
349/// One direction on one fd: who to wake, what the kernel has been told, and
350/// what it has yet to be told.
351#[derive(Default)]
352struct Direction {
353 waker: Option<Waker>,
354 /// Whether the kernel is currently watching this direction — libevent's
355 /// `event_change::old_events`. It is the field that makes deleting
356 /// something never added a no-op instead of a syscall that fails.
357 registered: bool,
358 /// The change no backend has been handed yet.
359 pending: Option<Action>,
360}
361
362impl Direction {
363 /// Nobody to wake, nothing in the kernel, nothing left to say.
364 fn is_idle(&self) -> bool {
365 self.waker.is_none() && !self.registered && self.pending.is_none()
366 }
367}
368
369#[derive(Default)]
370struct Slot {
371 read: Direction,
372 write: Direction,
373 /// The socket, once its last handle is dropped, held until the delete the
374 /// kernel is owed for it goes out — see [`Inner::disarm_all`].
375 closing: Option<TcpStream>,
376}
377
378impl Slot {
379 fn direction(&mut self, interest: Interest) -> &mut Direction {
380 match interest {
381 Interest::Read => &mut self.read,
382 Interest::Write => &mut self.write,
383 }
384 }
385
386 fn has_waker(&self) -> bool {
387 self.read.waker.is_some() || self.write.waker.is_some()
388 }
389
390 fn is_dirty(&self) -> bool {
391 self.read.pending.is_some() || self.write.pending.is_some()
392 }
393
394 fn is_idle(&self) -> bool {
395 self.read.is_idle() && self.write.is_idle() && self.closing.is_none()
396 }
397}
398
399/// The armed set and the changes the kernel has not been told about yet, under
400/// one lock so the two cannot disagree about the order of a transition.
401#[derive(Default)]
402struct Armed {
403 slots: HashMap<RawFd, Slot>,
404 /// The fds carrying at least one pending change, in the order they came to
405 /// carry one. An fd is pushed on the clean-to-dirty edge, so it lands here
406 /// once per drain rather than once per change; an entry left behind by a
407 /// slot that went idle before the drain is skipped there, which is cheaper
408 /// than searching this vector to remove it.
409 dirty: Vec<RawFd>,
410}
411
412struct Inner {
413 armed: Mutex<Armed>,
414 shutdown: AtomicBool,
415 backend: ActiveBackend,
416}
417
418impl Inner {
419 /// Arm `(fd, interest)` with `waker`, replacing whatever waker that
420 /// direction held.
421 fn arm(&self, fd: RawFd, interest: Interest, waker: &Waker) -> io::Result<()> {
422 if self.shutdown.load(Ordering::Acquire) {
423 return Err(io::Error::new(
424 io::ErrorKind::Other,
425 "readiness poller is shut down",
426 ));
427 }
428 {
429 let mut armed = self.armed.lock().expect("readiness armed set poisoned");
430 let Armed { slots, dirty } = &mut *armed;
431 let slot = slots.entry(fd).or_default();
432 let was_dirty = slot.is_dirty();
433 let cell = slot.direction(interest);
434 match &cell.waker {
435 // `will_wake` is the common case on a re-poll of the same
436 // task: keep the waker we have rather than cloning again.
437 Some(existing) if existing.will_wake(waker) => {}
438 _ => cell.waker = Some(waker.clone()),
439 }
440 // An add replaces a pending delete and is never cancelled into a
441 // no-op, which is libevent's rule and for libevent's reason: that
442 // delete may have named an fd this number no longer stands for,
443 // and the kernel dropped the registration with the `close`.
444 cell.pending = Some(Action::Arm);
445 if !was_dirty {
446 dirty.push(fd);
447 }
448 }
449 self.backend.interrupt()
450 }
451
452 /// Take a socket back: drop every interest on it and close it once the
453 /// kernel is no longer watching it. The one path that removes an fd
454 /// besides the one-shot removal in the poll loop — see the module
455 /// invariant.
456 ///
457 /// The close is the poll thread's when the kernel holds a registration for
458 /// the fd — the socket rides in the slot until the delete goes out — and
459 /// immediate when it does not: an fd the kernel was never told about, or
460 /// one on a poller whose thread is gone. `shutdown` is read under the
461 /// armed lock because [`Inner::retire`] sets it under the same lock, so
462 /// the two cannot both miss the socket.
463 fn disarm_all(&self, socket: TcpStream) {
464 let fd = socket.as_raw_fd();
465 let close_now = {
466 let mut armed = self.armed.lock().expect("readiness armed set poisoned");
467 if self.shutdown.load(Ordering::Acquire) {
468 Some(socket)
469 } else {
470 let Armed { slots, dirty } = &mut *armed;
471 match slots.get_mut(&fd) {
472 None => Some(socket),
473 Some(slot) => {
474 let was_dirty = slot.is_dirty();
475 for interest in [Interest::Read, Interest::Write] {
476 let cell = slot.direction(interest);
477 cell.waker = None;
478 cell.pending = cell.registered.then_some(Action::Disarm);
479 }
480 if slot.is_dirty() {
481 slot.closing = Some(socket);
482 if !was_dirty {
483 dirty.push(fd);
484 }
485 None
486 } else {
487 // Nothing registered — an add the poll thread
488 // never drained was cancelled just above — so
489 // there is nothing to remove before the close.
490 slots.remove(&fd);
491 Some(socket)
492 }
493 }
494 }
495 }
496 };
497 drop(close_now);
498 // The armed set is no longer rebuilt from scratch on every wait, so
499 // this delete — and the close behind it — reaches the kernel only
500 // when the poll thread next drains. Waking it now is what holds that
501 // window to the wait already in flight rather than to whenever some
502 // other fd is next armed.
503 let _ = self.backend.interrupt();
504 }
505
506 /// Take the waker for one ready interest and stop watching that direction
507 /// — the one-shot half of the module invariant, kernel side included.
508 fn take_ready(&self, fd: RawFd, interest: Interest) -> Option<Waker> {
509 let mut armed = self.armed.lock().expect("readiness armed set poisoned");
510 let Armed { slots, dirty } = &mut *armed;
511 let slot = slots.get_mut(&fd)?;
512 let was_dirty = slot.is_dirty();
513 let cell = slot.direction(interest);
514 let waker = cell.waker.take();
515 cell.pending = cell.registered.then_some(Action::Disarm);
516 if slot.is_idle() {
517 slots.remove(&fd);
518 } else if slot.is_dirty() && !was_dirty {
519 dirty.push(fd);
520 }
521 waker
522 }
523
524 /// How many fds hold at least one waker. See [`Poller::armed_fds`].
525 fn armed_fds(&self) -> usize {
526 self.armed
527 .lock()
528 .expect("readiness armed set poisoned")
529 .slots
530 .values()
531 .filter(|slot| slot.has_waker())
532 .count()
533 }
534
535 /// Move the pending changes into `out`, the sockets whose deletes they
536 /// carry into `closing`, and record that the kernel is about to be told
537 /// about them.
538 ///
539 /// `registered` is committed here rather than after the syscall because of
540 /// the [`Backend::wait`] MUST: the only failure that leaves this poller
541 /// running is `Interrupted`, and that one happens after the changelist is
542 /// in.
543 fn drain_changes(&self, out: &mut Vec<Change>, closing: &mut Vec<TcpStream>) {
544 out.clear();
545 debug_assert!(
546 closing.is_empty(),
547 "the backend owes every socket it was handed a close before it blocks"
548 );
549 let mut armed = self.armed.lock().expect("readiness armed set poisoned");
550 let Armed { slots, dirty } = &mut *armed;
551 for fd in dirty.drain(..) {
552 let Some(slot) = slots.get_mut(&fd) else {
553 continue;
554 };
555 for interest in [Interest::Read, Interest::Write] {
556 let cell = slot.direction(interest);
557 let Some(action) = cell.pending.take() else {
558 continue;
559 };
560 cell.registered = action == Action::Arm;
561 out.push(Change {
562 fd,
563 interest,
564 action,
565 });
566 }
567 // Both directions' deletes are in `out` now — `disarm_all` queued
568 // them together with the socket — so the socket goes with them.
569 if let Some(socket) = slot.closing.take() {
570 closing.push(socket);
571 }
572 if slot.is_idle() {
573 slots.remove(&fd);
574 }
575 }
576 }
577
578 /// The poll thread's last act, on either exit: shut the poller, wake every
579 /// parked task, and close every socket held for a delete that will now
580 /// never go out — the kernel stops watching when the thread stops
581 /// waiting.
582 ///
583 /// `shutdown` is stored under the armed lock so that a concurrent
584 /// [`Inner::disarm_all`] either sees it set and closes its socket itself,
585 /// or has stored the socket in a slot this sweep takes. A task woken here
586 /// re-polls its socket, sees `WouldBlock` and re-arms, and it is the
587 /// refused re-arm that hands it the error it unwinds on.
588 fn retire(&self) {
589 let mut armed = self.armed.lock().expect("readiness armed set poisoned");
590 self.shutdown.store(true, Ordering::Release);
591 let Armed { slots, dirty } = &mut *armed;
592 dirty.clear();
593 for (_, slot) in slots.drain() {
594 let Slot {
595 read,
596 write,
597 closing,
598 } = slot;
599 for cell in [read, write] {
600 if let Some(w) = cell.waker {
601 w.wake();
602 }
603 }
604 drop(closing);
605 }
606 }
607}
608
609/// A readiness poller and the thread that owns its armed set.
610///
611/// One per server. [`Poller::shutdown`] stops the thread; dropping the last
612/// handle does the same.
613pub struct Poller {
614 inner: Arc<Inner>,
615}
616
617impl Poller {
618 /// Start a poller and its thread.
619 ///
620 /// `name` is the thread name, which is what an `epicsThreadShowAll`
621 /// equivalent prints; keep it short.
622 ///
623 /// `priority` is the caller's, not this module's: the poll thread is the
624 /// reactor of whatever server owns it, and a server's band is set against
625 /// the *other* servers in the IOC — pvxs runs its TCP reactor at
626 /// `CAServerLow-2`, CA's rsrv its own ladder from `caservertask.c`. A
627 /// constant here would put every server's reactor in one band and silently
628 /// undo that ordering.
629 pub fn new(
630 name: &str,
631 priority: crate::runtime::task::ThreadPriority,
632 ) -> io::Result<Arc<Self>> {
633 let inner = Arc::new(Inner {
634 armed: Mutex::new(Armed::default()),
635 shutdown: AtomicBool::new(false),
636 backend: ActiveBackend::new(name)?,
637 });
638 let thread_inner = Arc::clone(&inner);
639 crate::runtime::task::spawn_dedicated_thread(
640 name.to_string(),
641 priority,
642 crate::runtime::task::StackSizeClass::Small,
643 move || poll_loop(&thread_inner),
644 )?;
645 Ok(Arc::new(Self { inner }))
646 }
647
648 /// Wrap an accepted socket so the protocol code can read and write it
649 /// through this poller.
650 pub fn stream(self: &Arc<Self>, stream: std::net::TcpStream) -> io::Result<ReadyStream> {
651 stream.set_nonblocking(true)?;
652 let fd = stream.as_raw_fd();
653 if fd < 0 || fd as usize >= FD_CAPACITY {
654 return Err(io::Error::new(
655 io::ErrorKind::Other,
656 format!("fd {fd} is beyond the readiness poller's {FD_CAPACITY}-fd capacity"),
657 ));
658 }
659 Ok(ReadyStream {
660 inner: Arc::new(StreamInner {
661 poller: Arc::clone(self),
662 stream: ManuallyDrop::new(stream),
663 }),
664 })
665 }
666
667 /// Stop the poll thread. Idempotent.
668 pub fn shutdown(&self) {
669 self.inner.shutdown.store(true, Ordering::Release);
670 let _ = self.inner.backend.interrupt();
671 }
672
673 /// How many fds currently hold at least one armed interest. Test and
674 /// report surface; not a control input.
675 ///
676 /// An fd whose last waker has been taken but whose kernel registration is
677 /// still queued for deletion is not one of them. The count is of tasks
678 /// parked here, which is the question a report is asking.
679 pub fn armed_fds(&self) -> usize {
680 self.inner.armed_fds()
681 }
682}
683
684impl Drop for Poller {
685 fn drop(&mut self) {
686 self.shutdown();
687 }
688}
689
690fn poll_loop(inner: &Arc<Inner>) {
691 let mut ready: Vec<(RawFd, Interest)> = Vec::new();
692 let mut changes: Vec<Change> = Vec::new();
693 let mut closing: Vec<TcpStream> = Vec::new();
694 while !inner.shutdown.load(Ordering::Acquire) {
695 inner.drain_changes(&mut changes, &mut closing);
696 ready.clear();
697 match inner.backend.wait(&changes, &mut closing, &mut ready) {
698 Ok(()) => {}
699 // The changes are not re-queued, and need not be: `Backend::wait`
700 // owes them and the closes to the kernel before it may block, so
701 // the interrupted call is one that already took them.
702 Err(e) if e.kind() == io::ErrorKind::Interrupted => continue,
703 Err(e) => {
704 // A backend that cannot wait cannot serve anyone; `retire`
705 // below is what tells the parked tasks.
706 //
707 // `eprintln!` for the reason `ActiveBackend::new` uses it: a
708 // poller dying is a diagnostic that must not depend on a
709 // `tracing` subscriber being installed.
710 eprintln!("epics-rs: readiness poller cannot wait, shutting down: {e}");
711 break;
712 }
713 }
714 for &(fd, interest) in &ready {
715 if let Some(waker) = inner.take_ready(fd, interest) {
716 waker.wake();
717 }
718 }
719 }
720 inner.retire();
721 // A wait that failed may have refused its `closing`; those sockets close
722 // with the vector, after the sweep, when the kernel is watching nothing.
723}
724
725/// The socket and the poller it is registered with — the state every handle
726/// to one connection shares.
727///
728/// Dropping the last handle does not close the socket. It hands the socket to
729/// the poller ([`Inner::disarm_all`]), which closes it once the kernel is no
730/// longer watching the fd — the module invariant, held by construction rather
731/// than by field order.
732struct StreamInner {
733 poller: Arc<Poller>,
734 /// Taken in `Drop`, once, to hand to the poller.
735 stream: ManuallyDrop<TcpStream>,
736}
737
738impl Drop for StreamInner {
739 fn drop(&mut self) {
740 // SAFETY: taken exactly once, here, and `self.stream` is never read
741 // again — `drop` is the last code to see `self`.
742 let socket = unsafe { ManuallyDrop::take(&mut self.stream) };
743 self.poller.inner.disarm_all(socket);
744 }
745}
746
747impl StreamInner {
748 fn arm(&self, interest: Interest, waker: &Waker) -> io::Result<()> {
749 self.poller
750 .inner
751 .arm(self.stream.as_raw_fd(), interest, waker)
752 }
753
754 fn poll_read(
755 &self,
756 cx: &mut std::task::Context<'_>,
757 buf: &mut tokio::io::ReadBuf<'_>,
758 ) -> std::task::Poll<io::Result<()>> {
759 use std::io::Read;
760 // SAFETY-adjacent, but not `unsafe`: read into the uninitialised tail
761 // is avoided by going through `initialize_unfilled`, which is what
762 // costs a memset and what keeps this file free of raw buffers.
763 let dst = buf.initialize_unfilled();
764 match (&*self.stream).read(dst) {
765 Ok(n) => {
766 buf.advance(n);
767 std::task::Poll::Ready(Ok(()))
768 }
769 Err(e) if e.kind() == io::ErrorKind::WouldBlock => {
770 match self.arm(Interest::Read, cx.waker()) {
771 Ok(()) => std::task::Poll::Pending,
772 Err(e) => std::task::Poll::Ready(Err(e)),
773 }
774 }
775 Err(e) => std::task::Poll::Ready(Err(e)),
776 }
777 }
778
779 fn poll_write(
780 &self,
781 cx: &mut std::task::Context<'_>,
782 src: &[u8],
783 ) -> std::task::Poll<io::Result<usize>> {
784 use std::io::Write;
785 match (&*self.stream).write(src) {
786 Ok(n) => std::task::Poll::Ready(Ok(n)),
787 Err(e) if e.kind() == io::ErrorKind::WouldBlock => {
788 match self.arm(Interest::Write, cx.waker()) {
789 Ok(()) => std::task::Poll::Pending,
790 Err(e) => std::task::Poll::Ready(Err(e)),
791 }
792 }
793 Err(e) => std::task::Poll::Ready(Err(e)),
794 }
795 }
796
797 fn poll_shutdown(&self) -> std::task::Poll<io::Result<()>> {
798 match self.stream.shutdown(std::net::Shutdown::Write) {
799 Ok(()) => std::task::Poll::Ready(Ok(())),
800 // The peer closing first makes our half-close a no-op, not a
801 // failure the connection loop should report.
802 Err(e) if e.kind() == io::ErrorKind::NotConnected => std::task::Poll::Ready(Ok(())),
803 Err(e) => std::task::Poll::Ready(Err(e)),
804 }
805 }
806}
807
808/// A non-blocking `TcpStream` that reads and writes through a [`Poller`].
809pub struct ReadyStream {
810 inner: Arc<StreamInner>,
811}
812
813impl ReadyStream {
814 /// Two handles to the one socket, as a connection protocol takes them: a
815 /// reader and a writer that are polled by different tasks.
816 ///
817 /// Splitting costs nothing at the poller: read and write are already
818 /// separate interests on the same fd, each with its own waker, so the two
819 /// halves park independently on the one registration they share. There is
820 /// no lock between them either — the halves call `read`/`write` through
821 /// `&TcpStream`, and the kernel already serialises those per direction.
822 pub fn split(self) -> (ReadHalf, WriteHalf) {
823 (
824 ReadHalf {
825 inner: Arc::clone(&self.inner),
826 },
827 WriteHalf { inner: self.inner },
828 )
829 }
830
831 /// The wrapped socket, for the options a driver sets after accept
832 /// (`SO_SNDBUF`, keepalive).
833 pub fn socket(&self) -> &TcpStream {
834 &self.inner.stream
835 }
836}
837
838/// The read half of a split [`ReadyStream`].
839pub struct ReadHalf {
840 inner: Arc<StreamInner>,
841}
842
843/// The write half of a split [`ReadyStream`].
844pub struct WriteHalf {
845 inner: Arc<StreamInner>,
846}
847
848impl tokio::io::AsyncRead for ReadyStream {
849 fn poll_read(
850 self: std::pin::Pin<&mut Self>,
851 cx: &mut std::task::Context<'_>,
852 buf: &mut tokio::io::ReadBuf<'_>,
853 ) -> std::task::Poll<io::Result<()>> {
854 self.inner.poll_read(cx, buf)
855 }
856}
857
858impl tokio::io::AsyncRead for ReadHalf {
859 fn poll_read(
860 self: std::pin::Pin<&mut Self>,
861 cx: &mut std::task::Context<'_>,
862 buf: &mut tokio::io::ReadBuf<'_>,
863 ) -> std::task::Poll<io::Result<()>> {
864 self.inner.poll_read(cx, buf)
865 }
866}
867
868impl tokio::io::AsyncWrite for ReadyStream {
869 fn poll_write(
870 self: std::pin::Pin<&mut Self>,
871 cx: &mut std::task::Context<'_>,
872 src: &[u8],
873 ) -> std::task::Poll<io::Result<usize>> {
874 self.inner.poll_write(cx, src)
875 }
876
877 fn poll_flush(
878 self: std::pin::Pin<&mut Self>,
879 _cx: &mut std::task::Context<'_>,
880 ) -> std::task::Poll<io::Result<()>> {
881 // A TCP socket has no userspace buffer of ours to flush: every
882 // accepted byte is already in the kernel's send buffer.
883 std::task::Poll::Ready(Ok(()))
884 }
885
886 fn poll_shutdown(
887 self: std::pin::Pin<&mut Self>,
888 _cx: &mut std::task::Context<'_>,
889 ) -> std::task::Poll<io::Result<()>> {
890 self.inner.poll_shutdown()
891 }
892}
893
894impl tokio::io::AsyncWrite for WriteHalf {
895 fn poll_write(
896 self: std::pin::Pin<&mut Self>,
897 cx: &mut std::task::Context<'_>,
898 src: &[u8],
899 ) -> std::task::Poll<io::Result<usize>> {
900 self.inner.poll_write(cx, src)
901 }
902
903 fn poll_flush(
904 self: std::pin::Pin<&mut Self>,
905 _cx: &mut std::task::Context<'_>,
906 ) -> std::task::Poll<io::Result<()>> {
907 std::task::Poll::Ready(Ok(()))
908 }
909
910 fn poll_shutdown(
911 self: std::pin::Pin<&mut Self>,
912 _cx: &mut std::task::Context<'_>,
913 ) -> std::task::Poll<io::Result<()>> {
914 self.inner.poll_shutdown()
915 }
916}
917
918#[cfg(test)]
919mod tests {
920 use super::*;
921 use crate::runtime::task::ThreadPriority;
922 use std::io::{Read, Write};
923 use std::net::{TcpListener, TcpStream};
924 use tokio::io::{AsyncReadExt, AsyncWriteExt};
925
926 /// A connected pair over loopback: (accepted server side, client side).
927 fn pair() -> (TcpStream, TcpStream) {
928 let listener = TcpListener::bind(("127.0.0.1", 0)).expect("bind");
929 let addr = listener.local_addr().expect("local_addr");
930 let client = TcpStream::connect(addr).expect("connect");
931 let (server, _) = listener.accept().expect("accept");
932 (server, client)
933 }
934
935 /// Whether `fd` is still open in this process. Sound as a test probe
936 /// because nextest runs each test in a process of its own, so a number
937 /// this test closed is reused only by this test.
938 fn is_open(fd: RawFd) -> bool {
939 // SAFETY: `F_GETFD` takes no pointer and touches nothing.
940 unsafe { libc::fcntl(fd, libc::F_GETFD) != -1 }
941 }
942
943 /// Two round trips, not one: the second is what fails if the delete the
944 /// first delivery queued outlives the re-arm that follows it, or if the
945 /// re-arm is skipped because the kernel is thought to hold a registration
946 /// it no longer has.
947 #[tokio::test]
948 async fn a_read_parks_until_the_peer_writes_and_then_delivers() {
949 let poller = Poller::new("RDT1", ThreadPriority::CaServerHigh).expect("poller");
950 let (server, mut client) = pair();
951 let mut stream = poller.stream(server).expect("stream");
952
953 let read = tokio::spawn(async move {
954 let mut both = [0u8; 10];
955 // Deliberately started before any byte exists: this must park in
956 // the poller, not spin or return zero.
957 stream.read_exact(&mut both[..5]).await.expect("first");
958 stream.read_exact(&mut both[5..]).await.expect("second");
959 both
960 });
961
962 for payload in [b"hello", b"again"] {
963 tokio::time::sleep(std::time::Duration::from_millis(50)).await;
964 client.write_all(payload).expect("peer write");
965 }
966
967 let got = tokio::time::timeout(std::time::Duration::from_secs(5), read)
968 .await
969 .expect("read completed")
970 .expect("join");
971 assert_eq!(&got, b"helloagain");
972 }
973
974 #[tokio::test]
975 async fn b_write_parks_when_the_send_buffer_fills_and_resumes_when_it_drains() {
976 const BYTES: usize = 32 * 1024 * 1024;
977 let poller = Poller::new("RDT2", ThreadPriority::CaServerHigh).expect("poller");
978 let (server, mut client) = pair();
979 let mut stream = poller.stream(server).expect("stream");
980
981 // Past any auto-tuned SO_SNDBUF plus the peer's receive buffer, so the
982 // write cannot finish while nobody reads.
983 let payload = vec![0x5au8; BYTES];
984 let write = tokio::spawn(async move {
985 stream.write_all(&payload).await.expect("write_all");
986 stream.flush().await.expect("flush");
987 });
988
989 tokio::time::sleep(std::time::Duration::from_millis(200)).await;
990 assert!(
991 !write.is_finished(),
992 "the write must park on the poller while the peer reads nothing"
993 );
994
995 let drained = tokio::task::spawn_blocking(move || {
996 let mut sink = vec![0u8; 64 * 1024];
997 let mut total = 0usize;
998 while total < BYTES {
999 match client.read(&mut sink) {
1000 Ok(0) => break,
1001 Ok(n) => total += n,
1002 Err(e) => panic!("peer read: {e}"),
1003 }
1004 }
1005 total
1006 });
1007
1008 tokio::time::timeout(std::time::Duration::from_secs(30), write)
1009 .await
1010 .expect("write completed once the peer drained")
1011 .expect("join");
1012 assert_eq!(drained.await.expect("join"), BYTES);
1013 }
1014
1015 /// The invariant end to end: the drop takes the fd out of the armed set
1016 /// at once, and the socket closes only once the poll thread has drained
1017 /// the delete — so the close is the poll thread's, never the dropping
1018 /// task's.
1019 #[tokio::test]
1020 async fn c_dropping_the_stream_disarms_the_fd_and_the_poll_thread_closes_it() {
1021 let poller = Poller::new("RDT3", ThreadPriority::CaServerHigh).expect("poller");
1022 let (server, _client) = pair();
1023 let mut stream = poller.stream(server).expect("stream");
1024 let fd = stream.socket().as_raw_fd();
1025
1026 // Park a read so the fd is genuinely armed.
1027 let mut buf = [0u8; 1];
1028 let armed =
1029 tokio::time::timeout(std::time::Duration::from_millis(100), stream.read(&mut buf))
1030 .await;
1031 assert!(armed.is_err(), "read must park with no byte available");
1032 assert_eq!(poller.armed_fds(), 1);
1033
1034 drop(stream);
1035 assert_eq!(
1036 poller.armed_fds(),
1037 0,
1038 "StreamInner::drop must clear the armed set at once"
1039 );
1040 let deadline = std::time::Instant::now() + std::time::Duration::from_secs(2);
1041 while is_open(fd) {
1042 assert!(
1043 std::time::Instant::now() < deadline,
1044 "the poll thread must close the socket once its delete is out"
1045 );
1046 tokio::time::sleep(std::time::Duration::from_millis(5)).await;
1047 }
1048 }
1049
1050 #[tokio::test]
1051 async fn d_an_fd_past_capacity_is_refused_rather_than_dropped() {
1052 let poller = Poller::new("RDT4", ThreadPriority::CaServerHigh).expect("poller");
1053 let (server, _client) = pair();
1054 // The real guard is in `Poller::stream`; drive it by asking about a
1055 // number no process will hand out, which is the same branch.
1056 let too_high = FD_CAPACITY as RawFd + 1;
1057 assert!(too_high as usize >= FD_CAPACITY);
1058 drop(server);
1059 let err = poller
1060 .inner
1061 .backend
1062 .wait(
1063 &[Change {
1064 fd: too_high,
1065 interest: Interest::Read,
1066 action: Action::Arm,
1067 }],
1068 &mut Vec::new(),
1069 &mut Vec::new(),
1070 )
1071 .expect_err("a fd past capacity must be an error, not a silent skip");
1072 assert_eq!(err.kind(), io::ErrorKind::InvalidInput);
1073 }
1074
1075 #[tokio::test]
1076 async fn f_the_two_halves_share_one_registration_until_the_last_drops() {
1077 let poller = Poller::new("RDT6", ThreadPriority::CaServerHigh).expect("poller");
1078 let (server, mut client) = pair();
1079 let (mut read_half, mut write_half) = poller.stream(server).expect("stream").split();
1080
1081 // Park the read half; the fd is armed for read from here on.
1082 let mut buf = [0u8; 5];
1083 let parked = tokio::time::timeout(
1084 std::time::Duration::from_millis(100),
1085 read_half.read(&mut buf),
1086 )
1087 .await;
1088 assert!(parked.is_err(), "the read half must park with no byte");
1089 assert_eq!(poller.armed_fds(), 1);
1090
1091 // The write half drives the same fd while that read is parked — the
1092 // two directions are independent interests, not a shared lock.
1093 write_half.write_all(b"ping").await.expect("write half");
1094 let mut echo = [0u8; 4];
1095 client.read_exact(&mut echo).expect("peer read");
1096 assert_eq!(&echo, b"ping");
1097
1098 // The registration belongs to neither half alone: only the last one
1099 // out may take the fd out of the armed set, because only then is the
1100 // socket about to close.
1101 drop(write_half);
1102 assert_eq!(
1103 poller.armed_fds(),
1104 1,
1105 "the surviving half still holds the registration"
1106 );
1107 drop(read_half);
1108 assert_eq!(
1109 poller.armed_fds(),
1110 0,
1111 "the last half dropping disarms the fd before the close"
1112 );
1113 }
1114
1115 #[tokio::test]
1116 async fn e_shutdown_stops_the_poll_thread_and_further_arming_fails() {
1117 let poller = Poller::new("RDT5", ThreadPriority::CaServerHigh).expect("poller");
1118 let (server, _client) = pair();
1119 let stream = poller.stream(server).expect("stream");
1120 poller.shutdown();
1121
1122 let err = stream
1123 .inner
1124 .arm(Interest::Read, std::task::Waker::noop())
1125 .expect_err("arming a shut-down poller must fail");
1126 assert_eq!(err.kind(), io::ErrorKind::Other);
1127 }
1128
1129 /// A poller whose backend cannot wait is dead, and the tasks parked on it
1130 /// must find out. Waking them is not enough: a woken task re-polls its
1131 /// socket, sees `WouldBlock` again, and re-arms — and if the poller still
1132 /// accepts the arm it parks there for good, on a thread that has exited.
1133 /// The fatal path must shut the poller first, so the re-arm is refused
1134 /// and the read completes with an error the connection can act on.
1135 ///
1136 /// The backend is made to fail through the one input it refuses: a
1137 /// change naming an fd past capacity, which `Inner::arm` does not check
1138 /// (`Poller::stream` does) and `Backend::wait` does.
1139 #[tokio::test]
1140 async fn l_a_poller_that_cannot_wait_fails_its_parked_reads() {
1141 let poller = Poller::new("RDT7", ThreadPriority::CaServerHigh).expect("poller");
1142 let (server, _client) = pair();
1143 let mut stream = poller.stream(server).expect("stream");
1144
1145 let read = tokio::spawn(async move {
1146 let mut buf = [0u8; 1];
1147 stream.read(&mut buf).await
1148 });
1149 tokio::time::sleep(std::time::Duration::from_millis(50)).await;
1150 assert_eq!(poller.armed_fds(), 1, "the read is parked on the poller");
1151
1152 poller
1153 .inner
1154 .arm(
1155 FD_CAPACITY as RawFd + 1,
1156 Interest::Read,
1157 std::task::Waker::noop(),
1158 )
1159 .expect("the armed set takes it; the backend is what refuses");
1160
1161 let outcome = tokio::time::timeout(std::time::Duration::from_secs(5), read)
1162 .await
1163 .expect("a read parked on a dead poller must not park forever")
1164 .expect("join");
1165 let err = outcome.expect_err("the read fails rather than pretending to wait");
1166 assert_eq!(err.kind(), io::ErrorKind::Other, "{err}");
1167 }
1168
1169 /// The armed set and its changelist, without a poll thread to race the
1170 /// assertions. `Inner::drain_changes` is the poll thread's first act on
1171 /// every turn, so calling it by hand is calling the same code.
1172 fn bare_inner() -> Arc<Inner> {
1173 Arc::new(Inner {
1174 armed: Mutex::new(Armed::default()),
1175 shutdown: AtomicBool::new(false),
1176 backend: ActiveBackend::new("RDTC").expect("backend"),
1177 })
1178 }
1179
1180 fn arm(inner: &Inner, fd: RawFd, interest: Interest) {
1181 inner
1182 .arm(fd, interest, std::task::Waker::noop())
1183 .expect("arm");
1184 }
1185
1186 fn change(fd: RawFd, interest: Interest, action: Action) -> Change {
1187 Change {
1188 fd,
1189 interest,
1190 action,
1191 }
1192 }
1193
1194 /// libevent's first changelist rule, and the one that matters under fd
1195 /// reuse: the add wins over the pending delete, and it is still an add.
1196 #[test]
1197 fn g_an_add_over_a_pending_delete_stays_an_add() {
1198 let inner = bare_inner();
1199 let mut out = Vec::new();
1200
1201 let mut closing = Vec::new();
1202
1203 arm(&inner, 7, Interest::Read);
1204 inner.drain_changes(&mut out, &mut closing);
1205 assert_eq!(out, [change(7, Interest::Read, Action::Arm)]);
1206
1207 // Delivery queues the one-shot delete; the task re-arms before the
1208 // poll thread gets back round to draining it.
1209 assert!(inner.take_ready(7, Interest::Read).is_some());
1210 arm(&inner, 7, Interest::Read);
1211 inner.drain_changes(&mut out, &mut closing);
1212 assert_eq!(
1213 out,
1214 [change(7, Interest::Read, Action::Arm)],
1215 "the re-arm replaces the delete, and does not cancel itself with it"
1216 );
1217 }
1218
1219 /// libevent's second rule: a delete of something the kernel was never told
1220 /// about is dropped rather than submitted — and with nothing to remove
1221 /// first, the socket closes on the spot.
1222 #[test]
1223 fn h_a_delete_of_an_unregistered_interest_is_cancelled() {
1224 let inner = bare_inner();
1225 let mut out = Vec::new();
1226 let mut closing = Vec::new();
1227 let (server, _client) = pair();
1228 let fd = server.as_raw_fd();
1229
1230 arm(&inner, fd, Interest::Read);
1231 // The connection died before the poll thread ever drained the add.
1232 inner.disarm_all(server);
1233 assert!(
1234 !is_open(fd),
1235 "nothing in the kernel to remove: closed at once"
1236 );
1237 inner.drain_changes(&mut out, &mut closing);
1238 assert!(out.is_empty(), "nothing to undo in the kernel: {out:?}");
1239 assert!(
1240 closing.is_empty(),
1241 "nothing left for the poll thread to close"
1242 );
1243 assert_eq!(inner.armed.lock().expect("armed").slots.len(), 0);
1244 }
1245
1246 /// The other side of that rule, and the one-shot invariant's kernel half:
1247 /// what the kernel does hold is deleted, exactly once — and the socket
1248 /// stays open until that delete is out, riding in the same drain.
1249 #[test]
1250 fn i_a_delete_of_a_registered_interest_is_emitted_once() {
1251 let inner = bare_inner();
1252 let mut out = Vec::new();
1253 let mut closing = Vec::new();
1254 let (server, _client) = pair();
1255 let fd = server.as_raw_fd();
1256
1257 arm(&inner, fd, Interest::Write);
1258 inner.drain_changes(&mut out, &mut closing);
1259 inner.disarm_all(server);
1260 assert!(is_open(fd), "registered: the close waits for the delete");
1261 inner.drain_changes(&mut out, &mut closing);
1262 assert_eq!(out, [change(fd, Interest::Write, Action::Disarm)]);
1263 assert_eq!(closing.len(), 1, "the socket rides with its delete");
1264 assert!(is_open(fd), "and is still open until the backend takes it");
1265 // What a backend does once the delete is applied.
1266 closing.clear();
1267 assert!(!is_open(fd));
1268
1269 inner.drain_changes(&mut out, &mut closing);
1270 assert!(
1271 out.is_empty(),
1272 "a drained change is not re-emitted: {out:?}"
1273 );
1274 assert_eq!(inner.armed.lock().expect("armed").slots.len(), 0);
1275 }
1276
1277 /// Boundary: the poller is already shut down when the socket comes back.
1278 /// No poll thread will ever drain the delete, so the close is immediate,
1279 /// and the slot does not keep a socket nobody will take.
1280 #[test]
1281 fn o_a_socket_given_back_to_a_shut_down_poller_closes_at_once() {
1282 let inner = bare_inner();
1283 let mut out = Vec::new();
1284 let mut closing = Vec::new();
1285 let (server, _client) = pair();
1286 let fd = server.as_raw_fd();
1287
1288 arm(&inner, fd, Interest::Read);
1289 inner.drain_changes(&mut out, &mut closing);
1290 inner.retire();
1291 inner.disarm_all(server);
1292 assert!(!is_open(fd), "no thread left to close it later");
1293 assert_eq!(inner.armed.lock().expect("armed").slots.len(), 0);
1294 }
1295
1296 /// The poll thread's exit sweep: a parked task is woken, its next arm is
1297 /// refused, and a socket held for a delete that will never go out is
1298 /// closed — on both exits, since both end in `retire`.
1299 #[test]
1300 fn p_retire_wakes_the_parked_and_closes_what_it_holds() {
1301 struct Flag(AtomicBool);
1302 impl std::task::Wake for Flag {
1303 fn wake(self: Arc<Self>) {
1304 self.0.store(true, Ordering::Release);
1305 }
1306 }
1307 let inner = bare_inner();
1308 let mut out = Vec::new();
1309 let mut closing = Vec::new();
1310 let (parked, _c1) = pair();
1311 let parked_fd = parked.as_raw_fd();
1312 let (dropped, _c2) = pair();
1313 let dropped_fd = dropped.as_raw_fd();
1314
1315 let woken = Arc::new(Flag(AtomicBool::new(false)));
1316 let waker = Waker::from(Arc::clone(&woken));
1317 inner.arm(parked_fd, Interest::Read, &waker).expect("arm");
1318 arm(&inner, dropped_fd, Interest::Read);
1319 inner.drain_changes(&mut out, &mut closing);
1320 inner.disarm_all(dropped);
1321 assert!(
1322 is_open(dropped_fd),
1323 "held for a delete the thread now never sends"
1324 );
1325
1326 inner.retire();
1327 assert!(woken.0.load(Ordering::Acquire), "the parked task is woken");
1328 assert!(
1329 !is_open(dropped_fd),
1330 "the held socket is closed by the sweep"
1331 );
1332 assert!(is_open(parked_fd), "a socket still owned by a task is not");
1333 assert!(
1334 inner.arm(parked_fd, Interest::Read, &waker).is_err(),
1335 "the woken task's re-arm is refused, which is its error"
1336 );
1337 inner.disarm_all(parked);
1338 assert!(!is_open(parked_fd), "and its own drop closes at once");
1339 }
1340
1341 /// The whole point of the changelist: holding connections costs nothing
1342 /// per wait once they are registered. This is the assertion the old
1343 /// whole-armed-set design could not have made.
1344 #[test]
1345 fn j_a_quiet_armed_set_submits_no_changes() {
1346 let inner = bare_inner();
1347 let mut out = Vec::new();
1348
1349 let mut closing = Vec::new();
1350
1351 for fd in 3..11 {
1352 arm(&inner, fd, Interest::Read);
1353 }
1354 inner.drain_changes(&mut out, &mut closing);
1355 assert_eq!(out.len(), 8, "eight arms, eight changes: {out:?}");
1356
1357 inner.drain_changes(&mut out, &mut closing);
1358 assert!(
1359 out.is_empty(),
1360 "eight registrations already placed cost nothing to keep: {out:?}"
1361 );
1362 assert_eq!(inner.armed_fds(), 8);
1363 }
1364
1365 /// Both directions of one fd are one entry in the dirty list and two
1366 /// changes out of it — the boundary the per-fd change record exists to
1367 /// get right.
1368 #[test]
1369 fn k_both_directions_of_one_fd_drain_together() {
1370 let inner = bare_inner();
1371 let mut out = Vec::new();
1372
1373 let mut closing = Vec::new();
1374 let (server, _client) = pair();
1375 let fd = server.as_raw_fd();
1376
1377 arm(&inner, fd, Interest::Read);
1378 arm(&inner, fd, Interest::Write);
1379 assert_eq!(inner.armed.lock().expect("armed").dirty, [fd]);
1380 inner.drain_changes(&mut out, &mut closing);
1381 out.sort_by_key(|c| c.interest == Interest::Write);
1382 assert_eq!(
1383 out,
1384 [
1385 change(fd, Interest::Read, Action::Arm),
1386 change(fd, Interest::Write, Action::Arm)
1387 ]
1388 );
1389
1390 inner.disarm_all(server);
1391 inner.drain_changes(&mut out, &mut closing);
1392 out.sort_by_key(|c| c.interest == Interest::Write);
1393 assert_eq!(
1394 out,
1395 [
1396 change(fd, Interest::Read, Action::Disarm),
1397 change(fd, Interest::Write, Action::Disarm)
1398 ]
1399 );
1400 assert_eq!(closing.len(), 1, "one socket behind the two deletes");
1401 }
1402}