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, HashSet, 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/// Heartbeat used when the shard is fully idle — no in-flight ops and not
86/// shutting down (rustfs/backlog#1169). New work still wakes the loop instantly
87/// via `wake_efd` and completions via the registered `cq_efd`; this only bounds
88/// the fallback wait, so a much longer value cuts idle timer/​syscall churn
89/// across many per-disk shards without affecting latency.
90const IDLE_HEARTBEAT: Duration = Duration::from_secs(1);
91
92/// Per-ring cap on io-wq BOUNDED workers (rustfs/backlog#1169). Cold buffered
93/// and O_DIRECT reads punted to io-wq each spawn a bounded worker, and the
94/// kernel default is min(sq_entries, 4*nCPU) PER ring — one ring per shard per
95/// disk can otherwise materialize thousands of PF_IO_WORKER threads under a
96/// cold-read burst. Best-effort (needs kernel >= 5.15); older kernels keep the
97/// default.
98const IOWQ_MAX_BOUNDED_WORKERS: u32 = 16;
99
100/// Consecutive non-transient `ring.submit()` failures the driver tolerates
101/// before it stops retrying silently and shuts the shard down, so callers get a
102/// driver-gone error and fall back to the std backend instead of stalling
103/// forever on ops the kernel will never accept (rustfs/backlog#1162). With the
104/// 50 ms heartbeat this bounds the silent-retry window to a few seconds.
105const MAX_CONSECUTIVE_SUBMIT_ERRORS: u32 = 128;
106
107/// How many times a single logical read retries a transient CQE errno
108/// (EINTR/EAGAIN) without making progress before it surfaces the error, so a
109/// pathological storm cannot spin the driver thread (rustfs/backlog#1166).
110const MAX_TRANSIENT_RETRIES: u32 = 16;
111
112/// Owned `eventfd(2)` used to wake the driver loop (backlog#1102): one is
113/// registered with the ring so the kernel signals it on every CQE, the other is
114/// signaled by `submit`/shutdown so a new message wakes the loop immediately —
115/// together they replace the spike's 200 µs busy-poll.
116struct EventFd {
117    fd: std::os::fd::RawFd,
118}
119
120impl EventFd {
121    fn new() -> io::Result<Self> {
122        // SAFETY: eventfd returns a fresh owned fd or -1; the flags are valid.
123        let fd = unsafe { libc::eventfd(0, libc::EFD_NONBLOCK | libc::EFD_CLOEXEC) };
124        if fd < 0 {
125            return Err(io::Error::last_os_error());
126        }
127        Ok(Self { fd })
128    }
129
130    fn as_raw(&self) -> std::os::fd::RawFd {
131        self.fd
132    }
133
134    /// Make the fd readable. A saturated counter (EAGAIN) is fine — it is
135    /// already readable, which is all a wakeup needs.
136    fn signal(&self) {
137        let v: u64 = 1;
138        // SAFETY: writing 8 bytes from a valid u64 to an eventfd we own.
139        unsafe {
140            libc::write(self.fd, (&v as *const u64).cast(), 8);
141        }
142    }
143
144    /// Reset the counter. EFD_NONBLOCK guarantees this never blocks; a single
145    /// successful read drains the whole counter, the next returns EAGAIN.
146    fn drain(&self) {
147        let mut v: u64 = 0;
148        // SAFETY: reading 8 bytes into a valid u64 from an eventfd we own.
149        while unsafe { libc::read(self.fd, (&mut v as *mut u64).cast(), 8) } == 8 {}
150    }
151}
152
153impl Drop for EventFd {
154    fn drop(&mut self) {
155        // SAFETY: we own this fd and drop it exactly once.
156        unsafe {
157            libc::close(self.fd);
158        }
159    }
160}
161
162/// Block until a CQE is ready (`cq`), a new message arrives (`wake`), or the
163/// heartbeat elapses. The return value is ignored: a spurious wakeup, timeout,
164/// or EINTR just runs one loop turn (intake + reap), which is always safe.
165fn wait_for_events(cq: &EventFd, wake: &EventFd, timeout: Duration) {
166    let mut fds = [
167        libc::pollfd {
168            fd: cq.as_raw(),
169            events: libc::POLLIN,
170            revents: 0,
171        },
172        libc::pollfd {
173            fd: wake.as_raw(),
174            events: libc::POLLIN,
175            revents: 0,
176        },
177    ];
178    let ms = timeout.as_millis().min(i32::MAX as u128) as libc::c_int;
179    // SAFETY: `fds` is a valid, initialized array of two pollfds.
180    unsafe {
181        libc::poll(fds.as_mut_ptr(), fds.len() as libc::nfds_t, ms);
182    }
183}
184
185/// Why the probe refused to start the io_uring driver.
186///
187/// Mirrors the P2 degradation contract (backlog#894): a restricted
188/// environment must be recognized and answered with a silent fallback to the
189/// std backend, never surfaced to callers.
190#[derive(Debug)]
191pub enum ProbeFailure {
192    /// `io_uring_setup` itself failed (seccomp/gVisor/old kernel).
193    Setup(io::Error),
194    /// The ring was created but a real `IORING_OP_READ` did not complete
195    /// correctly (gVisor accepts setup but fails ops; also covers silent
196    /// data corruption, which we treat as "unusable").
197    ReadOp(io::Error),
198}
199
200impl std::fmt::Display for ProbeFailure {
201    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
202        match self {
203            Self::Setup(err) => write!(f, "io_uring setup failed: {err}"),
204            Self::ReadOp(err) => write!(f, "io_uring probe read failed: {err}"),
205        }
206    }
207}
208
209impl std::error::Error for ProbeFailure {
210    fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
211        Some(match self {
212            Self::Setup(err) | Self::ReadOp(err) => err,
213        })
214    }
215}
216
217impl ProbeFailure {
218    /// True when the **probe-time** errno belongs to the "expected
219    /// restriction" class that P2 maps to permanent per-disk fallback:
220    /// EACCES/EPERM/ENOSYS/EINVAL/EOPNOTSUPP. Anything else is a genuine bug
221    /// worth surfacing.
222    ///
223    /// IMPORTANT (C7, rustfs/backlog#1059): this classification is valid ONLY
224    /// for a one-shot startup probe, where these errnos unambiguously mean
225    /// "io_uring is unusable here" (gVisor/seccomp/old kernel). Runtime
226    /// per-op errnos have different semantics and MUST NOT reuse this class.
227    /// In particular EINVAL is triple-meaning at runtime — offset > i64::MAX
228    /// (signed loff_t), O_DIRECT buffer/offset/len misalignment (P2 will use
229    /// O_DIRECT), and setup `entries` over the cap — none of which imply the
230    /// disk should be permanently degraded off io_uring. P2's degradation
231    /// contract must split errnos into three classes:
232    ///
233    ///   * probe-time restriction  -> degrade this disk to the std backend;
234    ///   * runtime parameter error -> return the error to the caller (and,
235    ///     for a suspected bug, re-verify once via std pread) — never latch;
236    ///   * transient (EINTR/EAGAIN) -> retry, never surface.
237    ///
238    /// See `submit` for the offset guard that keeps a caller arithmetic bug
239    /// from ever reaching the kernel as a runtime EINVAL.
240    pub fn is_expected_restriction(&self) -> bool {
241        let err = match self {
242            ProbeFailure::Setup(e) | ProbeFailure::ReadOp(e) => e,
243        };
244        matches!(
245            err.raw_os_error(),
246            Some(libc::EACCES) | Some(libc::EPERM) | Some(libc::ENOSYS) | Some(libc::EINVAL) | Some(libc::EOPNOTSUPP)
247        )
248    }
249}
250
251// Submission-side backpressure (C10, rustfs/backlog#1060; async in #1102).
252//
253// A `tokio::sync::Semaphore` with `entries` permits bounds in-flight ops below
254// CQ capacity. The load-bearing rule is the RELEASE POINT: a permit is released
255// at the CQE (when the pending-table entry is removed), NOT at future drop.
256// Tying a permit to the future (the natural RAII shape) would let a quorum
257// dropping many futures return permits while their orphan buffers still sit in
258// the pending table awaiting slow-disk CQEs, decoupling the permit count from
259// resident memory and reopening the memory-DoS surface.
260//
261// That rule is now enforced by the type system rather than by a manual
262// `release()` call: the `OwnedSemaphorePermit` travels with `Msg::Read` into the
263// `Pending` entry and is dropped exactly when the entry is removed at the final
264// CQE. A short-read resubmit keeps the entry — and thus the permit.
265//
266// Acquisition never blocks the caller's thread: `submit` takes the permit with
267// `try_acquire_owned()` on the common unsaturated path (no allocation, no await,
268// submission stays eager), and when saturated it hands the acquire future to the
269// returned `ReadHandle`, which awaits it on its first poll and submits then.
270
271/// Boxed `Semaphore::acquire_owned` future held by a saturated `ReadHandle`.
272type AcquireFut = Pin<Box<dyn Future<Output = Result<OwnedSemaphorePermit, tokio::sync::AcquireError>> + Send>>;
273
274#[derive(Default)]
275struct DriverStats {
276    submitted: AtomicU64,
277    delivered: AtomicU64,
278    orphan_reclaimed: AtomicU64,
279    in_flight: AtomicU64,
280    cancel_succeeded: AtomicU64,
281    cancel_not_found: AtomicU64,
282    cancel_already: AtomicU64,
283    cq_overflow: AtomicU64,
284    submit_errors: AtomicU64,
285}
286
287/// Point-in-time copy of the driver counters.
288#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
289pub struct StatsSnapshot {
290    /// Read ops handed to the kernel.
291    pub submitted: u64,
292    /// CQEs whose result was received by a live caller.
293    pub delivered: u64,
294    /// CQEs whose caller had dropped the future: the buffer stayed in the
295    /// pending table the whole time and was reclaimed here, at the CQE.
296    pub orphan_reclaimed: u64,
297    /// Ops submitted but not yet completed. The kernel may still write into
298    /// their buffers.
299    pub in_flight: u64,
300    /// ASYNC_CANCEL CQEs that reported the target op was canceled (res == 0).
301    pub cancel_succeeded: u64,
302    /// ASYNC_CANCEL CQEs that reported the target was not found (-ENOENT):
303    /// the op had already completed.
304    pub cancel_not_found: u64,
305    /// ASYNC_CANCEL CQEs that reported the target was already executing and
306    /// could not be interrupted (-EALREADY). A rising count is the hung-disk
307    /// signal that makes drain-to-zero non-terminating (C4,
308    /// rustfs/backlog#1055).
309    pub cancel_already: u64,
310    /// Kernel CQ-ring overflow counter. With NODROP (asserted at probe) overflow
311    /// CQEs are buffered in the kernel overflow list and flushed on the next
312    /// enter, NOT lost — so a non-zero value is a backpressure warning, not fatal
313    /// loss (C5, rustfs/backlog#1056, #1167). In-flight is capped at `entries`
314    /// and cancels are deduped, keeping completions <= 2*entries, so it should
315    /// stay 0 in practice.
316    pub cq_overflow: u64,
317    /// `ring.submit()` calls that returned a non-transient error. A rising count
318    /// means `io_uring_enter` is persistently failing (e.g. a seccomp/LSM policy
319    /// applied after startup); the driver shuts the shard down after a bounded
320    /// run of consecutive failures so callers fall back instead of stalling
321    /// (rustfs/backlog#1162).
322    pub submit_errors: u64,
323}
324
325enum Msg {
326    Read {
327        id: u64,
328        file: Arc<File>,
329        offset: u64,
330        len: usize,
331        done: oneshot::Sender<io::Result<Vec<u8>>>,
332        /// Backpressure permit, acquired before the op reaches the driver and
333        /// released only when the pending entry is dropped at the final CQE
334        /// (rustfs/backlog#1060/#1102). If the driver rejects the op (shutting
335        /// down) the permit is dropped with the message — released immediately.
336        permit: OwnedSemaphorePermit,
337        /// Block size the read must be aligned to. `1` means a normal buffered
338        /// read; `> 1` means the file was opened `O_DIRECT` and the driver must
339        /// read the block-aligned superset range into a block-aligned buffer
340        /// (rustfs/backlog#1102).
341        align: usize,
342    },
343    Cancel {
344        id: u64,
345    },
346    Shutdown,
347    /// Test-only fault injection (rustfs/backlog#1103): unwind the driver thread
348    /// with ops in flight so the `DriverState::Drop` abort barrier (C2/#1054) is
349    /// exercised. Never present in a default build.
350    #[cfg(feature = "fault-injection")]
351    TestPanic,
352}
353
354/// One in-flight LOGICAL read. This struct — not the caller — owns everything
355/// the kernel touches:
356///
357/// - `buf`: the destination buffer. Its heap allocation must stay put until
358///   the final CQE; the `Vec` itself may move (HashMap rehash) since that
359///   never relocates the heap block. It is never resized or dropped before
360///   the CQE handler removes this entry.
361/// - `file`: keeps the fd open even if every caller-side clone is dropped, and
362///   supplies the fd for short-read resubmission. Without it, dropping the
363///   future could close the fd while an SQE built from that fd still sits in
364///   the backlog (SQE construction → io_uring_enter window), and a recycled
365///   fd number would make the kernel read the WRONG file (spike finding, with
366///   the corrected mechanism per rustfs/backlog#1063).
367/// - `offset`/`nread`: track a short-read resubmit loop (C9,
368///   rustfs/backlog#1058). io_uring may legally short-read a regular file;
369///   the driver resubmits the remainder into `buf[nread..]` until the request
370///   is fully satisfied or a real EOF (res == 0) is seen, so reclamation
371///   happens only at the FINAL CQE of the logical read.
372/// - `_permit`: the backpressure permit. Holding it here makes the
373///   "release at the CQE, never at future drop" rule (rustfs/backlog#1060) a
374///   property of the type: the permit is dropped exactly when this entry is
375///   removed at the final CQE. A short-read resubmit keeps the entry, and thus
376///   the permit, so in-flight memory stays bounded.
377/// - Alignment geometry (rustfs/backlog#1102). For a buffered read these are
378///   `pad = head = 0`, `align = 1`, `region_len = want`, so every rule below
379///   collapses to the plain case. For an `O_DIRECT` read the driver reads the
380///   block-aligned superset `[offset, offset + region_len)` into
381///   `buf[pad .. pad + region_len]` (both block-aligned) and hands the caller
382///   only `buf[pad + head .. pad + head + want]` — alignment padding never
383///   escapes.
384struct Pending {
385    buf: Vec<u8>,
386    file: Arc<File>,
387    done: Option<oneshot::Sender<io::Result<Vec<u8>>>>,
388    /// Kernel read offset: the block-aligned offset for a direct read, the
389    /// logical offset for a buffered one, `CURRENT_POSITION` for a stream.
390    offset: u64,
391    /// Bytes already read into the read region (`buf[pad..]`).
392    nread: usize,
393    _permit: OwnedSemaphorePermit,
394    /// Offset inside `buf` where the block-aligned read region starts.
395    pad: usize,
396    /// Bytes of the read region that precede the caller's logical range.
397    head: usize,
398    /// Logical length the caller asked for.
399    want: usize,
400    /// Bytes the kernel is asked to read (block-aligned for a direct read).
401    region_len: usize,
402    /// `1` for buffered, the block size for `O_DIRECT`.
403    align: usize,
404    /// Consecutive transient-errno (EINTR/EAGAIN) retries since the last byte of
405    /// progress, bounded by `MAX_TRANSIENT_RETRIES` so a storm cannot spin the
406    /// driver thread (rustfs/backlog#1166). Reset whenever a read makes progress.
407    transient_retries: u32,
408}
409
410impl Pending {
411    /// Build the read SQE for the not-yet-read remainder
412    /// `[pad + nread, pad + region_len)` at file offset `offset + nread`. This is
413    /// the single place a read SQE is constructed: the initial submit calls it
414    /// with `nread == 0` (the whole region), and a short-read or transient-errno
415    /// resubmit calls it after `nread` has advanced (rustfs/backlog#1058/#1166).
416    /// For an `O_DIRECT` read `pad + nread`, `offset + nread`, and the remaining
417    /// length are all block-aligned.
418    fn read_sqe(&self, ud: u64) -> io_uring::squeue::Entry {
419        let remaining = self.region_len - self.nread;
420        // SAFETY: `pad + nread < pad + region_len <= buf.len()`, and the buffer
421        // lives in the pending table until the CQE, so the kernel may write here.
422        // The read region is exclusively owned by this entry (no live aliases),
423        // so deriving a `*mut` from the shared `as_ptr` is sound.
424        let ptr = unsafe { self.buf.as_ptr().add(self.pad + self.nread).cast_mut() };
425        let next_off = self.offset + self.nread as u64;
426        opcode::Read::new(types::Fd(self.file.as_raw_fd()), ptr, remaining as u32)
427            .offset(next_off)
428            .build()
429            .user_data(ud)
430    }
431}
432
433/// Where a [`ReadHandle`] is in its lifecycle (rustfs/backlog#1102).
434enum HandleState {
435    /// Nothing was ever handed to the driver (a rejected parameter, or the
436    /// driver was already gone). The result is already sitting in `rx`, and
437    /// there is no buffer, permit, or SQE to reclaim.
438    Inert,
439    /// Backpressure was saturated at `submit` time, so the permit — and with it
440    /// the submission — is deferred to the first poll. The caller's thread is
441    /// never blocked. Dropping the handle in this state submitted nothing.
442    WaitingPermit {
443        acquire: AcquireFut,
444        file: Arc<File>,
445        offset: u64,
446        len: usize,
447        align: usize,
448        done: oneshot::Sender<io::Result<Vec<u8>>>,
449        wake: Arc<EventFd>,
450    },
451    /// The op is with the driver: its buffer lives in the pending table and is
452    /// reclaimed only at the CQE.
453    Submitted {
454        /// The accepting shard's wakeup eventfd, so a cancel sent on drop wakes
455        /// the driver loop now instead of after the heartbeat
456        /// (rustfs/backlog#1163).
457        wake: Arc<EventFd>,
458    },
459}
460
461/// Handle to a read. Await it for the result.
462///
463/// Dropping it before completion abandons the result only; if the op was
464/// already submitted it also sends `IORING_OP_ASYNC_CANCEL` (best effort) so the
465/// CQE — and with it the buffer reclamation — arrives sooner.
466/// `without_cancel_on_drop` disables that to model the bare "quorum drops the
467/// future" case.
468///
469/// Submission is eager whenever a backpressure permit is immediately available
470/// (the common case, unchanged from the blocking implementation). Only when the
471/// semaphore is saturated does the handle acquire the permit and submit on its
472/// first poll, so `submit` never blocks a runtime worker.
473#[must_use = "a read handle must be awaited or explicitly dropped"]
474pub struct ReadHandle {
475    id: u64,
476    rx: oneshot::Receiver<io::Result<Vec<u8>>>,
477    tx: mpsc::Sender<Msg>,
478    finished: bool,
479    cancel_on_drop: bool,
480    state: HandleState,
481}
482
483impl ReadHandle {
484    /// Keep the driver's buffer until the normal CQE even when this handle is
485    /// dropped.
486    ///
487    /// By default, dropping an in-flight handle sends a best-effort
488    /// `IORING_OP_ASYNC_CANCEL` request to accelerate reclamation. This method
489    /// disables that request while preserving the same memory-safety guarantee:
490    /// the driver still owns the buffer and file descriptor until the read's
491    /// completion arrives. It is useful when cancellation traffic would add
492    /// more work than the abandoned read itself.
493    #[must_use = "the returned handle carries the changed cancellation policy"]
494    pub fn without_cancel_on_drop(mut self) -> Self {
495        self.cancel_on_drop = false;
496        self
497    }
498}
499
500impl Future for ReadHandle {
501    type Output = io::Result<Vec<u8>>;
502
503    fn poll(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
504        let this = &mut *self;
505
506        // Saturated at submit time: take the permit, then hand the op to the
507        // driver. The permit rides along in the message and is released only
508        // when the pending entry is dropped at the CQE.
509        let acquired = match &mut this.state {
510            HandleState::WaitingPermit { acquire, .. } => match acquire.as_mut().poll(cx) {
511                Poll::Pending => return Poll::Pending,
512                Poll::Ready(res) => Some(res),
513            },
514            _ => None,
515        };
516        if let Some(res) = acquired {
517            let Ok(permit) = res else {
518                // The semaphore was closed: the driver is gone.
519                this.finished = true;
520                return Poll::Ready(Err(io::Error::other("uring driver shut down")));
521            };
522            // Clone the wake before moving the WaitingPermit out, so the new
523            // Submitted state carries it for the drop-cancel path
524            // (rustfs/backlog#1163).
525            let submitted_wake = match &this.state {
526                HandleState::WaitingPermit { wake, .. } => Arc::clone(wake),
527                _ => unreachable!("state was WaitingPermit"),
528            };
529            let HandleState::WaitingPermit {
530                file,
531                offset,
532                len,
533                align,
534                done,
535                wake,
536                ..
537            } = std::mem::replace(&mut this.state, HandleState::Submitted { wake: submitted_wake })
538            else {
539                unreachable!("state was WaitingPermit")
540            };
541            if this
542                .tx
543                .send(Msg::Read {
544                    id: this.id,
545                    file,
546                    offset,
547                    len,
548                    done,
549                    permit,
550                    align,
551                })
552                .is_err()
553            {
554                // Driver gone between the acquire and the send; the message
555                // (with its permit) is dropped, releasing it.
556                this.finished = true;
557                return Poll::Ready(Err(io::Error::other("uring driver shut down")));
558            }
559            wake.signal();
560        }
561
562        match Pin::new(&mut this.rx).poll(cx) {
563            Poll::Ready(res) => {
564                this.finished = true;
565                Poll::Ready(match res {
566                    Ok(inner) => inner,
567                    Err(_) => Err(io::Error::other("uring driver shut down before completion")),
568                })
569            }
570            Poll::Pending => Poll::Pending,
571        }
572    }
573}
574
575impl Drop for ReadHandle {
576    fn drop(&mut self) {
577        // The buffer is deliberately NOT touched here: the driver owns it
578        // until the CQE. All we may do is ask the kernel to hurry up. A handle
579        // dropped before it was submitted (Inert / WaitingPermit) has no buffer,
580        // no permit and no SQE, so there is nothing to cancel.
581        if let HandleState::Submitted { wake } = &self.state
582            && !self.finished
583            && self.cancel_on_drop
584        {
585            // Close the receiver BEFORE waking the driver. The wake below
586            // makes the driver process the cancel immediately, possibly while
587            // this drop is still running — before the `rx` field is
588            // destroyed. Closing it first guarantees the cancel-induced
589            // completion the driver reaps is counted as an orphan reclaim, not
590            // delivered to a receiver that is about to drop anyway
591            // (rustfs/backlog#1163).
592            self.rx.close();
593            let _ = self.tx.send(Msg::Cancel { id: self.id });
594            // Wake the loop so the cancel is queued now, not after the
595            // heartbeat. On an idle ring (the hung-disk case cancel-on-drop
596            // exists for) this keeps orphan reclamation prompt.
597            wake.signal();
598        }
599    }
600}
601
602/// Process-level io_uring driver: one ring, one driver thread.
603/// One io_uring ring plus the thread that drives it.
604///
605/// Every cancel-safety invariant holds *per shard*, exactly as it did when a
606/// driver owned a single ring: this shard's pending table owns its buffers and
607/// fds until their CQEs, its permits are released only when a pending entry is
608/// dropped, and its bounded drain is what shutdown joins on. A `ReadHandle`
609/// carries the `tx` and `wake` of the shard that accepted it, so a cancel or a
610/// deferred submission always routes back to that same shard.
611struct Shard {
612    tx: mpsc::Sender<Msg>,
613    handle: Option<JoinHandle<()>>,
614    stats: Arc<DriverStats>,
615    /// Backpressure permits (one per allowed in-flight op on this ring). Closed
616    /// when the driver thread exits so any waiting `ReadHandle` resolves with a
617    /// driver-gone error instead of hanging (rustfs/backlog#1102).
618    sem: Arc<Semaphore>,
619    /// Signaled after every message send so this shard's loop wakes immediately
620    /// instead of waiting out the heartbeat (backlog#1102).
621    wake_efd: Arc<EventFd>,
622}
623
624impl Shard {
625    /// Ask the shard's thread to drain and exit, then join it. Idempotent: the
626    /// `JoinHandle` is taken, so a later `Drop` is a no-op.
627    fn join(&mut self) {
628        if let Some(h) = self.handle.take() {
629            let _ = self.tx.send(Msg::Shutdown);
630            self.wake_efd.signal();
631            let _ = h.join();
632        }
633    }
634}
635
636impl Drop for Shard {
637    fn drop(&mut self) {
638        self.join();
639    }
640}
641
642/// Process-level io_uring read driver.
643///
644/// A driver owns one or more independent Linux io_uring shards. It is safe to
645/// share by reference across async tasks; each read returns a [`ReadHandle`]
646/// that can be awaited or dropped without freeing memory still visible to the
647/// kernel. Construct it through [`UringDriver::probe_and_start`] so restricted
648/// environments can fall back to a blocking backend before serving traffic.
649pub struct UringDriver {
650    /// One or more independent rings. A cache-hit buffered read completes inline
651    /// inside `io_uring_enter`, so the thread driving a ring performs that
652    /// read's memcpy — which caps a single-ring driver at one core's memory
653    /// bandwidth (~5 GB/s measured, rustfs/backlog#1145). Sharding lifts that
654    /// ceiling roughly linearly while keeping the ring set per-disk, so a stalled
655    /// disk still cannot starve another disk's rings (rustfs/backlog#1055).
656    shards: Vec<Shard>,
657    next_id: AtomicU64,
658    /// Round-robin cursor for shard selection. Relaxed: it only has to spread
659    /// ops, never to order them.
660    rr: AtomicUsize,
661}
662
663impl UringDriver {
664    /// Create the ring AND verify a real `IORING_OP_READ` round-trip on a
665    /// temp file before accepting work. `io_uring_setup` succeeding is not
666    /// enough: gVisor/seccomp environments can create a ring whose ops then
667    /// fail with ENOSYS/EINVAL (backlog#894 probe design).
668    /// Start a single-ring driver. Identical to `probe_and_start_sharded(entries, 1)`.
669    pub fn probe_and_start(entries: u32) -> Result<Self, ProbeFailure> {
670        Self::probe_and_start_sharded(entries, 1)
671    }
672
673    /// Start a driver backed by `shards` independent rings, each with `entries`
674    /// SQ slots and its own driver thread.
675    ///
676    /// Use more than one shard when the workload hits the page cache: such reads
677    /// complete inline in `io_uring_enter`, so a single driver thread performs
678    /// every one of their memcpys and caps the driver at one core's memory
679    /// bandwidth. Measured on a 16-core host (rustfs/backlog#1145): 1 ring →
680    /// 4890 MB/s, 2 → 8969 MB/s, 4 → 15806 MB/s, with per-ring throughput flat.
681    /// Reads that miss the cache are device-bound and do not need sharding.
682    ///
683    /// In-flight ops are capped at `entries` *per shard* (the invariant that
684    /// makes CQ overflow structurally unreachable holds per ring), so the whole
685    /// driver admits up to `shards * entries` concurrent reads.
686    ///
687    /// `shards` is clamped to at least 1. Probing happens on the first shard, so
688    /// a restricted environment fails exactly as it does for a single ring; if a
689    /// later shard fails to start, the ones already running are shut down and
690    /// joined before the error is returned.
691    pub fn probe_and_start_sharded(entries: u32, shards: usize) -> Result<Self, ProbeFailure> {
692        let mut started = Vec::with_capacity(shards.max(1));
693        for i in 0..shards.max(1) {
694            // Probe only the first shard (rustfs/backlog#1165): the probe read
695            // exercises io_uring against the environment-global temp_dir, so one
696            // confirmation is representative. Shards 2..n only create a ring and
697            // verify NODROP — this avoids `shards - 1` extra O_TMPFILE
698            // create+write+read round-trips per disk on every start and renew.
699            // `?` drops `started`, whose `Shard::drop` joins each running thread.
700            started.push(Self::start_shard(entries, i == 0)?);
701        }
702        Ok(Self {
703            shards: started,
704            next_id: AtomicU64::new(1),
705            rr: AtomicUsize::new(0),
706        })
707    }
708
709    /// Pick the shard for the next op. Round-robin spreads the inline-completion
710    /// memcpy across driver threads; correctness does not depend on the choice,
711    /// because the handle remembers which shard took the op.
712    fn shard(&self) -> &Shard {
713        let n = self.shards.len();
714        &self.shards[self.rr.fetch_add(1, Ordering::Relaxed) % n]
715    }
716
717    fn start_shard(entries: u32, probe: bool) -> Result<Shard, ProbeFailure> {
718        let mut ring = IoUring::new(entries).map_err(ProbeFailure::Setup)?;
719        // Require the NODROP feature (kernel >= 5.5). Without it, CQ overflow
720        // silently drops CQEs, stranding pending entries forever and hanging
721        // shutdown (C5, rustfs/backlog#1056). ENOSYS is in the expected-
722        // restriction class, so this degrades to the std backend cleanly.
723        if !ring.params().is_feature_nodrop() {
724            return Err(ProbeFailure::Setup(io::Error::from_raw_os_error(libc::ENOSYS)));
725        }
726        // Only the first shard runs the real-read probe (rustfs/backlog#1165); the
727        // rest still create a ring and check NODROP above, which is what makes
728        // io_uring usable, but skip the redundant temp_dir round-trip.
729        if probe {
730            probe_real_read(&mut ring).map_err(ProbeFailure::ReadOp)?;
731        }
732
733        // Wake the driver loop on CQEs (kernel-signaled via a registered
734        // eventfd) and on new messages (submit-signaled), replacing the 200 µs
735        // busy-poll (backlog#1102). Registration needs the ring, which the
736        // driver thread then owns; `cq_efd` is moved in alongside so it outlives
737        // the ring (dropped after it, unregistering cleanly).
738        let cq_efd = EventFd::new().map_err(ProbeFailure::Setup)?;
739        ring.submitter()
740            .register_eventfd(cq_efd.as_raw())
741            .map_err(ProbeFailure::Setup)?;
742
743        // Cap the ring's io-wq bounded worker pool so a cold-read burst cannot
744        // materialize thousands of PF_IO_WORKER threads against the process's
745        // TasksMax/RLIMIT_NPROC (rustfs/backlog#1169). Best-effort: 0 leaves the
746        // unbounded pool unchanged, and a kernel without this op (< 5.15) keeps
747        // the default — neither is fatal to a working ring.
748        let mut iowq_max = [IOWQ_MAX_BOUNDED_WORKERS, 0u32];
749        let _ = ring.submitter().register_iowq_max_workers(&mut iowq_max);
750
751        let wake_efd = Arc::new(EventFd::new().map_err(ProbeFailure::Setup)?);
752        let thread_wake = Arc::clone(&wake_efd);
753
754        let (tx, rx) = mpsc::channel();
755        let stats = Arc::new(DriverStats::default());
756        let thread_stats = Arc::clone(&stats);
757        // Cap in-flight at the SQ depth (entries), which is < CQ capacity
758        // (2*entries), so CQ overflow is structurally unreachable (C5/C10).
759        let sem = Arc::new(Semaphore::new(entries as usize));
760        let thread_sem = Arc::clone(&sem);
761        // Deterministic spawn-failure seam (rustfs/backlog#1164): exercise the
762        // degrade-not-panic path without a real cgroup pids-limit. Never present
763        // in a default build.
764        #[cfg(feature = "fault-injection")]
765        if std::env::var_os("RUSTFS_URING_FAULT_SPAWN").is_some() {
766            return Err(ProbeFailure::Setup(io::Error::from_raw_os_error(libc::EAGAIN)));
767        }
768
769        // Thread creation fails with EAGAIN under a cgroup pids-limit or
770        // RLIMIT_NPROC — exactly the constrained environments the probe/degrade
771        // design exists for. Degrade to the std backend instead of panicking out
772        // of async disk init/reconnect (rustfs/backlog#1164). The spawn happens
773        // after the probe read already drained, so on failure `ring`/`cq_efd`
774        // (moved into the closure) drop cleanly with no SQE in flight.
775        let handle = std::thread::Builder::new()
776            .name("uring-spike-driver".into())
777            .spawn(move || drive(ring, rx, thread_stats, thread_sem, cq_efd, thread_wake))
778            .map_err(ProbeFailure::Setup)?;
779
780        Ok(Shard {
781            tx,
782            handle: Some(handle),
783            stats,
784            sem,
785            wake_efd,
786        })
787    }
788
789    /// Positioned read (pread semantics) — regular files, buffered.
790    ///
791    /// The offset must be at most `i64::MAX`; `u64::MAX` is reserved for
792    /// [`Self::read_current`] and is returned as an asynchronous
793    /// `io::ErrorKind::InvalidInput` result rather than panicking.
794    pub fn read_at(&self, file: Arc<File>, offset: u64, len: usize) -> ReadHandle {
795        self.submit(file, offset, len, 1, false)
796    }
797
798    /// Read at the file's current position (read(2) semantics) — pipes.
799    pub fn read_current(&self, file: Arc<File>, len: usize) -> ReadHandle {
800        self.submit(file, CURRENT_POSITION, len, 1, true)
801    }
802
803    /// Positioned read from a file opened with `O_DIRECT` (rustfs/backlog#1102).
804    ///
805    /// `align` is the device's logical block size — a power of two, typically
806    /// 512 or 4096. `offset` and `len` are the caller's *logical* range and need
807    /// no alignment: the driver reads the block-aligned superset range into a
808    /// block-aligned buffer and returns exactly `[offset, offset + len)`.
809    /// Alignment padding never reaches the caller, so a `BitrotReader` expecting
810    /// an exact shard length never sees padded output.
811    ///
812    /// The caller must have opened `file` with `O_DIRECT`; otherwise this is
813    /// just a (correct but pointless) buffered read of the superset range.
814    /// Invalid alignment, range, or reserved-offset inputs are returned through
815    /// the awaited result as `io::ErrorKind::InvalidInput`.
816    pub fn read_at_direct(&self, file: Arc<File>, offset: u64, len: usize, align: usize) -> ReadHandle {
817        self.submit(file, offset, len, align, false)
818    }
819
820    fn submit(&self, file: Arc<File>, offset: u64, len: usize, align: usize, allow_current_position: bool) -> ReadHandle {
821        let id = self.next_id.fetch_add(1, Ordering::Relaxed);
822        assert_eq!(id & CANCEL_BIT, 0, "op id overflowed into the cancel bit");
823        let (done, rx) = oneshot::channel();
824
825        // Bind the op to one shard for its whole life: the permit, the message,
826        // the wake, and any later cancel all go to this ring. The handle holds
827        // clones of that shard's `tx`/`wake_efd`, so nothing can route a cancel
828        // to a ring whose pending table does not hold the op. The rejection paths
829        // below return an `Inert` handle that never sends, but still need a `tx`.
830        let shard = self.shard();
831
832        // `CURRENT_POSITION` is an internal sentinel used only by
833        // `read_current`; accepting it through a positioned API would silently
834        // change pread semantics into read(2) semantics. Return a normal
835        // `InvalidInput` result instead of panicking on caller-controlled data.
836        if !allow_current_position && offset == CURRENT_POSITION {
837            let _ = done.send(Err(io::Error::new(
838                io::ErrorKind::InvalidInput,
839                "offset u64::MAX is reserved for read_current",
840            )));
841            return ReadHandle {
842                id,
843                rx,
844                tx: shard.tx.clone(),
845                finished: false,
846                cancel_on_drop: false,
847                state: HandleState::Inert,
848            };
849        }
850
851        // Reject an offset the kernel would answer with a runtime EINVAL that
852        // must NOT be mistaken for an environment restriction (C7,
853        // rustfs/backlog#1059). The kernel reads `off` as a signed loff_t, so
854        // offset > i64::MAX becomes a negative ki_pos → EINVAL. A caller
855        // offset-arithmetic bug has to surface as an error here, never as a
856        // permanent per-disk fallback. CURRENT_POSITION is the reserved
857        // read(2) sentinel and bypasses this check.
858        if offset != CURRENT_POSITION && offset > i64::MAX as u64 {
859            let _ = done.send(Err(io::Error::new(
860                io::ErrorKind::InvalidInput,
861                "offset exceeds i64::MAX (kernel loff_t is signed)",
862            )));
863            return ReadHandle {
864                id,
865                rx,
866                tx: shard.tx.clone(),
867                finished: false,
868                cancel_on_drop: false,
869                state: HandleState::Inert,
870            };
871        }
872
873        // Reject a length the kernel would short-read past MAX_RW_COUNT and
874        // that the SQE's u32 `len` field would silently truncate: len == 2^32
875        // becomes a 0-byte read the caller decodes as a false EOF (C6,
876        // rustfs/backlog#1057). Failing fast here also removes the caller-
877        // controlled `vec![0u8; len]` capacity-overflow panic that made the
878        // unwind-UAF (rustfs/backlog#1054) reachable. P2 must chunk instead.
879        if len > MAX_READ_LEN {
880            let _ = done.send(Err(io::Error::new(
881                io::ErrorKind::InvalidInput,
882                "read length exceeds MAX_RW_COUNT (2 GiB - 4 KiB); caller must chunk",
883            )));
884            return ReadHandle {
885                id,
886                rx,
887                tx: shard.tx.clone(),
888                finished: false,
889                cancel_on_drop: false,
890                state: HandleState::Inert,
891            };
892        }
893
894        // Reject a bad O_DIRECT alignment, a request whose block-aligned superset
895        // range would exceed the kernel's single-read cap, and one whose aligned
896        // END crosses i64::MAX — the kernel reads pos as a signed loff_t, so
897        // `kernel_offset + region_len > i64::MAX` fails at runtime with
898        // EINVAL/EOVERFLOW, exactly the errno class the C7 guard must pre-empt at
899        // submit (rustfs/backlog#1102, #1166). Pre-empting it here also makes
900        // every resubmit's `next_off < kernel_offset + region_len` provably
901        // <= i64::MAX. `align == 1` (buffered) always passes the alignment part.
902        match aligned_geometry(offset, len, align) {
903            // CURRENT_POSITION (stream) reads use no positional offset — the
904            // kernel reads from the current file position — so the i64::MAX end
905            // check does not apply to them (their sentinel offset would overflow
906            // it). Exempt them exactly as the offset guard above does.
907            Some((kernel_offset, _, region_len))
908                if region_len <= MAX_READ_LEN
909                    && (allow_current_position && offset == CURRENT_POSITION
910                        || kernel_offset
911                            .checked_add(region_len as u64)
912                            .is_some_and(|end| end <= i64::MAX as u64)) => {}
913            _ => {
914                let _ = done.send(Err(io::Error::new(
915                    io::ErrorKind::InvalidInput,
916                    "alignment must be a power of two, and the block-aligned range must fit MAX_RW_COUNT and end within i64::MAX",
917                )));
918                return ReadHandle {
919                    id,
920                    rx,
921                    tx: shard.tx.clone(),
922                    finished: false,
923                    cancel_on_drop: false,
924                    state: HandleState::Inert,
925                };
926            }
927        }
928
929        // Take a backpressure permit BEFORE the op reaches the driver; it is
930        // released only when the pending entry is dropped at the CQE (C10,
931        // rustfs/backlog#1060). Acquisition never blocks the caller's thread
932        // (rustfs/backlog#1102).
933        match Arc::clone(&shard.sem).try_acquire_owned() {
934            // Fast path: a permit was free, so submit eagerly — no allocation,
935            // no await, and the op is in flight the moment `submit` returns,
936            // exactly as with the previous blocking implementation.
937            Ok(permit) => {
938                if let Err(mpsc::SendError(msg)) = shard.tx.send(Msg::Read {
939                    id,
940                    file,
941                    offset,
942                    len,
943                    done,
944                    permit,
945                    align,
946                }) {
947                    // Driver gone: the op never reached it. Surface an explicit
948                    // driver-gone error through `done` instead of letting the
949                    // caller infer one from the dropped oneshot, matching the
950                    // `Closed` arm below. The permit rides back in `msg` and is
951                    // released when it drops here.
952                    if let Msg::Read { done, .. } = msg {
953                        let _ = done.send(Err(io::Error::other("uring driver shut down")));
954                    }
955                    return ReadHandle {
956                        id,
957                        rx,
958                        tx: shard.tx.clone(),
959                        finished: false,
960                        cancel_on_drop: false,
961                        state: HandleState::Inert,
962                    };
963                }
964                // Wake the driver loop so the read starts immediately.
965                shard.wake_efd.signal();
966                ReadHandle {
967                    id,
968                    rx,
969                    tx: shard.tx.clone(),
970                    finished: false,
971                    cancel_on_drop: true,
972                    state: HandleState::Submitted {
973                        wake: Arc::clone(&shard.wake_efd),
974                    },
975                }
976            }
977            // Saturated: `entries` ops are already in flight. Do NOT block the
978            // calling (runtime worker) thread — hand the acquire future to the
979            // handle, which awaits it on its first poll and submits then.
980            Err(TryAcquireError::NoPermits) => ReadHandle {
981                id,
982                rx,
983                tx: shard.tx.clone(),
984                finished: false,
985                cancel_on_drop: true,
986                state: HandleState::WaitingPermit {
987                    acquire: Box::pin(Arc::clone(&shard.sem).acquire_owned()),
988                    file,
989                    offset,
990                    len,
991                    align,
992                    done,
993                    wake: Arc::clone(&shard.wake_efd),
994                },
995            },
996            // The driver has exited and closed the semaphore.
997            Err(TryAcquireError::Closed) => {
998                let _ = done.send(Err(io::Error::other("uring driver shut down")));
999                ReadHandle {
1000                    id,
1001                    rx,
1002                    tx: shard.tx.clone(),
1003                    finished: false,
1004                    cancel_on_drop: false,
1005                    state: HandleState::Inert,
1006                }
1007            }
1008        }
1009    }
1010
1011    /// Counters summed across every shard. The conservation identities the
1012    /// cancel-safety tests assert (`submitted == delivered + orphan_reclaimed`,
1013    /// `in_flight == 0` after a clean drain) hold per shard, so they hold for
1014    /// the sum.
1015    pub fn stats(&self) -> StatsSnapshot {
1016        let mut snap = StatsSnapshot::default();
1017        for shard in &self.shards {
1018            let s = &shard.stats;
1019            snap.submitted += s.submitted.load(Ordering::SeqCst);
1020            snap.delivered += s.delivered.load(Ordering::SeqCst);
1021            snap.orphan_reclaimed += s.orphan_reclaimed.load(Ordering::SeqCst);
1022            snap.in_flight += s.in_flight.load(Ordering::SeqCst);
1023            snap.cancel_succeeded += s.cancel_succeeded.load(Ordering::SeqCst);
1024            snap.cancel_not_found += s.cancel_not_found.load(Ordering::SeqCst);
1025            snap.cancel_already += s.cancel_already.load(Ordering::SeqCst);
1026            snap.cq_overflow += s.cq_overflow.load(Ordering::SeqCst);
1027            snap.submit_errors += s.submit_errors.load(Ordering::SeqCst);
1028        }
1029        snap
1030    }
1031
1032    /// Test-only fault injection (rustfs/backlog#1103): poison one driver thread
1033    /// so it panics with ops in flight, exercising the `DriverState::Drop` abort
1034    /// barrier (C2/#1054). Compiled out entirely unless the `fault-injection`
1035    /// feature is on — never in a default/production build.
1036    #[cfg(feature = "fault-injection")]
1037    pub fn test_inject_panic(&self) {
1038        let shard = self.shard();
1039        let _ = shard.tx.send(Msg::TestPanic);
1040        shard.wake_efd.signal();
1041    }
1042
1043    /// Stop accepting work, cancel all in-flight ops, drain every ring to
1044    /// `in_flight == 0`, then join each driver thread. Only after that is a ring
1045    /// dropped/unmapped — the shutdown ordering P2 requires, per shard.
1046    ///
1047    /// Shards are asked to stop first and joined afterwards, so their bounded
1048    /// drains overlap instead of serializing `shards * DRAIN_TIMEOUT`.
1049    pub fn shutdown(mut self) -> StatsSnapshot {
1050        for shard in &self.shards {
1051            let _ = shard.tx.send(Msg::Shutdown);
1052            shard.wake_efd.signal();
1053        }
1054        for shard in &mut self.shards {
1055            shard.join();
1056        }
1057        let snap = self.stats();
1058        // A clean drain leaves in_flight == 0. A non-zero count here means some
1059        // shard's bounded drain bailed out on a hung device and leaked its
1060        // ring+buffers to stay memory-safe (C4, rustfs/backlog#1055) — a degraded
1061        // but safe outcome, not a panic. Callers/tests that require a clean drain
1062        // assert on the returned snapshot themselves.
1063        if snap.in_flight != 0 {
1064            tracing::warn!(
1065                in_flight = snap.in_flight,
1066                "uring shutdown: ops still in flight (bounded-drain bailout on a hung device)"
1067            );
1068        }
1069        snap
1070    }
1071}
1072
1073impl Drop for UringDriver {
1074    fn drop(&mut self) {
1075        // Ask every shard to stop before joining any of them, so their bounded
1076        // drains overlap. Dropping the `Vec<Shard>` would instead run each
1077        // `Shard::drop` in turn, serializing up to `shards * DRAIN_TIMEOUT` on a
1078        // hung device. `Shard::join` is idempotent, so the later drops are no-ops.
1079        for shard in &self.shards {
1080            let _ = shard.tx.send(Msg::Shutdown);
1081            shard.wake_efd.signal();
1082        }
1083        for shard in &mut self.shards {
1084            shard.join();
1085        }
1086    }
1087}
1088
1089fn probe_real_read(ring: &mut IoUring) -> io::Result<()> {
1090    let pattern: Vec<u8> = (0..512u32).map(|i| (i * 7 + 13) as u8).collect();
1091
1092    // Open an anonymous probe file seeded with the pattern. File setup runs
1093    // BEFORE any SQE, so its errors early-return safely — nothing is in flight.
1094    let file = open_probe_file(&pattern)?;
1095
1096    let mut buf = vec![0u8; pattern.len()];
1097    let sqe = opcode::Read::new(types::Fd(file.as_raw_fd()), buf.as_mut_ptr(), buf.len() as u32)
1098        .offset(0)
1099        .build()
1100        .user_data(0xB0BE);
1101
1102    // SAFETY: a push failure means the kernel never accepted the SQE, so
1103    // `buf`/`file` may be dropped safely on this early return.
1104    if unsafe { ring.submission().push(&sqe) }.is_err() {
1105        return Err(io::Error::other("probe: submission queue full"));
1106    }
1107
1108    // C1 (rustfs/backlog#1053): once the SQE is handed to the kernel, the read
1109    // may be punted to io-wq and write into `buf` at ANY later point. Until its
1110    // CQE arrives, `buf`/`file` must NOT be dropped and the ring must NOT be
1111    // unmapped — otherwise the kernel writes into freed memory (UAF). The probe
1112    // path has no pending-table backstop, so we must drain to the CQE here, and
1113    // any early exit first leaks the buffer ("leak over UAF").
1114    let res = match drain_probe_cqe(ring) {
1115        Ok(res) => res,
1116        Err(e) => {
1117            // Could not confirm the op terminated: leak `buf` (the real UAF
1118            // hazard — the kernel may still write 512 bytes into it) and,
1119            // defensively, `file`. Leaking one 512-byte startup-probe buffer is
1120            // trivially cheaper than a silent heap corruption.
1121            std::mem::forget(buf);
1122            std::mem::forget(file);
1123            return Err(e);
1124        }
1125    };
1126
1127    // The CQE has arrived: the kernel is done with `buf`, so dropping it and
1128    // `file` below is now safe.
1129    if res < 0 {
1130        Err(io::Error::from_raw_os_error(-res))
1131    } else if res as usize != pattern.len() || buf != pattern {
1132        Err(io::Error::other("probe: read completed but data mismatched"))
1133    } else {
1134        Ok(())
1135    }
1136}
1137
1138/// Open a probe file seeded with `pattern`, avoiding the symlink/TOCTOU/
1139/// leftover hazards of a predictable temp path (C3, rustfs/backlog#1061).
1140///
1141/// Primary: `O_TMPFILE` — an anonymous inode with no name at all, so there is
1142/// nothing for an attacker to pre-plant a symlink at, no TOCTOU window, and no
1143/// leftover file. Fallback (filesystems without O_TMPFILE): create in the temp
1144/// dir with `O_CREAT|O_EXCL|O_NOFOLLOW` + 0600 + a per-process nonce, then
1145/// unlink immediately so no attacker-planted symlink is followed and no named
1146/// file survives.
1147fn open_probe_file(pattern: &[u8]) -> io::Result<File> {
1148    let dir = std::env::temp_dir();
1149    let c_dir = std::ffi::CString::new(dir.as_os_str().as_bytes()).map_err(|_| io::Error::other("probe dir path has NUL"))?;
1150    // SAFETY: `c_dir` is a valid NUL-terminated path; O_TMPFILE requires a
1151    // directory and O_RDWR/O_WRONLY. On success we own the returned fd.
1152    let fd = unsafe { libc::open(c_dir.as_ptr(), libc::O_TMPFILE | libc::O_RDWR | libc::O_CLOEXEC, 0o600) };
1153    if fd >= 0 {
1154        let mut file = unsafe { File::from_raw_fd(fd) };
1155        file.write_all(pattern)?;
1156        return Ok(file);
1157    }
1158    open_probe_file_exclusive(&dir, pattern)
1159}
1160
1161fn open_probe_file_exclusive(dir: &std::path::Path, pattern: &[u8]) -> io::Result<File> {
1162    static SEQ: AtomicU64 = AtomicU64::new(0);
1163    let nonce = SEQ.fetch_add(1, Ordering::Relaxed);
1164    let path = dir.join(format!("uring-spike-probe-{}-{}", std::process::id(), nonce));
1165    let c_path = std::ffi::CString::new(path.as_os_str().as_bytes()).map_err(|_| io::Error::other("probe path has NUL"))?;
1166    // O_EXCL refuses a pre-existing file; O_NOFOLLOW refuses a symlink; 0600 is
1167    // owner-only. SAFETY: `c_path` is a valid NUL-terminated path; on success
1168    // we own the fd.
1169    let fd = unsafe {
1170        libc::open(
1171            c_path.as_ptr(),
1172            libc::O_CREAT | libc::O_EXCL | libc::O_NOFOLLOW | libc::O_RDWR | libc::O_CLOEXEC,
1173            0o600,
1174        )
1175    };
1176    if fd < 0 {
1177        return Err(io::Error::last_os_error());
1178    }
1179    let mut file = unsafe { File::from_raw_fd(fd) };
1180    file.write_all(pattern)?;
1181    // Unlink now: the fd stays valid, no named leftover remains.
1182    // SAFETY: `c_path` is still a valid NUL-terminated path.
1183    unsafe {
1184        libc::unlink(c_path.as_ptr());
1185    }
1186    Ok(file)
1187}
1188
1189/// Wait for the probe SQE's CQE and return its raw result.
1190///
1191/// The SQE has already been pushed; this only drains it. `submit_and_wait`
1192/// interrupted by a signal returns EINTR — since the kernel consumed the SQE
1193/// atomically before the wait phase, we retry the WAIT only and never re-push
1194/// (C8, backlog#1059). A bounded attempt count keeps a probe that hit a hung
1195/// device from blocking forever; exhausting it returns an error that drives
1196/// the caller's leak-over-UAF fallback.
1197fn drain_probe_cqe(ring: &mut IoUring) -> io::Result<i32> {
1198    // Bound the wait by WALL-CLOCK, not by an attempt count. `submit_and_wait(1)`
1199    // parks in the kernel's io_cqring_wait until a CQE or a signal, so a single
1200    // call can block forever when the probe read never completes — e.g. a
1201    // temp_dir backed by a hung/D-state or NFS device. Since this runs on the
1202    // caller's (async disk-init) thread, an unbounded block hangs startup. On
1203    // kernels with EXT_ARG (>= 5.11) pass a timeout to the enter; on older
1204    // kernels fall back to the blocking wait, whose only real risk is a hung
1205    // temp_dir (rare) and which the deadline still re-checks between returns
1206    // (rustfs/backlog#1165). On expiry, error out so the caller's leak-over-UAF
1207    // fallback degrades the disk to the std backend instead of hanging.
1208    const PROBE_TIMEOUT: Duration = Duration::from_secs(2);
1209    let deadline = Instant::now() + PROBE_TIMEOUT;
1210    let ext_arg = ring.params().is_feature_ext_arg();
1211    loop {
1212        let remaining = deadline.saturating_duration_since(Instant::now());
1213        if remaining.is_zero() {
1214            return Err(io::Error::other("probe: no CQE within the bounded wait"));
1215        }
1216        let waited = if ext_arg {
1217            let ts = types::Timespec::new().sec(remaining.as_secs()).nsec(remaining.subsec_nanos());
1218            let args = types::SubmitArgs::new().timespec(&ts);
1219            ring.submitter().submit_with_args(1, &args)
1220        } else {
1221            ring.submit_and_wait(1)
1222        };
1223        match waited {
1224            Ok(_) => {}
1225            // Signal interrupted the wait; the SQE is already in flight, so wait
1226            // again (do NOT re-push). The deadline still bounds the total time.
1227            Err(e) if e.raw_os_error() == Some(libc::EINTR) => {}
1228            // EXT_ARG timeout elapsed with no CQE: loop to re-check the deadline.
1229            Err(e) if e.raw_os_error() == Some(libc::ETIME) => {}
1230            Err(e) => return Err(e),
1231        }
1232        if let Some(cqe) = ring.completion().next() {
1233            // fault-injection (backlog#1103 → C1/#1053): the real CQE has arrived,
1234            // so the kernel is finished with the probe buffer. Forcing the error
1235            // path here exercises probe_real_read's leak-over-UAF fallback with no
1236            // live in-flight write to race.
1237            #[cfg(feature = "fault-injection")]
1238            if std::env::var_os("RUSTFS_URING_FAULT_PROBE_DRAIN").is_some() {
1239                return Err(io::Error::other("fault-injection: forced probe drain failure"));
1240            }
1241            return Ok(cqe.result());
1242        }
1243    }
1244}
1245
1246/// Owns everything the kernel can still be writing into: the ring, the
1247/// pending (orphan) table of in-flight buffers, and the SQE backlog.
1248///
1249/// C2 (rustfs/backlog#1054): the "CQE is the only reclamation point"
1250/// invariant holds only while the driver thread does NOT unwind. On a panic,
1251/// Rust would drop the pending table (freeing every in-flight buffer) while
1252/// the kernel may still write into them → mass UAF; reversing drop order does
1253/// not help because io_uring teardown on ring drop is asynchronous and does
1254/// not wait for in-flight ops. So this type's `Drop` refuses to run field
1255/// destructors during an unwind: it aborts the process first, leaving the
1256/// ring mapped and the buffers allocated (leak over UAF). A storage read path
1257/// silently corrupting memory is worse than a crash.
1258struct DriverState {
1259    ring: IoUring,
1260    pending: HashMap<u64, Pending>,
1261    backlog: VecDeque<io_uring::squeue::Entry>,
1262}
1263
1264impl Drop for DriverState {
1265    fn drop(&mut self) {
1266        if std::thread::panicking() {
1267            // Abort BEFORE any field destructor runs: the ring stays mapped
1268            // and the in-flight buffers stay allocated, so the kernel can
1269            // never write into freed memory.
1270            eprintln!(
1271                "uring-spike driver thread panicked with {} ops in flight; \
1272                 aborting to avoid UAF of in-flight buffers",
1273                self.pending.len()
1274            );
1275            std::process::abort();
1276        }
1277        // Normal drop: the shutdown invariant guarantees pending/backlog are
1278        // empty and in_flight == 0, so unmapping the ring here is safe.
1279    }
1280}
1281
1282/// Best-effort file length via `fstat` on the driver thread, used to tell a
1283/// genuine O_DIRECT tail short read from a non-block-multiple short read that
1284/// happened mid-file on a stacked filesystem (rustfs/backlog#1168). `None` when
1285/// the stat fails, in which case the caller keeps the conservative EOF
1286/// assumption rather than risk a wrong error or an unbounded resubmit loop.
1287fn file_len(file: &File) -> Option<u64> {
1288    file.metadata().ok().map(|m| m.len())
1289}
1290
1291/// Hand the caller exactly the logical range `[head, head + want)` of the read
1292/// region, truncated to what was actually read (rustfs/backlog#1102).
1293///
1294/// Alignment padding (`buf[..pad]`), the bytes before the logical range
1295/// (`head`), and the block-aligned tail after it never reach the caller — a
1296/// `BitrotReader` expecting an exact shard length would flag padded output as
1297/// corruption. Only bytes the kernel actually wrote are exposed: `avail` is
1298/// clamped to `nread`, so the zero-filled remainder of the buffer stays hidden
1299/// (content hygiene, C12 / rustfs/backlog#1062).
1300fn deliver(p: &mut Pending) -> Vec<u8> {
1301    let avail = p.nread.saturating_sub(p.head).min(p.want);
1302    let start = p.pad + p.head;
1303    // The buffered path (`align == 1`) has `pad == 0` and `head == 0`, so the
1304    // logical range already starts at byte 0 — skip the full-buffer memmove and
1305    // just truncate. Only the O_DIRECT path (nonzero start) needs the shift.
1306    if start != 0 && avail != 0 {
1307        p.buf.copy_within(start..start + avail, 0);
1308    }
1309    p.buf.truncate(avail);
1310    std::mem::take(&mut p.buf)
1311}
1312
1313/// What to do with a pending entry after its CQE (C9, rustfs/backlog#1058).
1314enum ReapStep {
1315    /// The logical read is done: remove the entry and deliver this result.
1316    Finish(io::Result<Vec<u8>>),
1317    /// Short read, not EOF: re-queue this SQE for the remainder; keep the entry.
1318    Resubmit(io_uring::squeue::Entry),
1319}
1320
1321/// Queue at most one `AsyncCancel` per op (rustfs/backlog#1167): a drop-cancel
1322/// followed by a shutdown, or the submit-error shutdown, must not enqueue a
1323/// second cancel for the same id. The set is bounded by the pending table
1324/// because ids are monotonic and an entry is removed when its op is reaped.
1325fn queue_cancel(backlog: &mut VecDeque<io_uring::squeue::Entry>, queued_cancels: &mut HashSet<u64>, id: u64) {
1326    if queued_cancels.insert(id) {
1327        backlog.push_back(opcode::AsyncCancel::new(id).build().user_data(id | CANCEL_BIT));
1328    }
1329}
1330
1331/// Push as much of the backlog into the SQ as fits, stopping when the ring is
1332/// full (the remainder retries next turn).
1333fn flush_backlog(ring: &mut IoUring, backlog: &mut VecDeque<io_uring::squeue::Entry>) {
1334    let mut sq = ring.submission();
1335    while let Some(sqe) = backlog.pop_front() {
1336        // SAFETY: read SQEs point into `pending`-owned buffers that live until
1337        // their CQE; cancel SQEs carry no pointers.
1338        if unsafe { sq.push(&sqe) }.is_err() {
1339            backlog.push_front(sqe);
1340            break;
1341        }
1342    }
1343}
1344
1345/// Flush the backlog into the SQ and submit it, with submit-error classification
1346/// (rustfs/backlog#1162). The single submit path for the whole loop: called once
1347/// after intake and once more after reap when resubmits were queued. Skips the
1348/// `io_uring_enter` syscall on an empty SQ (rustfs/backlog#1169). EINTR/EBUSY are
1349/// transient; any other errno is counted and, after a bounded run, transitions
1350/// the shard to shutdown so callers fall back to the std backend.
1351fn submit_ring(
1352    state: &mut DriverState,
1353    stats: &DriverStats,
1354    consecutive_submit_errors: &mut u32,
1355    submit_error_logged: &mut bool,
1356    shutting_down: &mut bool,
1357    queued_cancels: &mut HashSet<u64>,
1358) {
1359    flush_backlog(&mut state.ring, &mut state.backlog);
1360    if state.ring.submission().is_empty() {
1361        return;
1362    }
1363    match state.ring.submit() {
1364        Ok(_) => *consecutive_submit_errors = 0,
1365        // CQ-overflow backpressure (EBUSY) and signal interruption (EINTR) are
1366        // transient — retry next turn without counting them (C5, backlog#1056).
1367        Err(e) if matches!(e.raw_os_error(), Some(libc::EBUSY) | Some(libc::EINTR)) => *consecutive_submit_errors = 0,
1368        Err(e) => {
1369            // The queued SQEs were not accepted, so their CQEs never arrive. A
1370            // brief run may be transient (EAGAIN); a persistent one (e.g. EPERM
1371            // from a seccomp/LSM policy applied after startup) must not be retried
1372            // forever in silence.
1373            stats.submit_errors.fetch_add(1, Ordering::SeqCst);
1374            *consecutive_submit_errors += 1;
1375            if !*submit_error_logged {
1376                *submit_error_logged = true;
1377                tracing::warn!(error = %e, "uring driver: ring.submit() failed; retrying, will shut down if persistent");
1378            }
1379            if !*shutting_down && *consecutive_submit_errors >= MAX_CONSECUTIVE_SUBMIT_ERRORS {
1380                tracing::warn!(
1381                    consecutive_errors = *consecutive_submit_errors,
1382                    "uring driver: consecutive submit failures; shutting down so callers fall back to the std backend"
1383                );
1384                *shutting_down = true;
1385                let ids: Vec<u64> = state.pending.keys().copied().collect();
1386                for id in ids {
1387                    queue_cancel(&mut state.backlog, queued_cancels, id);
1388                }
1389            }
1390        }
1391    }
1392}
1393
1394fn drive(
1395    ring: IoUring,
1396    rx: mpsc::Receiver<Msg>,
1397    stats: Arc<DriverStats>,
1398    sem: Arc<Semaphore>,
1399    cq_efd: EventFd,
1400    wake_efd: Arc<EventFd>,
1401) {
1402    let mut state = DriverState {
1403        ring,
1404        pending: HashMap::new(),
1405        backlog: VecDeque::new(),
1406    };
1407    let mut shutting_down = false;
1408    let mut drain_deadline: Option<Instant> = None;
1409    // Consecutive non-transient submit failures, and a once-only log latch, for
1410    // the persistent-submit-failure escape hatch (rustfs/backlog#1162).
1411    let mut consecutive_submit_errors: u32 = 0;
1412    let mut submit_error_logged = false;
1413    // Ids with an AsyncCancel already queued, so a drop-cancel followed by a
1414    // shutdown (or vice versa) does not enqueue a second cancel for the same op —
1415    // keeping total completions <= 2*entries and CQ overflow unreachable
1416    // (rustfs/backlog#1167). Ids are monotonic, so an entry is removed only when
1417    // its pending op is reaped; the set stays bounded by the pending table.
1418    let mut queued_cancels: HashSet<u64> = HashSet::new();
1419
1420    // Bounded-drain deadline (C4, rustfs/backlog#1055). Production always uses the
1421    // fixed DRAIN_TIMEOUT; a fault-injection build may shorten it via env so the
1422    // leak-over-UAF escape hatch is testable without a 5 s wait (backlog#1103).
1423    // Read once here (not per turn) so a `--test-threads=1` env toggle in one
1424    // test never leaks into another's already-running driver thread.
1425    #[cfg(not(feature = "fault-injection"))]
1426    let drain_timeout = DRAIN_TIMEOUT;
1427    #[cfg(feature = "fault-injection")]
1428    let drain_timeout = std::env::var("RUSTFS_URING_FAULT_DRAIN_TIMEOUT_MS")
1429        .ok()
1430        .and_then(|ms| ms.parse().ok())
1431        .map(Duration::from_millis)
1432        .unwrap_or(DRAIN_TIMEOUT);
1433    // When set, drop an op's real completion on the floor so it stays pending and
1434    // the bounded drain is forced onto its timeout path (backlog#1103 → C4/#1055).
1435    #[cfg(feature = "fault-injection")]
1436    let fault_stuck_drain = std::env::var_os("RUSTFS_URING_FAULT_STUCK_DRAIN").is_some();
1437
1438    loop {
1439        // Block until a CQE is ready (the ring's registered eventfd), a new
1440        // message arrives (the wakeup eventfd), or the heartbeat elapses —
1441        // this replaces the spike's 200 µs busy-poll (backlog#1102). Draining
1442        // both eventfds after waking keeps them from staying spuriously
1443        // readable; a missed edge is harmless because the CQ/mpsc are re-checked
1444        // unconditionally below.
1445        // Adaptive heartbeat (rustfs/backlog#1169): poll at 50 ms only while
1446        // there is in-flight work to reap or a drain deadline to honor; when the
1447        // shard is fully idle, wait up to IDLE_HEARTBEAT. New work still wakes us
1448        // immediately via wake_efd and completions via cq_efd, so the longer idle
1449        // wait only cuts timer/syscall churn.
1450        let heartbeat = if shutting_down || !state.pending.is_empty() {
1451            LOOP_HEARTBEAT
1452        } else {
1453            IDLE_HEARTBEAT
1454        };
1455        wait_for_events(&cq_efd, &wake_efd, heartbeat);
1456        cq_efd.drain();
1457        wake_efd.drain();
1458
1459        // 1. Intake: drain all queued messages (the wait above did the blocking,
1460        //    so this is purely non-blocking).
1461        loop {
1462            let msg = match rx.try_recv() {
1463                Ok(m) => m,
1464                Err(TryRecvError::Empty) => break,
1465                Err(TryRecvError::Disconnected) => {
1466                    shutting_down = true;
1467                    break;
1468                }
1469            };
1470            match msg {
1471                Msg::Read {
1472                    id,
1473                    file,
1474                    offset,
1475                    len,
1476                    done,
1477                    permit,
1478                    align,
1479                } => {
1480                    if shutting_down {
1481                        let _ = done.send(Err(io::Error::other("uring driver shutting down")));
1482                        // The op never became in-flight; dropping `permit` here
1483                        // returns it immediately.
1484                        drop(permit);
1485                        continue;
1486                    }
1487                    // `submit` already validated this geometry.
1488                    let (kernel_offset, head, region_len) =
1489                        aligned_geometry(offset, len, align).expect("submit validated the geometry");
1490                    // For an O_DIRECT read the kernel needs a block-aligned
1491                    // buffer, so over-allocate by `align - 1` and start the read
1492                    // region at the first aligned byte inside the allocation.
1493                    // For a buffered read this degenerates to `vec![0u8; len]`.
1494                    // `submit` already capped `align <= MAX_READ_LEN` and
1495                    // `region_len <= MAX_READ_LEN`, so this add cannot overflow;
1496                    // the checked form keeps the invariant explicit rather than
1497                    // relying on it silently.
1498                    let cap = match region_len.checked_add(align - 1) {
1499                        Some(cap) => cap,
1500                        None => {
1501                            let _ = done.send(Err(io::Error::other("aligned O_DIRECT allocation size overflow")));
1502                            drop(permit);
1503                            continue;
1504                        }
1505                    };
1506                    let buf = vec![0u8; cap];
1507                    let pad = buf.as_ptr().align_offset(align);
1508                    // Runtime guard (not a debug-only assert): if the allocator
1509                    // ever returned a block `align_offset` cannot satisfy, refuse
1510                    // the read instead of doing UB pointer arithmetic below.
1511                    if pad == usize::MAX || pad.checked_add(region_len).is_none_or(|end| end > buf.len()) {
1512                        let _ = done.send(Err(io::Error::other("could not align O_DIRECT read buffer")));
1513                        drop(permit);
1514                        continue;
1515                    }
1516
1517                    // Move the buffer into the pending table (which owns it until
1518                    // the CQE), THEN build the SQE from the entry: the initial read
1519                    // is `read_sqe` with `nread == 0`, so the read-region pointer
1520                    // math and the `Read` builder live in exactly one place.
1521                    // Moving the Vec never relocates its heap block, so the pointer
1522                    // the SQE captures stays valid.
1523                    state.pending.insert(
1524                        id,
1525                        Pending {
1526                            buf,
1527                            file,
1528                            done: Some(done),
1529                            offset: kernel_offset,
1530                            nread: 0,
1531                            // Released exactly when this entry is removed at the
1532                            // final CQE — never at future drop (backlog#1060).
1533                            _permit: permit,
1534                            pad,
1535                            head,
1536                            want: len,
1537                            region_len,
1538                            align,
1539                            transient_retries: 0,
1540                        },
1541                    );
1542                    let sqe = state.pending.get(&id).expect("just inserted").read_sqe(id);
1543                    stats.submitted.fetch_add(1, Ordering::SeqCst);
1544                    stats.in_flight.fetch_add(1, Ordering::SeqCst);
1545                    state.backlog.push_back(sqe);
1546                }
1547                Msg::Cancel { id } => {
1548                    if state.pending.contains_key(&id) {
1549                        queue_cancel(&mut state.backlog, &mut queued_cancels, id);
1550                    }
1551                }
1552                Msg::Shutdown => {
1553                    shutting_down = true;
1554                    let ids: Vec<u64> = state.pending.keys().copied().collect();
1555                    for id in ids {
1556                        queue_cancel(&mut state.backlog, &mut queued_cancels, id);
1557                    }
1558                }
1559                #[cfg(feature = "fault-injection")]
1560                Msg::TestPanic => {
1561                    // Panic WITH buffers still in flight: the abort barrier in
1562                    // `DriverState::Drop` must fire rather than let the unwind
1563                    // free them under the kernel (rustfs/backlog#1103 → C2/#1054).
1564                    panic!(
1565                        "fault-injection: driver thread panic requested with {} ops in flight",
1566                        state.pending.len()
1567                    );
1568                }
1569            }
1570        }
1571
1572        // 2. Flush the backlog into the SQ and submit it (the single submit path;
1573        //    see `submit_ring`).
1574        submit_ring(
1575            &mut state,
1576            &stats,
1577            &mut consecutive_submit_errors,
1578            &mut submit_error_logged,
1579            &mut shutting_down,
1580            &mut queued_cancels,
1581        );
1582
1583        // 3. Reap. A Pending entry (and thus its buffer) is dropped ONLY when
1584        //    the logical read finishes; a short read is resubmitted for the
1585        //    remainder and the entry stays put (C9, rustfs/backlog#1058).
1586        while let Some(cqe) = state.ring.completion().next() {
1587            let ud = cqe.user_data();
1588            if ud & CANCEL_BIT != 0 {
1589                // Result of the AsyncCancel op itself; the read's own CQE
1590                // (ECANCELED or success) still arrives separately. Record the
1591                // three-state outcome for diagnosability (C4,
1592                // rustfs/backlog#1055): EALREADY means the read is executing
1593                // and cannot be interrupted, i.e. its CQE may never come on a
1594                // hung device — the signal the bounded drain below relies on.
1595                match cqe.result() {
1596                    0 => stats.cancel_succeeded.fetch_add(1, Ordering::SeqCst),
1597                    r if r == -libc::ENOENT => stats.cancel_not_found.fetch_add(1, Ordering::SeqCst),
1598                    r if r == -libc::EALREADY => stats.cancel_already.fetch_add(1, Ordering::SeqCst),
1599                    _ => 0,
1600                };
1601                continue;
1602            }
1603            // fault-injection (backlog#1103 → C4/#1055): drop this real completion
1604            // so the op stays pending and the bounded drain must take its
1605            // DRAIN_TIMEOUT leak path. The CQE has already arrived, so the kernel
1606            // is done with the buffer — the eventual `forget` leaks a completed
1607            // allocation, never live memory.
1608            #[cfg(feature = "fault-injection")]
1609            if fault_stuck_drain && state.pending.contains_key(&ud) {
1610                continue;
1611            }
1612            let res = cqe.result();
1613            if !state.pending.contains_key(&ud) {
1614                continue;
1615            }
1616
1617            // Decide the next step while borrowing the entry, then act after
1618            // the borrow ends (finish removes it; resubmit re-queues an SQE).
1619            let step = {
1620                let p = state.pending.get_mut(&ud).expect("checked above");
1621                if res < 0 {
1622                    let err = -res;
1623                    // C7 three-class contract (rustfs/backlog#1166): a transient
1624                    // errno (EINTR/EAGAIN) must be retried, not surfaced as the
1625                    // read's final result — surfacing it would also discard the
1626                    // already-read prefix of a resubmit. Bounded per logical read
1627                    // so a storm cannot spin the driver thread. Streams
1628                    // (CURRENT_POSITION) cannot resubmit positionally; ECANCELED
1629                    // and every other errno terminate the logical read.
1630                    let transient = err == libc::EINTR || err == libc::EAGAIN;
1631                    if transient
1632                        && p.offset != CURRENT_POSITION
1633                        && p.nread < p.region_len
1634                        && p.transient_retries < MAX_TRANSIENT_RETRIES
1635                    {
1636                        p.transient_retries += 1;
1637                        ReapStep::Resubmit(p.read_sqe(ud))
1638                    } else {
1639                        // Error (incl. ECANCELED, or a transient errno past its
1640                        // retry budget) terminates the logical read.
1641                        ReapStep::Finish(Err(io::Error::from_raw_os_error(err)))
1642                    }
1643                } else if res == 0 {
1644                    // Real EOF: deliver whatever of the logical range was read.
1645                    ReapStep::Finish(Ok(deliver(p)))
1646                } else {
1647                    p.nread += res as usize;
1648                    // Progress resets the transient-retry budget (rustfs/backlog#1166).
1649                    p.transient_retries = 0;
1650                    // Only POSITIONED reads (read_at / read_at_direct, whole-range
1651                    // pread contract) resubmit a short read. CURRENT_POSITION
1652                    // reads (read_current on pipes/streams) follow read(2)
1653                    // semantics: a short read is a valid final result and must be
1654                    // delivered as-is — resubmitting would block forever waiting
1655                    // for stream data that may never come.
1656                    let is_stream = p.offset == CURRENT_POSITION;
1657                    let covered = p.nread >= p.head + p.want;
1658                    if is_stream || covered || p.nread >= p.region_len {
1659                        ReapStep::Finish(Ok(deliver(p)))
1660                    } else if p.align > 1 && !p.nread.is_multiple_of(p.align) {
1661                        // O_DIRECT non-block-multiple short read below the covered
1662                        // range. The kernel returns block multiples EXCEPT at the
1663                        // file tail — but a stacked filesystem (NFS/FUSE, or a
1664                        // signal-split direct I/O) can legally return a non-multiple
1665                        // mid-file, and assuming EOF there would silently truncate
1666                        // the delivered range. Disambiguate with the actual file
1667                        // length instead of inferring it (rustfs/backlog#1168).
1668                        match file_len(&p.file) {
1669                            // Genuine tail: at or past EOF — deliver what we read.
1670                            Some(len) if p.offset + p.nread as u64 >= len => ReapStep::Finish(Ok(deliver(p))),
1671                            // Mid-file non-multiple: an O_DIRECT read cannot resubmit
1672                            // from a non-block-aligned offset, so surface an error
1673                            // rather than truncate. The integration falls back to
1674                            // the std backend for this read, preserving correctness.
1675                            Some(_) => ReapStep::Finish(Err(io::Error::other(
1676                                "io_uring O_DIRECT: non-block-aligned short read before EOF",
1677                            ))),
1678                            // fstat failed: keep the conservative EOF assumption
1679                            // rather than risk a wrong error or an infinite loop.
1680                            None => ReapStep::Finish(Ok(deliver(p))),
1681                        }
1682                    } else {
1683                        // Positioned short read, not EOF, block-aligned: resubmit
1684                        // the remainder into the read region. The buffer stays
1685                        // owned by the driver and in_flight is unchanged — one
1686                        // logical op.
1687                        ReapStep::Resubmit(p.read_sqe(ud))
1688                    }
1689                }
1690            };
1691
1692            match step {
1693                ReapStep::Finish(outcome) => {
1694                    // Content hygiene (C12, rustfs/backlog#1062): the delivered
1695                    // bytes are ⊆ [0, res) — buf was freshly zeroed per op and
1696                    // truncated to res. When P3 reuses a driver-owned slab
1697                    // across requests, this ⊆ [0, res) property MUST be
1698                    // preserved or a previous tenant's object bytes leak.
1699                    let mut p = state.pending.remove(&ud).expect("checked above");
1700                    match p.done.take().expect("done sender set at submit").send(outcome) {
1701                        Ok(()) => stats.delivered.fetch_add(1, Ordering::SeqCst),
1702                        // Caller dropped the future: the buffer survived in
1703                        // the table until this final CQE and is reclaimed here.
1704                        Err(_) => stats.orphan_reclaimed.fetch_add(1, Ordering::SeqCst),
1705                    };
1706                    stats.in_flight.fetch_sub(1, Ordering::SeqCst);
1707                    // Drop any queued-cancel bookkeeping for this now-gone op so
1708                    // the dedup set stays bounded by the pending table
1709                    // (rustfs/backlog#1167).
1710                    queued_cancels.remove(&ud);
1711                    // `p` (and with it `_permit`) is dropped here, at the CQE
1712                    // and pending-table removal — never at future drop (C10,
1713                    // rustfs/backlog#1060). No manual release to forget.
1714                }
1715                ReapStep::Resubmit(sqe) => state.backlog.push_back(sqe),
1716            }
1717        }
1718
1719        // A short-read resubmit queued during reap must reach the kernel in THIS
1720        // turn, not wait out the next heartbeat (rustfs/backlog#1163). Reap runs
1721        // after the submit above, so re-run the single submit path when reap left
1722        // work in the backlog; an idle turn leaves it empty and skips the call.
1723        if !state.backlog.is_empty() {
1724            submit_ring(
1725                &mut state,
1726                &stats,
1727                &mut consecutive_submit_errors,
1728                &mut submit_error_logged,
1729                &mut shutting_down,
1730                &mut queued_cancels,
1731            );
1732        }
1733
1734        // Monitor CQ overflow. With NODROP (asserted at probe) overflowed CQEs
1735        // are BUFFERED in the kernel overflow list and flushed on the next enter,
1736        // never lost — so a non-zero value is a backpressure warning, not fatal
1737        // loss (rustfs/backlog#1056, #1167). In-flight reads are capped at
1738        // `entries` and cancels are deduped (at most one per op), keeping total
1739        // completions <= 2*entries, so this should stay 0 in practice.
1740        let overflow = state.ring.completion().overflow();
1741        if overflow != 0 {
1742            stats.cq_overflow.store(overflow as u64, Ordering::SeqCst);
1743            tracing::warn!(
1744                overflow,
1745                "uring driver: CQ overflow; CQEs buffered (NODROP), not lost — backpressure warning"
1746            );
1747        }
1748
1749        // 4. Exit when drained: the kernel no longer references any buffer, so
1750        //    dropping the ring (unmap) is safe. If a hung device keeps a CQE
1751        //    from ever arriving, bail out under a bounded deadline instead of
1752        //    blocking forever (C4, rustfs/backlog#1055).
1753        if shutting_down {
1754            if state.pending.is_empty() && state.backlog.is_empty() {
1755                // Close the semaphore so any handle still awaiting a permit
1756                // resolves with a driver-gone error instead of hanging.
1757                sem.close();
1758                return; // clean drain: DriverState drops normally, ring unmaps.
1759            }
1760            let deadline = *drain_deadline.get_or_insert_with(|| Instant::now() + drain_timeout);
1761            if Instant::now() >= deadline {
1762                // A CQE may never arrive (ASYNC_CANCEL cannot interrupt an
1763                // in-execution regular-file read on a hung disk). We must NOT
1764                // unmap the ring or free the still-in-flight buffers — leak the
1765                // whole state (leak over UAF) and exit so shutdown() returns.
1766                tracing::warn!(
1767                    in_flight = state.pending.len(),
1768                    "uring driver: bounded drain timed out with ops still in flight; leaking ring + buffers to stay memory-safe"
1769                );
1770                // Fail every stranded caller BEFORE leaking the pending table.
1771                // `oneshot::Sender::send` consumes the sender and never touches
1772                // `p.buf`, so the kernel-owned buffer stays allocated (leak over
1773                // UAF preserved) while an awaited `ReadHandle` resolves with an
1774                // error instead of pending forever — every other driver-gone path
1775                // already delivers an error, and this one must too
1776                // (rustfs/backlog#1161).
1777                for p in state.pending.values_mut() {
1778                    if let Some(tx) = p.done.take() {
1779                        let _ = tx.send(Err(io::Error::other("uring driver leaked op on bounded-drain timeout")));
1780                    }
1781                }
1782                // Close the semaphore so any handle still awaiting a permit
1783                // resolves with a driver-gone error too. The leaked pending
1784                // entries keep their permits, which is fine: nothing waits on
1785                // them any more.
1786                sem.close();
1787                // The leaked ring still has `cq_efd` registered via
1788                // IORING_REGISTER_EVENTFD and in-flight ops that may post CQEs, so
1789                // the eventfd must outlive it. Leak it alongside the ring instead
1790                // of letting the returning `drive` drop (close) it out from under
1791                // the still-mapped ring, honoring start_shard's documented "cq_efd
1792                // outlives the ring" invariant on this exit too (rustfs/backlog#1167).
1793                std::mem::forget(cq_efd);
1794                std::mem::forget(state);
1795                return;
1796            }
1797        }
1798        // No pacing sleep: `wait_for_events` at the top of the loop blocks until
1799        // the next CQE, message, or heartbeat (backlog#1102).
1800    }
1801}