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