Skip to main content

rustfs_uring/
driver.rs

1// Copyright 2024 RustFS Team
2//
3// Licensed under the Apache License, Version 2.0 (the "License");
4// you may not use this file except in compliance with the License.
5// You may obtain a copy of the License at
6//
7//     http://www.apache.org/licenses/LICENSE-2.0
8//
9// Unless required by applicable law or agreed to in writing, software
10// distributed under the License is distributed on an "AS IS" BASIS,
11// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12// See the License for the specific language governing permissions and
13// limitations under the License.
14
15use std::collections::{HashMap, VecDeque};
16use std::fs::File;
17use std::io;
18use std::io::Write as _;
19use std::os::fd::{AsRawFd, FromRawFd};
20use std::os::unix::ffi::OsStrExt;
21use std::pin::Pin;
22use std::sync::Arc;
23use std::sync::atomic::{AtomicU64, AtomicUsize, Ordering};
24use std::sync::mpsc::{self, TryRecvError};
25use std::task::{Context, Poll};
26use std::thread::JoinHandle;
27use std::time::{Duration, Instant};
28
29use io_uring::{IoUring, opcode, types};
30
31/// Upper bound on how long shutdown waits for in-flight ops to drain before
32/// leaking the ring+buffers and exiting (C4, rustfs/backlog#1055). ASYNC_CANCEL
33/// cannot interrupt an in-execution regular-file read on a D-state/NFS-hung
34/// disk, so drain-to-zero can be non-terminating; this bounds it.
35const DRAIN_TIMEOUT: Duration = Duration::from_secs(5);
36use tokio::sync::{OwnedSemaphorePermit, Semaphore, TryAcquireError, oneshot};
37
38/// user_data bit marking the CQE of an `AsyncCancel` SQE itself (as opposed
39/// to the CQE of the read op it targets).
40const CANCEL_BIT: u64 = 1 << 63;
41
42/// `offset` value meaning "use the file's current position" (read(2)
43/// semantics); required for pipes/sockets where pread returns ESPIPE.
44const CURRENT_POSITION: u64 = u64::MAX;
45
46/// Kernel single-read cap: `MAX_RW_COUNT = INT_MAX & PAGE_MASK` (2 GiB − 4 KiB
47/// on 4 KiB pages). io_uring's READ length field is a u32, and any request
48/// above this short-reads. We reject beyond it in `submit` so a `len as u32`
49/// truncation can never silently turn a huge read into a 0-byte "EOF" (C6,
50/// rustfs/backlog#1057); P2 must chunk reads larger than this.
51const MAX_READ_LEN: usize = 0x7fff_f000;
52
53/// Block-aligned superset geometry for a read (rustfs/backlog#1102).
54///
55/// Returns `(kernel_offset, head, region_len)`: the offset handed to the kernel,
56/// how many bytes of the read region precede the caller's logical range, and how
57/// many bytes the kernel is asked to read. `align == 1` is the buffered case and
58/// passes `offset` (which may be `CURRENT_POSITION`) straight through.
59///
60/// `None` when `align` is not a power of two or the aligned range would overflow.
61fn aligned_geometry(offset: u64, len: usize, align: usize) -> Option<(u64, usize, usize)> {
62    // A real device block is tiny (512..=4096). Capping alignment at the read
63    // cap keeps `align_offset(align)` always satisfiable (so it never returns
64    // `usize::MAX`, which would make the driver's later `ptr::add(pad)` UB) and
65    // keeps the `region_len + align - 1` allocation from overflowing `usize`.
66    if align == 0 || !align.is_power_of_two() || align > MAX_READ_LEN {
67        return None;
68    }
69    if align == 1 {
70        return Some((offset, 0, len));
71    }
72    let mask = align as u64 - 1;
73    let kernel_offset = offset & !mask;
74    let head = usize::try_from(offset - kernel_offset).ok()?;
75    let region_len = head.checked_add(len)?.checked_next_multiple_of(align)?;
76    Some((kernel_offset, head, region_len))
77}
78
79/// Heartbeat bound on the driver loop's blocking wait (backlog#1102). The loop
80/// normally wakes on a CQE (the ring's registered eventfd) or a new message
81/// (the wakeup eventfd); this timeout only bounds the wait so the bounded-drain
82/// deadline is still checked and any queued cancel is picked up promptly.
83const LOOP_HEARTBEAT: Duration = Duration::from_millis(50);
84
85/// Owned `eventfd(2)` used to wake the driver loop (backlog#1102): one is
86/// registered with the ring so the kernel signals it on every CQE, the other is
87/// signaled by `submit`/shutdown so a new message wakes the loop immediately —
88/// together they replace the spike's 200 µs busy-poll.
89struct EventFd {
90    fd: std::os::fd::RawFd,
91}
92
93impl EventFd {
94    fn new() -> io::Result<Self> {
95        // SAFETY: eventfd returns a fresh owned fd or -1; the flags are valid.
96        let fd = unsafe { libc::eventfd(0, libc::EFD_NONBLOCK | libc::EFD_CLOEXEC) };
97        if fd < 0 {
98            return Err(io::Error::last_os_error());
99        }
100        Ok(Self { fd })
101    }
102
103    fn as_raw(&self) -> std::os::fd::RawFd {
104        self.fd
105    }
106
107    /// Make the fd readable. A saturated counter (EAGAIN) is fine — it is
108    /// already readable, which is all a wakeup needs.
109    fn signal(&self) {
110        let v: u64 = 1;
111        // SAFETY: writing 8 bytes from a valid u64 to an eventfd we own.
112        unsafe {
113            libc::write(self.fd, (&v as *const u64).cast(), 8);
114        }
115    }
116
117    /// Reset the counter. EFD_NONBLOCK guarantees this never blocks; a single
118    /// successful read drains the whole counter, the next returns EAGAIN.
119    fn drain(&self) {
120        let mut v: u64 = 0;
121        // SAFETY: reading 8 bytes into a valid u64 from an eventfd we own.
122        while unsafe { libc::read(self.fd, (&mut v as *mut u64).cast(), 8) } == 8 {}
123    }
124}
125
126impl Drop for EventFd {
127    fn drop(&mut self) {
128        // SAFETY: we own this fd and drop it exactly once.
129        unsafe {
130            libc::close(self.fd);
131        }
132    }
133}
134
135/// Block until a CQE is ready (`cq`), a new message arrives (`wake`), or the
136/// heartbeat elapses. The return value is ignored: a spurious wakeup, timeout,
137/// or EINTR just runs one loop turn (intake + reap), which is always safe.
138fn wait_for_events(cq: &EventFd, wake: &EventFd, timeout: Duration) {
139    let mut fds = [
140        libc::pollfd {
141            fd: cq.as_raw(),
142            events: libc::POLLIN,
143            revents: 0,
144        },
145        libc::pollfd {
146            fd: wake.as_raw(),
147            events: libc::POLLIN,
148            revents: 0,
149        },
150    ];
151    let ms = timeout.as_millis().min(i32::MAX as u128) as libc::c_int;
152    // SAFETY: `fds` is a valid, initialized array of two pollfds.
153    unsafe {
154        libc::poll(fds.as_mut_ptr(), fds.len() as libc::nfds_t, ms);
155    }
156}
157
158/// Why the probe refused to start the io_uring driver.
159///
160/// Mirrors the P2 degradation contract (backlog#894): a restricted
161/// environment must be recognized and answered with a silent fallback to the
162/// std backend, never surfaced to callers.
163#[derive(Debug)]
164pub enum ProbeFailure {
165    /// `io_uring_setup` itself failed (seccomp/gVisor/old kernel).
166    Setup(io::Error),
167    /// The ring was created but a real `IORING_OP_READ` did not complete
168    /// correctly (gVisor accepts setup but fails ops; also covers silent
169    /// data corruption, which we treat as "unusable").
170    ReadOp(io::Error),
171}
172
173impl ProbeFailure {
174    /// True when the **probe-time** errno belongs to the "expected
175    /// restriction" class that P2 maps to permanent per-disk fallback:
176    /// EACCES/EPERM/ENOSYS/EINVAL/EOPNOTSUPP. Anything else is a genuine bug
177    /// worth surfacing.
178    ///
179    /// IMPORTANT (C7, rustfs/backlog#1059): this classification is valid ONLY
180    /// for a one-shot startup probe, where these errnos unambiguously mean
181    /// "io_uring is unusable here" (gVisor/seccomp/old kernel). Runtime
182    /// per-op errnos have different semantics and MUST NOT reuse this class.
183    /// In particular EINVAL is triple-meaning at runtime — offset > i64::MAX
184    /// (signed loff_t), O_DIRECT buffer/offset/len misalignment (P2 will use
185    /// O_DIRECT), and setup `entries` over the cap — none of which imply the
186    /// disk should be permanently degraded off io_uring. P2's degradation
187    /// contract must split errnos into three classes:
188    ///
189    ///   * probe-time restriction  -> degrade this disk to the std backend;
190    ///   * runtime parameter error -> return the error to the caller (and,
191    ///     for a suspected bug, re-verify once via std pread) — never latch;
192    ///   * transient (EINTR/EAGAIN) -> retry, never surface.
193    ///
194    /// See `submit` for the offset guard that keeps a caller arithmetic bug
195    /// from ever reaching the kernel as a runtime EINVAL.
196    pub fn is_expected_restriction(&self) -> bool {
197        let err = match self {
198            ProbeFailure::Setup(e) | ProbeFailure::ReadOp(e) => e,
199        };
200        matches!(
201            err.raw_os_error(),
202            Some(libc::EACCES) | Some(libc::EPERM) | Some(libc::ENOSYS) | Some(libc::EINVAL) | Some(libc::EOPNOTSUPP)
203        )
204    }
205}
206
207// Submission-side backpressure (C10, rustfs/backlog#1060; async in #1102).
208//
209// A `tokio::sync::Semaphore` with `entries` permits bounds in-flight ops below
210// CQ capacity. The load-bearing rule is the RELEASE POINT: a permit is released
211// at the CQE (when the pending-table entry is removed), NOT at future drop.
212// Tying a permit to the future (the natural RAII shape) would let a quorum
213// dropping many futures return permits while their orphan buffers still sit in
214// the pending table awaiting slow-disk CQEs, decoupling the permit count from
215// resident memory and reopening the memory-DoS surface.
216//
217// That rule is now enforced by the type system rather than by a manual
218// `release()` call: the `OwnedSemaphorePermit` travels with `Msg::Read` into the
219// `Pending` entry and is dropped exactly when the entry is removed at the final
220// CQE. A short-read resubmit keeps the entry — and thus the permit.
221//
222// Acquisition never blocks the caller's thread: `submit` takes the permit with
223// `try_acquire_owned()` on the common unsaturated path (no allocation, no await,
224// submission stays eager), and when saturated it hands the acquire future to the
225// returned `ReadHandle`, which awaits it on its first poll and submits then.
226
227/// Boxed `Semaphore::acquire_owned` future held by a saturated `ReadHandle`.
228type AcquireFut = Pin<Box<dyn Future<Output = Result<OwnedSemaphorePermit, tokio::sync::AcquireError>> + Send>>;
229
230#[derive(Default)]
231struct DriverStats {
232    submitted: AtomicU64,
233    delivered: AtomicU64,
234    orphan_reclaimed: AtomicU64,
235    in_flight: AtomicU64,
236    cancel_succeeded: AtomicU64,
237    cancel_not_found: AtomicU64,
238    cancel_already: AtomicU64,
239    cq_overflow: AtomicU64,
240}
241
242/// Point-in-time copy of the driver counters.
243#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
244pub struct StatsSnapshot {
245    /// Read ops handed to the kernel.
246    pub submitted: u64,
247    /// CQEs whose result was received by a live caller.
248    pub delivered: u64,
249    /// CQEs whose caller had dropped the future: the buffer stayed in the
250    /// pending table the whole time and was reclaimed here, at the CQE.
251    pub orphan_reclaimed: u64,
252    /// Ops submitted but not yet completed. The kernel may still write into
253    /// their buffers.
254    pub in_flight: u64,
255    /// ASYNC_CANCEL CQEs that reported the target op was canceled (res == 0).
256    pub cancel_succeeded: u64,
257    /// ASYNC_CANCEL CQEs that reported the target was not found (-ENOENT):
258    /// the op had already completed.
259    pub cancel_not_found: u64,
260    /// ASYNC_CANCEL CQEs that reported the target was already executing and
261    /// could not be interrupted (-EALREADY). A rising count is the hung-disk
262    /// signal that makes drain-to-zero non-terminating (C4,
263    /// rustfs/backlog#1055).
264    pub cancel_already: u64,
265    /// Kernel CQ-ring overflow counter. MUST stay 0: a non-zero value means
266    /// CQEs were lost, so their pending entries are never reclaimed and drain
267    /// never completes. Treated as fatal (C5, rustfs/backlog#1056).
268    pub cq_overflow: u64,
269}
270
271enum Msg {
272    Read {
273        id: u64,
274        file: Arc<File>,
275        offset: u64,
276        len: usize,
277        done: oneshot::Sender<io::Result<Vec<u8>>>,
278        /// Backpressure permit, acquired before the op reaches the driver and
279        /// released only when the pending entry is dropped at the final CQE
280        /// (rustfs/backlog#1060/#1102). If the driver rejects the op (shutting
281        /// down) the permit is dropped with the message — released immediately.
282        permit: OwnedSemaphorePermit,
283        /// Block size the read must be aligned to. `1` means a normal buffered
284        /// read; `> 1` means the file was opened `O_DIRECT` and the driver must
285        /// read the block-aligned superset range into a block-aligned buffer
286        /// (rustfs/backlog#1102).
287        align: usize,
288    },
289    Cancel {
290        id: u64,
291    },
292    Shutdown,
293}
294
295/// One in-flight LOGICAL read. This struct — not the caller — owns everything
296/// the kernel touches:
297///
298/// - `buf`: the destination buffer. Its heap allocation must stay put until
299///   the final CQE; the `Vec` itself may move (HashMap rehash) since that
300///   never relocates the heap block. It is never resized or dropped before
301///   the CQE handler removes this entry.
302/// - `file`: keeps the fd open even if every caller-side clone is dropped, and
303///   supplies the fd for short-read resubmission. Without it, dropping the
304///   future could close the fd while an SQE built from that fd still sits in
305///   the backlog (SQE construction → io_uring_enter window), and a recycled
306///   fd number would make the kernel read the WRONG file (spike finding, with
307///   the corrected mechanism per rustfs/backlog#1063).
308/// - `offset`/`nread`: track a short-read resubmit loop (C9,
309///   rustfs/backlog#1058). io_uring may legally short-read a regular file;
310///   the driver resubmits the remainder into `buf[nread..]` until the request
311///   is fully satisfied or a real EOF (res == 0) is seen, so reclamation
312///   happens only at the FINAL CQE of the logical read.
313/// - `_permit`: the backpressure permit. Holding it here makes the
314///   "release at the CQE, never at future drop" rule (rustfs/backlog#1060) a
315///   property of the type: the permit is dropped exactly when this entry is
316///   removed at the final CQE. A short-read resubmit keeps the entry, and thus
317///   the permit, so in-flight memory stays bounded.
318/// - Alignment geometry (rustfs/backlog#1102). For a buffered read these are
319///   `pad = head = 0`, `align = 1`, `region_len = want`, so every rule below
320///   collapses to the plain case. For an `O_DIRECT` read the driver reads the
321///   block-aligned superset `[offset, offset + region_len)` into
322///   `buf[pad .. pad + region_len]` (both block-aligned) and hands the caller
323///   only `buf[pad + head .. pad + head + want]` — alignment padding never
324///   escapes.
325struct Pending {
326    buf: Vec<u8>,
327    file: Arc<File>,
328    done: Option<oneshot::Sender<io::Result<Vec<u8>>>>,
329    /// Kernel read offset: the block-aligned offset for a direct read, the
330    /// logical offset for a buffered one, `CURRENT_POSITION` for a stream.
331    offset: u64,
332    /// Bytes already read into the read region (`buf[pad..]`).
333    nread: usize,
334    _permit: OwnedSemaphorePermit,
335    /// Offset inside `buf` where the block-aligned read region starts.
336    pad: usize,
337    /// Bytes of the read region that precede the caller's logical range.
338    head: usize,
339    /// Logical length the caller asked for.
340    want: usize,
341    /// Bytes the kernel is asked to read (block-aligned for a direct read).
342    region_len: usize,
343    /// `1` for buffered, the block size for `O_DIRECT`.
344    align: usize,
345}
346
347/// Where a [`ReadHandle`] is in its lifecycle (rustfs/backlog#1102).
348enum HandleState {
349    /// Nothing was ever handed to the driver (a rejected parameter, or the
350    /// driver was already gone). The result is already sitting in `rx`, and
351    /// there is no buffer, permit, or SQE to reclaim.
352    Inert,
353    /// Backpressure was saturated at `submit` time, so the permit — and with it
354    /// the submission — is deferred to the first poll. The caller's thread is
355    /// never blocked. Dropping the handle in this state submitted nothing.
356    WaitingPermit {
357        acquire: AcquireFut,
358        file: Arc<File>,
359        offset: u64,
360        len: usize,
361        align: usize,
362        done: oneshot::Sender<io::Result<Vec<u8>>>,
363        wake: Arc<EventFd>,
364    },
365    /// The op is with the driver: its buffer lives in the pending table and is
366    /// reclaimed only at the CQE.
367    Submitted,
368}
369
370/// Handle to a read. Await it for the result.
371///
372/// Dropping it before completion abandons the result only; if the op was
373/// already submitted it also sends `IORING_OP_ASYNC_CANCEL` (best effort) so the
374/// CQE — and with it the buffer reclamation — arrives sooner.
375/// `without_cancel_on_drop` disables that to model the bare "quorum drops the
376/// future" case.
377///
378/// Submission is eager whenever a backpressure permit is immediately available
379/// (the common case, unchanged from the blocking implementation). Only when the
380/// semaphore is saturated does the handle acquire the permit and submit on its
381/// first poll, so `submit` never blocks a runtime worker.
382pub struct ReadHandle {
383    id: u64,
384    rx: oneshot::Receiver<io::Result<Vec<u8>>>,
385    tx: mpsc::Sender<Msg>,
386    finished: bool,
387    cancel_on_drop: bool,
388    state: HandleState,
389}
390
391impl ReadHandle {
392    pub fn without_cancel_on_drop(mut self) -> Self {
393        self.cancel_on_drop = false;
394        self
395    }
396}
397
398impl Future for ReadHandle {
399    type Output = io::Result<Vec<u8>>;
400
401    fn poll(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
402        let this = &mut *self;
403
404        // Saturated at submit time: take the permit, then hand the op to the
405        // driver. The permit rides along in the message and is released only
406        // when the pending entry is dropped at the CQE.
407        let acquired = match &mut this.state {
408            HandleState::WaitingPermit { acquire, .. } => match acquire.as_mut().poll(cx) {
409                Poll::Pending => return Poll::Pending,
410                Poll::Ready(res) => Some(res),
411            },
412            _ => None,
413        };
414        if let Some(res) = acquired {
415            let Ok(permit) = res else {
416                // The semaphore was closed: the driver is gone.
417                this.finished = true;
418                return Poll::Ready(Err(io::Error::other("uring driver shut down")));
419            };
420            let HandleState::WaitingPermit {
421                file,
422                offset,
423                len,
424                align,
425                done,
426                wake,
427                ..
428            } = std::mem::replace(&mut this.state, HandleState::Submitted)
429            else {
430                unreachable!("state was WaitingPermit")
431            };
432            if this
433                .tx
434                .send(Msg::Read {
435                    id: this.id,
436                    file,
437                    offset,
438                    len,
439                    done,
440                    permit,
441                    align,
442                })
443                .is_err()
444            {
445                // Driver gone between the acquire and the send; the message
446                // (with its permit) is dropped, releasing it.
447                this.finished = true;
448                return Poll::Ready(Err(io::Error::other("uring driver shut down")));
449            }
450            wake.signal();
451        }
452
453        match Pin::new(&mut this.rx).poll(cx) {
454            Poll::Ready(res) => {
455                this.finished = true;
456                Poll::Ready(match res {
457                    Ok(inner) => inner,
458                    Err(_) => Err(io::Error::other("uring driver shut down before completion")),
459                })
460            }
461            Poll::Pending => Poll::Pending,
462        }
463    }
464}
465
466impl Drop for ReadHandle {
467    fn drop(&mut self) {
468        // The buffer is deliberately NOT touched here: the driver owns it
469        // until the CQE. All we may do is ask the kernel to hurry up. A handle
470        // dropped before it was submitted (Inert / WaitingPermit) has no buffer,
471        // no permit and no SQE, so there is nothing to cancel.
472        if matches!(self.state, HandleState::Submitted) && !self.finished && self.cancel_on_drop {
473            let _ = self.tx.send(Msg::Cancel { id: self.id });
474        }
475    }
476}
477
478/// Process-level io_uring driver: one ring, one driver thread.
479/// One io_uring ring plus the thread that drives it.
480///
481/// Every cancel-safety invariant holds *per shard*, exactly as it did when a
482/// driver owned a single ring: this shard's pending table owns its buffers and
483/// fds until their CQEs, its permits are released only when a pending entry is
484/// dropped, and its bounded drain is what shutdown joins on. A `ReadHandle`
485/// carries the `tx` and `wake` of the shard that accepted it, so a cancel or a
486/// deferred submission always routes back to that same shard.
487struct Shard {
488    tx: mpsc::Sender<Msg>,
489    handle: Option<JoinHandle<()>>,
490    stats: Arc<DriverStats>,
491    /// Backpressure permits (one per allowed in-flight op on this ring). Closed
492    /// when the driver thread exits so any waiting `ReadHandle` resolves with a
493    /// driver-gone error instead of hanging (rustfs/backlog#1102).
494    sem: Arc<Semaphore>,
495    /// Signaled after every message send so this shard's loop wakes immediately
496    /// instead of waiting out the heartbeat (backlog#1102).
497    wake_efd: Arc<EventFd>,
498}
499
500impl Shard {
501    /// Ask the shard's thread to drain and exit, then join it. Idempotent: the
502    /// `JoinHandle` is taken, so a later `Drop` is a no-op.
503    fn join(&mut self) {
504        if let Some(h) = self.handle.take() {
505            let _ = self.tx.send(Msg::Shutdown);
506            self.wake_efd.signal();
507            let _ = h.join();
508        }
509    }
510}
511
512impl Drop for Shard {
513    fn drop(&mut self) {
514        self.join();
515    }
516}
517
518pub struct UringDriver {
519    /// One or more independent rings. A cache-hit buffered read completes inline
520    /// inside `io_uring_enter`, so the thread driving a ring performs that
521    /// read's memcpy — which caps a single-ring driver at one core's memory
522    /// bandwidth (~5 GB/s measured, rustfs/backlog#1145). Sharding lifts that
523    /// ceiling roughly linearly while keeping the ring set per-disk, so a stalled
524    /// disk still cannot starve another disk's rings (rustfs/backlog#1055).
525    shards: Vec<Shard>,
526    next_id: AtomicU64,
527    /// Round-robin cursor for shard selection. Relaxed: it only has to spread
528    /// ops, never to order them.
529    rr: AtomicUsize,
530}
531
532impl UringDriver {
533    /// Create the ring AND verify a real `IORING_OP_READ` round-trip on a
534    /// temp file before accepting work. `io_uring_setup` succeeding is not
535    /// enough: gVisor/seccomp environments can create a ring whose ops then
536    /// fail with ENOSYS/EINVAL (backlog#894 probe design).
537    /// Start a single-ring driver. Identical to `probe_and_start_sharded(entries, 1)`.
538    pub fn probe_and_start(entries: u32) -> Result<Self, ProbeFailure> {
539        Self::probe_and_start_sharded(entries, 1)
540    }
541
542    /// Start a driver backed by `shards` independent rings, each with `entries`
543    /// SQ slots and its own driver thread.
544    ///
545    /// Use more than one shard when the workload hits the page cache: such reads
546    /// complete inline in `io_uring_enter`, so a single driver thread performs
547    /// every one of their memcpys and caps the driver at one core's memory
548    /// bandwidth. Measured on a 16-core host (rustfs/backlog#1145): 1 ring →
549    /// 4890 MB/s, 2 → 8969 MB/s, 4 → 15806 MB/s, with per-ring throughput flat.
550    /// Reads that miss the cache are device-bound and do not need sharding.
551    ///
552    /// In-flight ops are capped at `entries` *per shard* (the invariant that
553    /// makes CQ overflow structurally unreachable holds per ring), so the whole
554    /// driver admits up to `shards * entries` concurrent reads.
555    ///
556    /// `shards` is clamped to at least 1. Probing happens on the first shard, so
557    /// a restricted environment fails exactly as it does for a single ring; if a
558    /// later shard fails to start, the ones already running are shut down and
559    /// joined before the error is returned.
560    pub fn probe_and_start_sharded(entries: u32, shards: usize) -> Result<Self, ProbeFailure> {
561        let mut started = Vec::with_capacity(shards.max(1));
562        for _ in 0..shards.max(1) {
563            // `?` drops `started`, whose `Shard::drop` joins each running thread.
564            started.push(Self::start_shard(entries)?);
565        }
566        Ok(Self {
567            shards: started,
568            next_id: AtomicU64::new(1),
569            rr: AtomicUsize::new(0),
570        })
571    }
572
573    /// Pick the shard for the next op. Round-robin spreads the inline-completion
574    /// memcpy across driver threads; correctness does not depend on the choice,
575    /// because the handle remembers which shard took the op.
576    fn shard(&self) -> &Shard {
577        let n = self.shards.len();
578        &self.shards[self.rr.fetch_add(1, Ordering::Relaxed) % n]
579    }
580
581    fn start_shard(entries: u32) -> Result<Shard, ProbeFailure> {
582        let mut ring = IoUring::new(entries).map_err(ProbeFailure::Setup)?;
583        // Require the NODROP feature (kernel >= 5.5). Without it, CQ overflow
584        // silently drops CQEs, stranding pending entries forever and hanging
585        // shutdown (C5, rustfs/backlog#1056). ENOSYS is in the expected-
586        // restriction class, so this degrades to the std backend cleanly.
587        if !ring.params().is_feature_nodrop() {
588            return Err(ProbeFailure::Setup(io::Error::from_raw_os_error(libc::ENOSYS)));
589        }
590        probe_real_read(&mut ring).map_err(ProbeFailure::ReadOp)?;
591
592        // Wake the driver loop on CQEs (kernel-signaled via a registered
593        // eventfd) and on new messages (submit-signaled), replacing the 200 µs
594        // busy-poll (backlog#1102). Registration needs the ring, which the
595        // driver thread then owns; `cq_efd` is moved in alongside so it outlives
596        // the ring (dropped after it, unregistering cleanly).
597        let cq_efd = EventFd::new().map_err(ProbeFailure::Setup)?;
598        ring.submitter()
599            .register_eventfd(cq_efd.as_raw())
600            .map_err(ProbeFailure::Setup)?;
601        let wake_efd = Arc::new(EventFd::new().map_err(ProbeFailure::Setup)?);
602        let thread_wake = Arc::clone(&wake_efd);
603
604        let (tx, rx) = mpsc::channel();
605        let stats = Arc::new(DriverStats::default());
606        let thread_stats = Arc::clone(&stats);
607        // Cap in-flight at the SQ depth (entries), which is < CQ capacity
608        // (2*entries), so CQ overflow is structurally unreachable (C5/C10).
609        let sem = Arc::new(Semaphore::new(entries as usize));
610        let thread_sem = Arc::clone(&sem);
611        let handle = std::thread::Builder::new()
612            .name("uring-spike-driver".into())
613            .spawn(move || drive(ring, rx, thread_stats, thread_sem, cq_efd, thread_wake))
614            .expect("spawn driver thread");
615
616        Ok(Shard {
617            tx,
618            handle: Some(handle),
619            stats,
620            sem,
621            wake_efd,
622        })
623    }
624
625    /// Positioned read (pread semantics) — regular files, buffered.
626    pub fn read_at(&self, file: Arc<File>, offset: u64, len: usize) -> ReadHandle {
627        assert_ne!(offset, CURRENT_POSITION, "offset u64::MAX is reserved");
628        self.submit(file, offset, len, 1)
629    }
630
631    /// Read at the file's current position (read(2) semantics) — pipes.
632    pub fn read_current(&self, file: Arc<File>, len: usize) -> ReadHandle {
633        self.submit(file, CURRENT_POSITION, len, 1)
634    }
635
636    /// Positioned read from a file opened with `O_DIRECT` (rustfs/backlog#1102).
637    ///
638    /// `align` is the device's logical block size — a power of two, typically
639    /// 512 or 4096. `offset` and `len` are the caller's *logical* range and need
640    /// no alignment: the driver reads the block-aligned superset range into a
641    /// block-aligned buffer and returns exactly `[offset, offset + len)`.
642    /// Alignment padding never reaches the caller, so a `BitrotReader` expecting
643    /// an exact shard length never sees padded output.
644    ///
645    /// The caller must have opened `file` with `O_DIRECT`; otherwise this is
646    /// just a (correct but pointless) buffered read of the superset range.
647    pub fn read_at_direct(&self, file: Arc<File>, offset: u64, len: usize, align: usize) -> ReadHandle {
648        assert_ne!(offset, CURRENT_POSITION, "offset u64::MAX is reserved");
649        self.submit(file, offset, len, align)
650    }
651
652    fn submit(&self, file: Arc<File>, offset: u64, len: usize, align: usize) -> ReadHandle {
653        let id = self.next_id.fetch_add(1, Ordering::Relaxed);
654        assert_eq!(id & CANCEL_BIT, 0, "op id overflowed into the cancel bit");
655        let (done, rx) = oneshot::channel();
656
657        // Bind the op to one shard for its whole life: the permit, the message,
658        // the wake, and any later cancel all go to this ring. The handle holds
659        // clones of that shard's `tx`/`wake_efd`, so nothing can route a cancel
660        // to a ring whose pending table does not hold the op. The rejection paths
661        // below return an `Inert` handle that never sends, but still need a `tx`.
662        let shard = self.shard();
663
664        // Reject an offset the kernel would answer with a runtime EINVAL that
665        // must NOT be mistaken for an environment restriction (C7,
666        // rustfs/backlog#1059). The kernel reads `off` as a signed loff_t, so
667        // offset > i64::MAX becomes a negative ki_pos → EINVAL. A caller
668        // offset-arithmetic bug has to surface as an error here, never as a
669        // permanent per-disk fallback. CURRENT_POSITION is the reserved
670        // read(2) sentinel and bypasses this check.
671        if offset != CURRENT_POSITION && offset > i64::MAX as u64 {
672            let _ = done.send(Err(io::Error::new(
673                io::ErrorKind::InvalidInput,
674                "offset exceeds i64::MAX (kernel loff_t is signed)",
675            )));
676            return ReadHandle {
677                id,
678                rx,
679                tx: shard.tx.clone(),
680                finished: false,
681                cancel_on_drop: false,
682                state: HandleState::Inert,
683            };
684        }
685
686        // Reject a length the kernel would short-read past MAX_RW_COUNT and
687        // that the SQE's u32 `len` field would silently truncate: len == 2^32
688        // becomes a 0-byte read the caller decodes as a false EOF (C6,
689        // rustfs/backlog#1057). Failing fast here also removes the caller-
690        // controlled `vec![0u8; len]` capacity-overflow panic that made the
691        // unwind-UAF (rustfs/backlog#1054) reachable. P2 must chunk instead.
692        if len > MAX_READ_LEN {
693            let _ = done.send(Err(io::Error::new(
694                io::ErrorKind::InvalidInput,
695                "read length exceeds MAX_RW_COUNT (2 GiB - 4 KiB); caller must chunk",
696            )));
697            return ReadHandle {
698                id,
699                rx,
700                tx: shard.tx.clone(),
701                finished: false,
702                cancel_on_drop: false,
703                state: HandleState::Inert,
704            };
705        }
706
707        // Reject a bad O_DIRECT alignment, and a request whose block-aligned
708        // superset range would exceed the kernel's single-read cap
709        // (rustfs/backlog#1102). `align == 1` (buffered) always passes.
710        match aligned_geometry(offset, len, align) {
711            Some((_, _, region_len)) if region_len <= MAX_READ_LEN => {}
712            _ => {
713                let _ = done.send(Err(io::Error::new(
714                    io::ErrorKind::InvalidInput,
715                    "alignment must be a power of two and the block-aligned range must fit MAX_RW_COUNT",
716                )));
717                return ReadHandle {
718                    id,
719                    rx,
720                    tx: shard.tx.clone(),
721                    finished: false,
722                    cancel_on_drop: false,
723                    state: HandleState::Inert,
724                };
725            }
726        }
727
728        // Take a backpressure permit BEFORE the op reaches the driver; it is
729        // released only when the pending entry is dropped at the CQE (C10,
730        // rustfs/backlog#1060). Acquisition never blocks the caller's thread
731        // (rustfs/backlog#1102).
732        match Arc::clone(&shard.sem).try_acquire_owned() {
733            // Fast path: a permit was free, so submit eagerly — no allocation,
734            // no await, and the op is in flight the moment `submit` returns,
735            // exactly as with the previous blocking implementation.
736            Ok(permit) => {
737                if let Err(mpsc::SendError(msg)) = shard.tx.send(Msg::Read {
738                    id,
739                    file,
740                    offset,
741                    len,
742                    done,
743                    permit,
744                    align,
745                }) {
746                    // Driver gone: the op never reached it. Surface an explicit
747                    // driver-gone error through `done` instead of letting the
748                    // caller infer one from the dropped oneshot, matching the
749                    // `Closed` arm below. The permit rides back in `msg` and is
750                    // released when it drops here.
751                    if let Msg::Read { done, .. } = msg {
752                        let _ = done.send(Err(io::Error::other("uring driver shut down")));
753                    }
754                    return ReadHandle {
755                        id,
756                        rx,
757                        tx: shard.tx.clone(),
758                        finished: false,
759                        cancel_on_drop: false,
760                        state: HandleState::Inert,
761                    };
762                }
763                // Wake the driver loop so the read starts immediately.
764                shard.wake_efd.signal();
765                ReadHandle {
766                    id,
767                    rx,
768                    tx: shard.tx.clone(),
769                    finished: false,
770                    cancel_on_drop: true,
771                    state: HandleState::Submitted,
772                }
773            }
774            // Saturated: `entries` ops are already in flight. Do NOT block the
775            // calling (runtime worker) thread — hand the acquire future to the
776            // handle, which awaits it on its first poll and submits then.
777            Err(TryAcquireError::NoPermits) => ReadHandle {
778                id,
779                rx,
780                tx: shard.tx.clone(),
781                finished: false,
782                cancel_on_drop: true,
783                state: HandleState::WaitingPermit {
784                    acquire: Box::pin(Arc::clone(&shard.sem).acquire_owned()),
785                    file,
786                    offset,
787                    len,
788                    align,
789                    done,
790                    wake: Arc::clone(&shard.wake_efd),
791                },
792            },
793            // The driver has exited and closed the semaphore.
794            Err(TryAcquireError::Closed) => {
795                let _ = done.send(Err(io::Error::other("uring driver shut down")));
796                ReadHandle {
797                    id,
798                    rx,
799                    tx: shard.tx.clone(),
800                    finished: false,
801                    cancel_on_drop: false,
802                    state: HandleState::Inert,
803                }
804            }
805        }
806    }
807
808    /// Counters summed across every shard. The conservation identities the
809    /// cancel-safety tests assert (`submitted == delivered + orphan_reclaimed`,
810    /// `in_flight == 0` after a clean drain) hold per shard, so they hold for
811    /// the sum.
812    pub fn stats(&self) -> StatsSnapshot {
813        let mut snap = StatsSnapshot::default();
814        for shard in &self.shards {
815            let s = &shard.stats;
816            snap.submitted += s.submitted.load(Ordering::SeqCst);
817            snap.delivered += s.delivered.load(Ordering::SeqCst);
818            snap.orphan_reclaimed += s.orphan_reclaimed.load(Ordering::SeqCst);
819            snap.in_flight += s.in_flight.load(Ordering::SeqCst);
820            snap.cancel_succeeded += s.cancel_succeeded.load(Ordering::SeqCst);
821            snap.cancel_not_found += s.cancel_not_found.load(Ordering::SeqCst);
822            snap.cancel_already += s.cancel_already.load(Ordering::SeqCst);
823            snap.cq_overflow += s.cq_overflow.load(Ordering::SeqCst);
824        }
825        snap
826    }
827
828    /// Stop accepting work, cancel all in-flight ops, drain every ring to
829    /// `in_flight == 0`, then join each driver thread. Only after that is a ring
830    /// dropped/unmapped — the shutdown ordering P2 requires, per shard.
831    ///
832    /// Shards are asked to stop first and joined afterwards, so their bounded
833    /// drains overlap instead of serializing `shards * DRAIN_TIMEOUT`.
834    pub fn shutdown(mut self) -> StatsSnapshot {
835        for shard in &self.shards {
836            let _ = shard.tx.send(Msg::Shutdown);
837            shard.wake_efd.signal();
838        }
839        for shard in &mut self.shards {
840            shard.join();
841        }
842        let snap = self.stats();
843        // A clean drain leaves in_flight == 0. A non-zero count here means some
844        // shard's bounded drain bailed out on a hung device and leaked its
845        // ring+buffers to stay memory-safe (C4, rustfs/backlog#1055) — a degraded
846        // but safe outcome, not a panic. Callers/tests that require a clean drain
847        // assert on the returned snapshot themselves.
848        if snap.in_flight != 0 {
849            eprintln!(
850                "uring-spike shutdown: {} ops still in flight (bounded-drain bailout on a hung device)",
851                snap.in_flight
852            );
853        }
854        snap
855    }
856}
857
858impl Drop for UringDriver {
859    fn drop(&mut self) {
860        // Ask every shard to stop before joining any of them, so their bounded
861        // drains overlap. Dropping the `Vec<Shard>` would instead run each
862        // `Shard::drop` in turn, serializing up to `shards * DRAIN_TIMEOUT` on a
863        // hung device. `Shard::join` is idempotent, so the later drops are no-ops.
864        for shard in &self.shards {
865            let _ = shard.tx.send(Msg::Shutdown);
866            shard.wake_efd.signal();
867        }
868        for shard in &mut self.shards {
869            shard.join();
870        }
871    }
872}
873
874fn probe_real_read(ring: &mut IoUring) -> io::Result<()> {
875    let pattern: Vec<u8> = (0..512u32).map(|i| (i * 7 + 13) as u8).collect();
876
877    // Open an anonymous probe file seeded with the pattern. File setup runs
878    // BEFORE any SQE, so its errors early-return safely — nothing is in flight.
879    let file = open_probe_file(&pattern)?;
880
881    let mut buf = vec![0u8; pattern.len()];
882    let sqe = opcode::Read::new(types::Fd(file.as_raw_fd()), buf.as_mut_ptr(), buf.len() as u32)
883        .offset(0)
884        .build()
885        .user_data(0xB0BE);
886
887    // SAFETY: a push failure means the kernel never accepted the SQE, so
888    // `buf`/`file` may be dropped safely on this early return.
889    if unsafe { ring.submission().push(&sqe) }.is_err() {
890        return Err(io::Error::other("probe: submission queue full"));
891    }
892
893    // C1 (rustfs/backlog#1053): once the SQE is handed to the kernel, the read
894    // may be punted to io-wq and write into `buf` at ANY later point. Until its
895    // CQE arrives, `buf`/`file` must NOT be dropped and the ring must NOT be
896    // unmapped — otherwise the kernel writes into freed memory (UAF). The probe
897    // path has no pending-table backstop, so we must drain to the CQE here, and
898    // any early exit first leaks the buffer ("leak over UAF").
899    let res = match drain_probe_cqe(ring) {
900        Ok(res) => res,
901        Err(e) => {
902            // Could not confirm the op terminated: leak `buf` (the real UAF
903            // hazard — the kernel may still write 512 bytes into it) and,
904            // defensively, `file`. Leaking one 512-byte startup-probe buffer is
905            // trivially cheaper than a silent heap corruption.
906            std::mem::forget(buf);
907            std::mem::forget(file);
908            return Err(e);
909        }
910    };
911
912    // The CQE has arrived: the kernel is done with `buf`, so dropping it and
913    // `file` below is now safe.
914    if res < 0 {
915        Err(io::Error::from_raw_os_error(-res))
916    } else if res as usize != pattern.len() || buf != pattern {
917        Err(io::Error::other("probe: read completed but data mismatched"))
918    } else {
919        Ok(())
920    }
921}
922
923/// Open a probe file seeded with `pattern`, avoiding the symlink/TOCTOU/
924/// leftover hazards of a predictable temp path (C3, rustfs/backlog#1061).
925///
926/// Primary: `O_TMPFILE` — an anonymous inode with no name at all, so there is
927/// nothing for an attacker to pre-plant a symlink at, no TOCTOU window, and no
928/// leftover file. Fallback (filesystems without O_TMPFILE): create in the temp
929/// dir with `O_CREAT|O_EXCL|O_NOFOLLOW` + 0600 + a per-process nonce, then
930/// unlink immediately so no attacker-planted symlink is followed and no named
931/// file survives.
932fn open_probe_file(pattern: &[u8]) -> io::Result<File> {
933    let dir = std::env::temp_dir();
934    let c_dir = std::ffi::CString::new(dir.as_os_str().as_bytes()).map_err(|_| io::Error::other("probe dir path has NUL"))?;
935    // SAFETY: `c_dir` is a valid NUL-terminated path; O_TMPFILE requires a
936    // directory and O_RDWR/O_WRONLY. On success we own the returned fd.
937    let fd = unsafe { libc::open(c_dir.as_ptr(), libc::O_TMPFILE | libc::O_RDWR | libc::O_CLOEXEC, 0o600) };
938    if fd >= 0 {
939        let mut file = unsafe { File::from_raw_fd(fd) };
940        file.write_all(pattern)?;
941        return Ok(file);
942    }
943    open_probe_file_exclusive(&dir, pattern)
944}
945
946fn open_probe_file_exclusive(dir: &std::path::Path, pattern: &[u8]) -> io::Result<File> {
947    static SEQ: AtomicU64 = AtomicU64::new(0);
948    let nonce = SEQ.fetch_add(1, Ordering::Relaxed);
949    let path = dir.join(format!("uring-spike-probe-{}-{}", std::process::id(), nonce));
950    let c_path = std::ffi::CString::new(path.as_os_str().as_bytes()).map_err(|_| io::Error::other("probe path has NUL"))?;
951    // O_EXCL refuses a pre-existing file; O_NOFOLLOW refuses a symlink; 0600 is
952    // owner-only. SAFETY: `c_path` is a valid NUL-terminated path; on success
953    // we own the fd.
954    let fd = unsafe {
955        libc::open(
956            c_path.as_ptr(),
957            libc::O_CREAT | libc::O_EXCL | libc::O_NOFOLLOW | libc::O_RDWR | libc::O_CLOEXEC,
958            0o600,
959        )
960    };
961    if fd < 0 {
962        return Err(io::Error::last_os_error());
963    }
964    let mut file = unsafe { File::from_raw_fd(fd) };
965    file.write_all(pattern)?;
966    // Unlink now: the fd stays valid, no named leftover remains.
967    // SAFETY: `c_path` is still a valid NUL-terminated path.
968    unsafe {
969        libc::unlink(c_path.as_ptr());
970    }
971    Ok(file)
972}
973
974/// Wait for the probe SQE's CQE and return its raw result.
975///
976/// The SQE has already been pushed; this only drains it. `submit_and_wait`
977/// interrupted by a signal returns EINTR — since the kernel consumed the SQE
978/// atomically before the wait phase, we retry the WAIT only and never re-push
979/// (C8, backlog#1059). A bounded attempt count keeps a probe that hit a hung
980/// device from blocking forever; exhausting it returns an error that drives
981/// the caller's leak-over-UAF fallback.
982fn drain_probe_cqe(ring: &mut IoUring) -> io::Result<i32> {
983    const MAX_WAIT_ATTEMPTS: u32 = 4096;
984    for _ in 0..MAX_WAIT_ATTEMPTS {
985        match ring.submit_and_wait(1) {
986            Ok(_) => {}
987            // Signal interrupted the wait; the SQE is already in flight, so
988            // just wait again (do NOT re-push).
989            Err(e) if e.raw_os_error() == Some(libc::EINTR) => {}
990            Err(e) => return Err(e),
991        }
992        if let Some(cqe) = ring.completion().next() {
993            return Ok(cqe.result());
994        }
995    }
996    Err(io::Error::other("probe: no CQE after bounded wait"))
997}
998
999/// Owns everything the kernel can still be writing into: the ring, the
1000/// pending (orphan) table of in-flight buffers, and the SQE backlog.
1001///
1002/// C2 (rustfs/backlog#1054): the "CQE is the only reclamation point"
1003/// invariant holds only while the driver thread does NOT unwind. On a panic,
1004/// Rust would drop the pending table (freeing every in-flight buffer) while
1005/// the kernel may still write into them → mass UAF; reversing drop order does
1006/// not help because io_uring teardown on ring drop is asynchronous and does
1007/// not wait for in-flight ops. So this type's `Drop` refuses to run field
1008/// destructors during an unwind: it aborts the process first, leaving the
1009/// ring mapped and the buffers allocated (leak over UAF). A storage read path
1010/// silently corrupting memory is worse than a crash.
1011struct DriverState {
1012    ring: IoUring,
1013    pending: HashMap<u64, Pending>,
1014    backlog: VecDeque<io_uring::squeue::Entry>,
1015}
1016
1017impl Drop for DriverState {
1018    fn drop(&mut self) {
1019        if std::thread::panicking() {
1020            // Abort BEFORE any field destructor runs: the ring stays mapped
1021            // and the in-flight buffers stay allocated, so the kernel can
1022            // never write into freed memory.
1023            eprintln!(
1024                "uring-spike driver thread panicked with {} ops in flight; \
1025                 aborting to avoid UAF of in-flight buffers",
1026                self.pending.len()
1027            );
1028            std::process::abort();
1029        }
1030        // Normal drop: the shutdown invariant guarantees pending/backlog are
1031        // empty and in_flight == 0, so unmapping the ring here is safe.
1032    }
1033}
1034
1035/// Hand the caller exactly the logical range `[head, head + want)` of the read
1036/// region, truncated to what was actually read (rustfs/backlog#1102).
1037///
1038/// Alignment padding (`buf[..pad]`), the bytes before the logical range
1039/// (`head`), and the block-aligned tail after it never reach the caller — a
1040/// `BitrotReader` expecting an exact shard length would flag padded output as
1041/// corruption. Only bytes the kernel actually wrote are exposed: `avail` is
1042/// clamped to `nread`, so the zero-filled remainder of the buffer stays hidden
1043/// (content hygiene, C12 / rustfs/backlog#1062).
1044fn deliver(p: &mut Pending) -> Vec<u8> {
1045    let avail = p.nread.saturating_sub(p.head).min(p.want);
1046    let start = p.pad + p.head;
1047    // The buffered path (`align == 1`) has `pad == 0` and `head == 0`, so the
1048    // logical range already starts at byte 0 — skip the full-buffer memmove and
1049    // just truncate. Only the O_DIRECT path (nonzero start) needs the shift.
1050    if start != 0 && avail != 0 {
1051        p.buf.copy_within(start..start + avail, 0);
1052    }
1053    p.buf.truncate(avail);
1054    std::mem::take(&mut p.buf)
1055}
1056
1057/// What to do with a pending entry after its CQE (C9, rustfs/backlog#1058).
1058enum ReapStep {
1059    /// The logical read is done: remove the entry and deliver this result.
1060    Finish(io::Result<Vec<u8>>),
1061    /// Short read, not EOF: re-queue this SQE for the remainder; keep the entry.
1062    Resubmit(io_uring::squeue::Entry),
1063}
1064
1065fn drive(
1066    ring: IoUring,
1067    rx: mpsc::Receiver<Msg>,
1068    stats: Arc<DriverStats>,
1069    sem: Arc<Semaphore>,
1070    cq_efd: EventFd,
1071    wake_efd: Arc<EventFd>,
1072) {
1073    let mut state = DriverState {
1074        ring,
1075        pending: HashMap::new(),
1076        backlog: VecDeque::new(),
1077    };
1078    let mut shutting_down = false;
1079    let mut drain_deadline: Option<Instant> = None;
1080
1081    loop {
1082        // Block until a CQE is ready (the ring's registered eventfd), a new
1083        // message arrives (the wakeup eventfd), or the heartbeat elapses —
1084        // this replaces the spike's 200 µs busy-poll (backlog#1102). Draining
1085        // both eventfds after waking keeps them from staying spuriously
1086        // readable; a missed edge is harmless because the CQ/mpsc are re-checked
1087        // unconditionally below.
1088        wait_for_events(&cq_efd, &wake_efd, LOOP_HEARTBEAT);
1089        cq_efd.drain();
1090        wake_efd.drain();
1091
1092        // 1. Intake: drain all queued messages (the wait above did the blocking,
1093        //    so this is purely non-blocking).
1094        loop {
1095            let msg = match rx.try_recv() {
1096                Ok(m) => m,
1097                Err(TryRecvError::Empty) => break,
1098                Err(TryRecvError::Disconnected) => {
1099                    shutting_down = true;
1100                    break;
1101                }
1102            };
1103            match msg {
1104                Msg::Read {
1105                    id,
1106                    file,
1107                    offset,
1108                    len,
1109                    done,
1110                    permit,
1111                    align,
1112                } => {
1113                    if shutting_down {
1114                        let _ = done.send(Err(io::Error::other("uring driver shutting down")));
1115                        // The op never became in-flight; dropping `permit` here
1116                        // returns it immediately.
1117                        drop(permit);
1118                        continue;
1119                    }
1120                    // `submit` already validated this geometry.
1121                    let (kernel_offset, head, region_len) =
1122                        aligned_geometry(offset, len, align).expect("submit validated the geometry");
1123                    // For an O_DIRECT read the kernel needs a block-aligned
1124                    // buffer, so over-allocate by `align - 1` and start the read
1125                    // region at the first aligned byte inside the allocation.
1126                    // For a buffered read this degenerates to `vec![0u8; len]`.
1127                    // `submit` already capped `align <= MAX_READ_LEN` and
1128                    // `region_len <= MAX_READ_LEN`, so this add cannot overflow;
1129                    // the checked form keeps the invariant explicit rather than
1130                    // relying on it silently.
1131                    let cap = match region_len.checked_add(align - 1) {
1132                        Some(cap) => cap,
1133                        None => {
1134                            let _ = done.send(Err(io::Error::other("aligned O_DIRECT allocation size overflow")));
1135                            drop(permit);
1136                            continue;
1137                        }
1138                    };
1139                    let mut buf = vec![0u8; cap];
1140                    let pad = buf.as_ptr().align_offset(align);
1141                    // Runtime guard (not a debug-only assert): if the allocator
1142                    // ever returned a block `align_offset` cannot satisfy, refuse
1143                    // the read instead of doing UB pointer arithmetic below.
1144                    if pad == usize::MAX || pad.checked_add(region_len).is_none_or(|end| end > buf.len()) {
1145                        let _ = done.send(Err(io::Error::other("could not align O_DIRECT read buffer")));
1146                        drop(permit);
1147                        continue;
1148                    }
1149
1150                    // The raw pointer is captured before `buf` moves into the
1151                    // table; moving the Vec never relocates its heap block, and
1152                    // the entry is only removed at the CQE. `region_len as u32`
1153                    // is lossless: `submit` rejected anything > MAX_READ_LEN.
1154                    //
1155                    // SAFETY: `pad <= align - 1` and `pad + region_len <=
1156                    // buf.len()`, so the pointer stays inside the allocation.
1157                    let region_ptr = unsafe { buf.as_mut_ptr().add(pad) };
1158                    let sqe = opcode::Read::new(types::Fd(file.as_raw_fd()), region_ptr, region_len as u32)
1159                        .offset(kernel_offset)
1160                        .build()
1161                        .user_data(id);
1162                    state.pending.insert(
1163                        id,
1164                        Pending {
1165                            buf,
1166                            file,
1167                            done: Some(done),
1168                            offset: kernel_offset,
1169                            nread: 0,
1170                            // Released exactly when this entry is removed at the
1171                            // final CQE — never at future drop (backlog#1060).
1172                            _permit: permit,
1173                            pad,
1174                            head,
1175                            want: len,
1176                            region_len,
1177                            align,
1178                        },
1179                    );
1180                    stats.submitted.fetch_add(1, Ordering::SeqCst);
1181                    stats.in_flight.fetch_add(1, Ordering::SeqCst);
1182                    state.backlog.push_back(sqe);
1183                }
1184                Msg::Cancel { id } => {
1185                    if state.pending.contains_key(&id) {
1186                        state
1187                            .backlog
1188                            .push_back(opcode::AsyncCancel::new(id).build().user_data(id | CANCEL_BIT));
1189                    }
1190                }
1191                Msg::Shutdown => {
1192                    shutting_down = true;
1193                    for id in state.pending.keys() {
1194                        state
1195                            .backlog
1196                            .push_back(opcode::AsyncCancel::new(*id).build().user_data(*id | CANCEL_BIT));
1197                    }
1198                }
1199            }
1200        }
1201
1202        // 2. Push backlog into the SQ (stop when full; retry next turn).
1203        {
1204            let mut sq = state.ring.submission();
1205            while let Some(sqe) = state.backlog.pop_front() {
1206                // SAFETY: read SQEs point into `pending`-owned buffers that
1207                // live until their CQE; cancel SQEs carry no pointers.
1208                if unsafe { sq.push(&sqe) }.is_err() {
1209                    state.backlog.push_front(sqe);
1210                    break;
1211                }
1212            }
1213        }
1214        match state.ring.submit() {
1215            Ok(_) => {}
1216            Err(e) if e.raw_os_error() == Some(libc::EBUSY) => {
1217                // CQ-overflow backpressure on pre-5.19 NODROP kernels: the
1218                // kernel refuses new submissions until we reap. Keep the
1219                // backlog and reap this turn instead of spinning (C5,
1220                // rustfs/backlog#1056).
1221            }
1222            Err(_) => {
1223                // EINTR and friends: retry on the next loop turn.
1224            }
1225        }
1226
1227        // 3. Reap. A Pending entry (and thus its buffer) is dropped ONLY when
1228        //    the logical read finishes; a short read is resubmitted for the
1229        //    remainder and the entry stays put (C9, rustfs/backlog#1058).
1230        while let Some(cqe) = state.ring.completion().next() {
1231            let ud = cqe.user_data();
1232            if ud & CANCEL_BIT != 0 {
1233                // Result of the AsyncCancel op itself; the read's own CQE
1234                // (ECANCELED or success) still arrives separately. Record the
1235                // three-state outcome for diagnosability (C4,
1236                // rustfs/backlog#1055): EALREADY means the read is executing
1237                // and cannot be interrupted, i.e. its CQE may never come on a
1238                // hung device — the signal the bounded drain below relies on.
1239                match cqe.result() {
1240                    0 => stats.cancel_succeeded.fetch_add(1, Ordering::SeqCst),
1241                    r if r == -libc::ENOENT => stats.cancel_not_found.fetch_add(1, Ordering::SeqCst),
1242                    r if r == -libc::EALREADY => stats.cancel_already.fetch_add(1, Ordering::SeqCst),
1243                    _ => 0,
1244                };
1245                continue;
1246            }
1247            let res = cqe.result();
1248            if !state.pending.contains_key(&ud) {
1249                continue;
1250            }
1251
1252            // Decide the next step while borrowing the entry, then act after
1253            // the borrow ends (finish removes it; resubmit re-queues an SQE).
1254            let step = {
1255                let p = state.pending.get_mut(&ud).expect("checked above");
1256                if res < 0 {
1257                    // Error (incl. ECANCELED) terminates the logical read.
1258                    ReapStep::Finish(Err(io::Error::from_raw_os_error(-res)))
1259                } else if res == 0 {
1260                    // Real EOF: deliver whatever of the logical range was read.
1261                    ReapStep::Finish(Ok(deliver(p)))
1262                } else {
1263                    p.nread += res as usize;
1264                    // Only POSITIONED reads (read_at / read_at_direct, whole-range
1265                    // pread contract) resubmit a short read. CURRENT_POSITION
1266                    // reads (read_current on pipes/streams) follow read(2)
1267                    // semantics: a short read is a valid final result and must be
1268                    // delivered as-is — resubmitting would block forever waiting
1269                    // for stream data that may never come.
1270                    let is_stream = p.offset == CURRENT_POSITION;
1271                    let covered = p.nread >= p.head + p.want;
1272                    // An O_DIRECT resubmit must stay block-aligned. The kernel
1273                    // returns block multiples except at the file tail, so a
1274                    // non-multiple means we reached EOF: stop and deliver.
1275                    let unaligned_tail = p.align > 1 && !p.nread.is_multiple_of(p.align);
1276                    if is_stream || covered || unaligned_tail || p.nread >= p.region_len {
1277                        ReapStep::Finish(Ok(deliver(p)))
1278                    } else {
1279                        // Positioned short read, not EOF: resubmit the remainder
1280                        // into the read region. The buffer stays owned by the
1281                        // driver and in_flight is unchanged — one logical op.
1282                        // For a direct read, `pad + nread` and `offset + nread`
1283                        // are both block-aligned, as is `remaining`.
1284                        let remaining = p.region_len - p.nread;
1285                        // SAFETY: `pad + nread < pad + region_len <= buf.len()`,
1286                        // and the buffer lives in the pending table until the CQE.
1287                        let ptr = unsafe { p.buf.as_mut_ptr().add(p.pad + p.nread) };
1288                        let next_off = p.offset + p.nread as u64;
1289                        let sqe = opcode::Read::new(types::Fd(p.file.as_raw_fd()), ptr, remaining as u32)
1290                            .offset(next_off)
1291                            .build()
1292                            .user_data(ud);
1293                        ReapStep::Resubmit(sqe)
1294                    }
1295                }
1296            };
1297
1298            match step {
1299                ReapStep::Finish(outcome) => {
1300                    // Content hygiene (C12, rustfs/backlog#1062): the delivered
1301                    // bytes are ⊆ [0, res) — buf was freshly zeroed per op and
1302                    // truncated to res. When P3 reuses a driver-owned slab
1303                    // across requests, this ⊆ [0, res) property MUST be
1304                    // preserved or a previous tenant's object bytes leak.
1305                    let mut p = state.pending.remove(&ud).expect("checked above");
1306                    match p.done.take().expect("done sender set at submit").send(outcome) {
1307                        Ok(()) => stats.delivered.fetch_add(1, Ordering::SeqCst),
1308                        // Caller dropped the future: the buffer survived in
1309                        // the table until this final CQE and is reclaimed here.
1310                        Err(_) => stats.orphan_reclaimed.fetch_add(1, Ordering::SeqCst),
1311                    };
1312                    stats.in_flight.fetch_sub(1, Ordering::SeqCst);
1313                    // `p` (and with it `_permit`) is dropped here, at the CQE
1314                    // and pending-table removal — never at future drop (C10,
1315                    // rustfs/backlog#1060). No manual release to forget.
1316                }
1317                ReapStep::Resubmit(sqe) => state.backlog.push_back(sqe),
1318            }
1319        }
1320
1321        // Monitor CQ overflow. With NODROP (asserted at probe) the crate's
1322        // submit() auto-flushes the kernel overflow list, so this should stay
1323        // 0; any non-zero value means CQEs were lost — pending entries would
1324        // never be reclaimed. Record it as a fatal signal (C5,
1325        // rustfs/backlog#1056).
1326        let overflow = state.ring.completion().overflow();
1327        if overflow != 0 {
1328            stats.cq_overflow.store(overflow as u64, Ordering::SeqCst);
1329            eprintln!("uring-spike driver: CQ overflow = {overflow}; CQEs lost — treat as fatal in P2");
1330        }
1331
1332        // 4. Exit when drained: the kernel no longer references any buffer, so
1333        //    dropping the ring (unmap) is safe. If a hung device keeps a CQE
1334        //    from ever arriving, bail out under a bounded deadline instead of
1335        //    blocking forever (C4, rustfs/backlog#1055).
1336        if shutting_down {
1337            if state.pending.is_empty() && state.backlog.is_empty() {
1338                // Close the semaphore so any handle still awaiting a permit
1339                // resolves with a driver-gone error instead of hanging.
1340                sem.close();
1341                return; // clean drain: DriverState drops normally, ring unmaps.
1342            }
1343            let deadline = *drain_deadline.get_or_insert_with(|| Instant::now() + DRAIN_TIMEOUT);
1344            if Instant::now() >= deadline {
1345                // A CQE may never arrive (ASYNC_CANCEL cannot interrupt an
1346                // in-execution regular-file read on a hung disk). We must NOT
1347                // unmap the ring or free the still-in-flight buffers — leak the
1348                // whole state (leak over UAF) and exit so shutdown() returns.
1349                eprintln!(
1350                    "uring-spike driver: bounded drain timed out with {} ops still in flight; \
1351                     leaking ring + buffers to stay memory-safe",
1352                    state.pending.len()
1353                );
1354                // Close the semaphore so any handle awaiting a permit resolves
1355                // with a driver-gone error. The leaked pending entries keep
1356                // their permits, which is fine: nothing will wait on them.
1357                sem.close();
1358                std::mem::forget(state);
1359                return;
1360            }
1361        }
1362        // No pacing sleep: `wait_for_events` at the top of the loop blocks until
1363        // the next CQE, message, or heartbeat (backlog#1102).
1364    }
1365}