Skip to main content

acyclic_native_runtime/
lib.rs

1//! Private native file primitives shared by durable local providers.
2
3use bytes::Bytes;
4#[cfg(any(windows, target_os = "linux", target_vendor = "apple"))]
5use std::cell::RefCell;
6#[cfg(any(windows, target_os = "linux", target_vendor = "apple"))]
7use std::collections::HashMap;
8use std::collections::VecDeque;
9use std::fs::File;
10use std::future::Future;
11use std::io::{self, Read};
12use std::path::Path;
13use std::pin::Pin;
14use std::sync::atomic::{AtomicBool, Ordering};
15use std::sync::{Arc, Condvar, Mutex, OnceLock, Weak, mpsc};
16use std::task::{Context, Poll, Waker};
17
18type FileSequencer = Arc<Mutex<Option<Arc<OperationFence>>>>;
19
20#[cfg(windows)]
21type FileIdentity = windows::FileIdentity;
22#[cfg(target_os = "linux")]
23type FileIdentity = linux::FileIdentity;
24#[cfg(target_vendor = "apple")]
25type FileIdentity = apple::FileIdentity;
26
27#[cfg(any(windows, target_os = "linux", target_vendor = "apple"))]
28struct FileHealth {
29    tail: FileSequencer,
30    uncertain: Arc<AtomicBool>,
31    // Only an uncertain identity needs an open handle to prevent inode reuse.
32    // Retaining healthy handles here would outlive their NativeFile owner and
33    // can keep an exclusive Windows share lock open indefinitely.
34    poison_anchor: Option<Arc<File>>,
35}
36
37#[cfg(any(windows, target_os = "linux", target_vendor = "apple"))]
38static FILE_HEALTH: OnceLock<Mutex<FileHealthRegistry>> = OnceLock::new();
39
40#[cfg(any(windows, target_os = "linux", target_vendor = "apple"))]
41#[derive(Default)]
42struct FileHealthRegistry {
43    entries: HashMap<FileIdentity, FileHealth>,
44}
45
46#[cfg(any(windows, target_os = "linux", target_vendor = "apple"))]
47impl FileHealthRegistry {
48    fn sweep_idle(&mut self) {
49        self.entries.retain(|_, health| {
50            health.poison_anchor.is_some()
51                || Arc::strong_count(&health.tail) > 1
52                || Arc::strong_count(&health.uncertain) > 1
53        });
54    }
55}
56
57#[cfg(any(windows, target_os = "linux", target_vendor = "apple"))]
58fn file_identity(file: &File) -> io::Result<FileIdentity> {
59    #[cfg(windows)]
60    return windows::file_identity(file);
61    #[cfg(target_os = "linux")]
62    return linux::file_identity(file);
63    #[cfg(target_vendor = "apple")]
64    apple::file_identity(file)
65}
66
67#[cfg(any(windows, target_os = "linux", target_vendor = "apple"))]
68thread_local! {
69    static ACTIVE_FILES: RefCell<Vec<usize>> = const { RefCell::new(Vec::new()) };
70}
71
72#[cfg(any(windows, target_os = "linux", target_vendor = "apple"))]
73struct FileScope(usize);
74
75#[cfg(any(windows, target_os = "linux", target_vendor = "apple"))]
76impl FileScope {
77    fn enter(tail: &FileSequencer) -> Self {
78        let identity = Arc::as_ptr(tail) as usize;
79        ACTIVE_FILES.with_borrow_mut(|active| active.push(identity));
80        Self(identity)
81    }
82
83    fn contains(tail: &FileSequencer) -> bool {
84        let identity = Arc::as_ptr(tail) as usize;
85        ACTIVE_FILES.with_borrow(|active| active.contains(&identity))
86    }
87}
88
89#[cfg(any(windows, target_os = "linux", target_vendor = "apple"))]
90impl Drop for FileScope {
91    fn drop(&mut self) {
92        ACTIVE_FILES.with_borrow_mut(|active| {
93            assert_eq!(
94                active.pop(),
95                Some(self.0),
96                "native file scope order changed"
97            );
98        });
99    }
100}
101
102#[cfg(any(windows, target_os = "linux", target_vendor = "apple"))]
103fn shared_file_health(file: &File) -> io::Result<(FileSequencer, Arc<AtomicBool>)> {
104    let identity = file_identity(file)?;
105    let registry_lock = FILE_HEALTH.get_or_init(|| Mutex::new(FileHealthRegistry::default()));
106    let mut registry = registry_lock
107        .lock()
108        .unwrap_or_else(std::sync::PoisonError::into_inner);
109    if let Some(health) = registry.entries.get(&identity) {
110        if health.poison_anchor.is_some()
111            || Arc::strong_count(&health.tail) > 1
112            || Arc::strong_count(&health.uncertain) > 1
113        {
114            return Ok((Arc::clone(&health.tail), Arc::clone(&health.uncertain)));
115        }
116        // No owner can still refer to this healthy gate. An OS identity may
117        // have been reused since the last admission, so start a fresh gate.
118        registry.entries.remove(&identity);
119    }
120    #[cfg(target_os = "linux")]
121    let capacity = linux::file_identity_capacity();
122    #[cfg(target_vendor = "apple")]
123    let capacity = apple::file_identity_capacity();
124    #[cfg(windows)]
125    let capacity: usize = 4096;
126    // Sweep metadata only near capacity. A queue of historical identities
127    // would grow without bound when one healthy file is repeatedly reopened.
128    if registry.entries.len() >= capacity {
129        registry.sweep_idle();
130    }
131    if registry.entries.len() >= capacity {
132        return Err(io::Error::other("native file admission capacity exhausted"));
133    }
134    let tail = Arc::new(Mutex::new(None));
135    let uncertain = Arc::new(AtomicBool::new(false));
136    registry.entries.insert(
137        identity,
138        FileHealth {
139            tail: Arc::clone(&tail),
140            uncertain: Arc::clone(&uncertain),
141            poison_anchor: None,
142        },
143    );
144    Ok((tail, uncertain))
145}
146
147#[cfg(any(windows, target_os = "linux", target_vendor = "apple"))]
148fn poison_file_health(file: &Arc<File>, uncertain: &Arc<AtomicBool>) {
149    let identity = file_identity(file).unwrap_or_else(|_| std::process::abort());
150    let registry_lock = FILE_HEALTH.get().unwrap_or_else(|| std::process::abort());
151    let mut registry = registry_lock
152        .lock()
153        .unwrap_or_else(std::sync::PoisonError::into_inner);
154    let health = registry
155        .entries
156        .get_mut(&identity)
157        .unwrap_or_else(|| std::process::abort());
158    if !Arc::ptr_eq(&health.uncertain, uncertain) {
159        std::process::abort();
160    }
161    health.poison_anchor.get_or_insert_with(|| Arc::clone(file));
162    uncertain.store(true, Ordering::Release);
163}
164
165#[cfg(any(windows, target_os = "linux", target_vendor = "apple"))]
166fn poison_borrowed_file_health(file: &File, uncertain: &Arc<AtomicBool>) {
167    let anchor = Arc::new(file.try_clone().unwrap_or_else(|_| std::process::abort()));
168    poison_file_health(&anchor, uncertain);
169}
170
171#[cfg(any(windows, target_os = "linux", target_vendor = "apple"))]
172fn with_file_admission<T>(file: &File, operation: impl FnOnce() -> io::Result<T>) -> io::Result<T> {
173    let (tail, uncertain) = shared_file_health(file)?;
174    // A control/sync operation already running under this file's admitted
175    // fence may call the direct API. It must inherit that fence, not enqueue
176    // behind itself and deadlock the worker.
177    if FileScope::contains(&tail) {
178        if uncertain.load(Ordering::Acquire) {
179            return Err(io::Error::other(
180                "prior native I/O completion on this file is uncertain",
181            ));
182        }
183        let result = std::panic::catch_unwind(std::panic::AssertUnwindSafe(operation))
184            .unwrap_or_else(|_| std::process::abort());
185        if result.as_ref().err().is_some_and(is_uncertain_io_error) {
186            poison_borrowed_file_health(file, &uncertain);
187        }
188        return result;
189    }
190    let (predecessor, completion) = {
191        let mut last = tail
192            .lock()
193            .unwrap_or_else(std::sync::PoisonError::into_inner);
194        let predecessor = last.clone();
195        let completion = Arc::new(OperationFence::default());
196        *last = Some(Arc::clone(&completion));
197        (predecessor, completion)
198    };
199    if let Some(predecessor) = predecessor {
200        predecessor.wait_until_complete();
201    }
202    if uncertain.load(Ordering::Acquire) {
203        completion.complete();
204        return Err(io::Error::other(
205            "prior native I/O completion on this file is uncertain",
206        ));
207    }
208    let scope = FileScope::enter(&tail);
209    let result = std::panic::catch_unwind(std::panic::AssertUnwindSafe(operation))
210        .unwrap_or_else(|_| std::process::abort());
211    if result.as_ref().err().is_some_and(is_uncertain_io_error) {
212        poison_borrowed_file_health(file, &uncertain);
213    }
214    drop(scope);
215    completion.complete();
216    result
217}
218
219/// One file owned by the native I/O runtime.
220///
221/// Callers retain responsibility for capability-safe path resolution and pass
222/// the resulting handle here exactly once. Operations submitted through this
223/// handle execute in first-poll order. Creating an operation is lazy: an
224/// unpolled future neither submits I/O nor delays later operations. Once an
225/// operation has been polled, dropping its observer does not overtake earlier
226/// admitted work.
227pub struct NativeFile {
228    file: Arc<File>,
229    tail: FileSequencer,
230    uncertain: Arc<AtomicBool>,
231    #[cfg(windows)]
232    overlapped: bool,
233}
234
235impl NativeFile {
236    /// Transfers ownership of an already-authorized file handle to the runtime.
237    pub fn from_file(file: File) -> io::Result<Self> {
238        let file = Arc::new(file);
239        #[cfg(any(windows, target_os = "linux", target_vendor = "apple"))]
240        let (tail, uncertain) = shared_file_health(&file)?;
241        #[cfg(not(any(windows, target_os = "linux", target_vendor = "apple")))]
242        let (tail, uncertain) = (Arc::new(Mutex::new(None)), Arc::new(AtomicBool::new(false)));
243        Ok(Self {
244            file,
245            tail,
246            uncertain,
247            #[cfg(windows)]
248            overlapped: false,
249        })
250    }
251
252    /// Transfers a Windows handle already opened for overlapped I/O.
253    ///
254    /// # Safety
255    /// The handle must have been opened with `FILE_FLAG_OVERLAPPED`, must not
256    /// already be associated with an I/O completion port, and must have no
257    /// concurrent independent I/O outside this `NativeFile`. The caller must
258    /// transfer the sole I/O ownership of the handle to this function.
259    #[cfg(windows)]
260    #[allow(unsafe_code)]
261    pub unsafe fn from_overlapped_file_unchecked(file: File) -> io::Result<Self> {
262        let mut native = Self::from_file(file)?;
263        native.overlapped = true;
264        Ok(native)
265    }
266
267    /// Changes the logical file length without blocking the caller's executor.
268    #[must_use]
269    pub fn set_len_async(&self, size: u64) -> UnitCompletion {
270        UnitCompletion::submit(
271            Arc::clone(&self.file),
272            NativeUnitOperation::SetLen(size),
273            Some(Arc::clone(&self.tail)),
274            Some(Arc::clone(&self.uncertain)),
275            #[cfg(windows)]
276            self.overlapped,
277        )
278    }
279
280    /// Runs a platform-specific handle control in this file's operation order.
281    /// The closure owns no borrowed handle after completion and executes off
282    /// the caller's async executor when native completion is unavailable.
283    #[must_use]
284    pub fn control_async(
285        &self,
286        operation: impl FnOnce(&File) -> io::Result<()> + Send + 'static,
287    ) -> UnitCompletion {
288        UnitCompletion::submit(
289            Arc::clone(&self.file),
290            NativeUnitOperation::Control(Box::new(operation)),
291            Some(Arc::clone(&self.tail)),
292            Some(Arc::clone(&self.uncertain)),
293            #[cfg(windows)]
294            self.overlapped,
295        )
296    }
297
298    /// Submits owned positional reads while retaining this handle.
299    #[must_use]
300    pub fn read_batch_async(&self, reads: Vec<OwnedRead>) -> ReadBatch {
301        ReadBatch::submit(
302            Arc::clone(&self.file),
303            reads,
304            Some(Arc::clone(&self.tail)),
305            Some(Arc::clone(&self.uncertain)),
306            #[cfg(windows)]
307            self.overlapped,
308        )
309    }
310
311    /// Submits owned writes while retaining this handle for later operations.
312    #[must_use]
313    pub fn write_all_batch_async(&self, writes: Vec<OwnedWrite>) -> UnitCompletion {
314        UnitCompletion::submit(
315            Arc::clone(&self.file),
316            NativeUnitOperation::Write(writes),
317            Some(Arc::clone(&self.tail)),
318            Some(Arc::clone(&self.uncertain)),
319            #[cfg(windows)]
320            self.overlapped,
321        )
322    }
323
324    /// Flushes file contents and metadata without blocking the caller's executor.
325    #[must_use]
326    pub fn sync_async(&self, durability: Durability) -> UnitCompletion {
327        UnitCompletion::submit(
328            Arc::clone(&self.file),
329            NativeUnitOperation::Sync(durability),
330            Some(Arc::clone(&self.tail)),
331            Some(Arc::clone(&self.uncertain)),
332            #[cfg(windows)]
333            self.overlapped,
334        )
335    }
336}
337
338#[cfg(target_vendor = "apple")]
339mod apple;
340#[cfg(target_os = "linux")]
341mod linux;
342mod process_tree;
343#[cfg(windows)]
344mod windows;
345
346pub use process_tree::ProcessTree;
347
348/// Whether a native operation may still have an unresolved kernel completion.
349///
350/// Such an error is not an ordinary retryable I/O failure: callers must not
351/// assume the previous write was rolled back or safely publish another view.
352pub fn is_uncertain_io_error(error: &io::Error) -> bool {
353    #[cfg(target_os = "linux")]
354    {
355        linux::is_uncertain_completion(error)
356    }
357    #[cfg(windows)]
358    {
359        let _ = error;
360        false
361    }
362    #[cfg(not(any(target_os = "linux", windows)))]
363    {
364        let _ = error;
365        false
366    }
367}
368
369/// Shared lifetime token for native resources that must not outlive an external owner.
370///
371/// Cloning the token extends the owner's lifetime. The owned value is released only after the
372/// final token is dropped, allowing layered providers to carry a process-local ownership gate
373/// alongside every independently cloned native handle.
374#[derive(Clone)]
375pub struct OwnershipAnchor {
376    // Keep this field before `release`: Rust drops fields in declaration order, so the notifier
377    // observes the owner count after this anchor has released its strong reference.
378    _owner: Arc<dyn Send + Sync>,
379    release: OwnershipReleaseNotifier,
380}
381
382#[derive(Clone)]
383struct OwnershipReleaseNotifier {
384    owner: Weak<dyn Send + Sync>,
385    released: Arc<(Mutex<()>, Condvar)>,
386}
387
388impl Drop for OwnershipReleaseNotifier {
389    fn drop(&mut self) {
390        if self.owner.strong_count() == 0 {
391            let (lock, released) = &*self.released;
392            let _guard = lock
393                .lock()
394                .unwrap_or_else(std::sync::PoisonError::into_inner);
395            released.notify_all();
396        }
397    }
398}
399
400impl OwnershipAnchor {
401    /// Erases and retains one ownership value behind a cloneable lifetime token.
402    pub fn new(owner: impl Send + Sync + 'static) -> Self {
403        let owner: Arc<dyn Send + Sync> = Arc::new(owner);
404        Self {
405            release: OwnershipReleaseNotifier {
406                owner: Arc::downgrade(&owner),
407                released: Arc::new((Mutex::new(()), Condvar::new())),
408            },
409            _owner: owner,
410        }
411    }
412
413    /// Observes the exact boundary at which every clone has released the owner.
414    #[must_use]
415    pub fn release_barrier(&self) -> OwnershipReleaseBarrier {
416        OwnershipReleaseBarrier {
417            owner: self.release.owner.clone(),
418            released: Arc::clone(&self.release.released),
419        }
420    }
421}
422
423/// A non-owning release condition for an [`OwnershipAnchor`] clone family.
424///
425/// The barrier never extends the protected owner's lifetime. Waiting completes only after the
426/// final anchor clone has dropped the owner, making it suitable for explicit shutdown boundaries.
427pub struct OwnershipReleaseBarrier {
428    owner: Weak<dyn Send + Sync>,
429    released: Arc<(Mutex<()>, Condvar)>,
430}
431
432impl OwnershipReleaseBarrier {
433    /// Returns whether every ownership-anchor clone has released the owner.
434    #[must_use]
435    pub fn is_released(&self) -> bool {
436        self.owner.strong_count() == 0
437    }
438
439    /// Blocks until every ownership-anchor clone has released the owner.
440    pub fn wait(self) {
441        let (lock, released) = &*self.released;
442        let mut guard = lock
443            .lock()
444            .unwrap_or_else(std::sync::PoisonError::into_inner);
445        while self.owner.strong_count() != 0 {
446            guard = released
447                .wait(guard)
448                .unwrap_or_else(std::sync::PoisonError::into_inner);
449        }
450    }
451}
452
453/// Required durability boundary for one file operation.
454#[derive(Clone, Copy, Debug, Eq, PartialEq)]
455pub enum Durability {
456    /// Flush file contents and metadata to stable storage.
457    Full,
458    /// Order earlier writes before later writes without forcing the device cache to drain.
459    ///
460    /// This capability is currently exposed only where the operating system provides that
461    /// exact primitive. Other targets return [`io::ErrorKind::Unsupported`].
462    Barrier,
463}
464
465/// Flushes file contents and metadata according to `durability`.
466pub fn sync_file(file: &File, durability: Durability) -> io::Result<()> {
467    #[cfg(any(windows, target_os = "linux", target_vendor = "apple"))]
468    return with_file_admission(file, || sync_file_unsequenced(file, durability));
469    #[cfg(not(any(windows, target_os = "linux", target_vendor = "apple")))]
470    sync_file_unsequenced(file, durability)
471}
472
473fn sync_file_unsequenced(file: &File, durability: Durability) -> io::Result<()> {
474    match durability {
475        Durability::Full => file.sync_all(),
476        Durability::Barrier => barrier_sync(file),
477    }
478}
479
480/// Flushes file data according to `durability`.
481pub fn sync_data(file: &File, durability: Durability) -> io::Result<()> {
482    #[cfg(any(windows, target_os = "linux", target_vendor = "apple"))]
483    return with_file_admission(file, || sync_data_unsequenced(file, durability));
484    #[cfg(not(any(windows, target_os = "linux", target_vendor = "apple")))]
485    sync_data_unsequenced(file, durability)
486}
487
488fn sync_data_unsequenced(file: &File, durability: Durability) -> io::Result<()> {
489    match durability {
490        Durability::Full => file.sync_data(),
491        Durability::Barrier => barrier_sync(file),
492    }
493}
494
495/// Flushes the directory entry namespace on platforms exposing directory flushes.
496pub fn sync_parent(path: &Path, durability: Durability) -> io::Result<()> {
497    sync_parent_impl(path, durability)
498}
499
500/// Whether an exclusive file-lock failure proves that another owner holds the lock.
501/// Other failures must retain their original I/O error for recovery decisions.
502#[must_use]
503pub fn is_exclusive_lock_contention(error: &io::Error) -> bool {
504    error.kind() == io::ErrorKind::WouldBlock || cfg!(windows) && error.raw_os_error() == Some(33)
505}
506
507/// Destination behavior for one durable rename.
508#[derive(Clone, Copy, Debug, Eq, PartialEq)]
509pub enum RenameMode {
510    /// Fail atomically when the destination already exists.
511    NoReplace,
512    /// Atomically replace a compatible destination entry.
513    Replace,
514}
515
516/// Renames one filesystem entry and durably publishes the affected namespace.
517pub fn durable_rename(from: &Path, to: &Path, mode: RenameMode) -> io::Result<()> {
518    durable_rename_impl(from, to, mode)
519}
520
521/// Reads at an absolute offset without changing the file cursor.
522pub fn read_at(file: &File, offset: u64, destination: &mut [u8]) -> io::Result<usize> {
523    #[cfg(any(windows, target_os = "linux", target_vendor = "apple"))]
524    return with_file_admission(file, || read_at_impl(file, offset, destination));
525    #[cfg(not(any(windows, target_os = "linux", target_vendor = "apple")))]
526    read_at_impl(file, offset, destination)
527}
528
529/// Writes every byte at an absolute offset without changing the file cursor.
530pub fn write_all_at(file: &File, offset: u64, bytes: &[u8]) -> io::Result<()> {
531    #[cfg(any(windows, target_os = "linux", target_vendor = "apple"))]
532    return with_file_admission(file, || write_all_at_impl(file, offset, bytes));
533    #[cfg(not(any(windows, target_os = "linux", target_vendor = "apple")))]
534    write_all_at_impl(file, offset, bytes)
535}
536
537/// Applies the exact native Windows file-attribute bitset.
538#[cfg(windows)]
539pub fn set_file_attributes(path: &Path, attributes: u32) -> io::Result<()> {
540    windows::set_file_attributes(path, attributes)
541}
542
543/// One owned positional write whose buffer remains live through completion.
544#[derive(Clone, Debug, Eq, PartialEq)]
545pub struct OwnedWrite {
546    /// Absolute file offset for the first byte.
547    pub offset: u64,
548    /// Bytes written completely or reported as an error.
549    pub bytes: Bytes,
550}
551
552/// One owned positional read request.
553#[derive(Clone, Copy, Debug, Eq, PartialEq)]
554pub struct OwnedRead {
555    /// Absolute file offset for the first byte.
556    pub offset: u64,
557    /// Maximum bytes returned for this request.
558    pub length: usize,
559}
560
561/// Submits an owned read batch to the shared native completion worker.
562///
563/// The first poll transfers ownership to the worker if capacity is available.
564/// Dropping before admission discards the job; dropping after admission only
565/// detaches the observer and the worker completes the operation.
566pub fn read_batch_async(file: File, reads: Vec<OwnedRead>) -> ReadBatch {
567    let file = Arc::new(file);
568    #[cfg(any(windows, target_os = "linux", target_vendor = "apple"))]
569    {
570        match shared_file_health(&file) {
571            Ok((tail, uncertain)) => ReadBatch::submit(
572                file,
573                reads,
574                Some(tail),
575                Some(uncertain),
576                #[cfg(windows)]
577                false,
578            ),
579            Err(error) => ReadBatch::failed(error),
580        }
581    }
582    #[cfg(not(any(windows, target_os = "linux", target_vendor = "apple")))]
583    ReadBatch::submit(file, reads, None, None)
584}
585
586/// Submits an owned write batch to the shared native completion workers.
587///
588/// Admission is irrevocable: after the first successful submission, dropping
589/// the returned future does not roll back or cancel the write.
590pub fn write_all_batch_async(file: File, writes: Vec<OwnedWrite>) -> UnitCompletion {
591    let file = Arc::new(file);
592    #[cfg(any(windows, target_os = "linux", target_vendor = "apple"))]
593    {
594        match shared_file_health(&file) {
595            Ok((tail, uncertain)) => UnitCompletion::submit(
596                file,
597                NativeUnitOperation::Write(writes),
598                Some(tail),
599                Some(uncertain),
600                #[cfg(windows)]
601                false,
602            ),
603            Err(error) => UnitCompletion::failed(error),
604        }
605    }
606    #[cfg(not(any(windows, target_os = "linux", target_vendor = "apple")))]
607    UnitCompletion::submit(file, NativeUnitOperation::Write(writes), None, None)
608}
609
610/// Starts the current executable's `__service` mode without inheriting host
611/// standard-I/O handles.
612///
613/// On Windows this uses a detached process group and disables handle
614/// inheritance at process creation, preventing a durable service from keeping
615/// a short-lived hook runner's pipes open.
616pub fn spawn_service_process(executable: &Path) -> io::Result<()> {
617    #[cfg(windows)]
618    {
619        windows::spawn_service_process(executable)
620    }
621    #[cfg(unix)]
622    {
623        use std::os::unix::process::CommandExt as _;
624
625        let mut command = std::process::Command::new(executable);
626        command
627            .arg("__service")
628            .stdin(std::process::Stdio::null())
629            .stdout(std::process::Stdio::null())
630            .stderr(std::process::Stdio::null());
631        // A fresh process group keeps the independently durable service outside the caller's
632        // process-tree containment so client teardown cannot substitute for service drain.
633        command.process_group(0);
634        command.spawn().map(drop)
635    }
636    #[cfg(not(any(windows, unix)))]
637    {
638        use std::os::unix::process::CommandExt as _;
639
640        let mut command = std::process::Command::new(executable);
641        command
642            .arg("__service")
643            .stdin(std::process::Stdio::null())
644            .stdout(std::process::Stdio::null())
645            .stderr(std::process::Stdio::null())
646            .process_group(0);
647        command.spawn().map(drop)
648    }
649}
650
651/// Starts a child in an owned operating-system process tree.
652///
653/// Descendants inherit the Windows Job or Unix process group. Dropping the
654/// returned guard, or explicitly terminating it, kills that containment. Unix
655/// descendants can deliberately escape by creating another group or session,
656/// so independently durable services require their own lifecycle ownership.
657pub fn spawn_process_tree(command: &mut std::process::Command) -> io::Result<ProcessTree> {
658    ProcessTree::spawn(command)
659}
660
661/// Runtime-independent completion of an owned native-file operation.
662/// Once admitted, dropping the observer never cancels the underlying operation.
663pub struct NativeCompletion<T> {
664    state: Arc<Mutex<CompletionState<T>>>,
665    pending: Option<NativeJob>,
666    waiter: Option<u64>,
667    sequencer: Option<FileSequencer>,
668    uncertain: Option<Arc<AtomicBool>>,
669    predecessor: Option<Arc<OperationFence>>,
670    completion: Option<Arc<OperationFence>>,
671    terminated: bool,
672}
673
674/// Completion of one owned native read batch.
675pub type ReadBatch = NativeCompletion<Vec<Bytes>>;
676
677/// Completion of an owned write, resize, sync, or file-control operation.
678pub type UnitCompletion = NativeCompletion<()>;
679
680/// Runtime-independent bounded offload for host operations without a native
681/// completion path, such as file locks and directory namespace transactions.
682/// Dropping the observer after admission does not cancel the operation.
683pub struct BlockingIoTask<T> {
684    state: Arc<Mutex<CompletionState<T>>>,
685    pending: Option<NativeJob>,
686    waiter: Option<u64>,
687    terminated: bool,
688}
689
690/// Schedules one owned host operation on the bounded native worker pool.
691/// Creating the future is lazy; polling it admits the operation.
692#[must_use]
693pub fn run_blocking_io<T: Send + 'static>(
694    operation: impl FnOnce() -> T + Send + 'static,
695) -> BlockingIoTask<T> {
696    let state = Arc::new(Mutex::new(CompletionState {
697        result: None,
698        waker: None,
699    }));
700    let completion = Arc::clone(&state);
701    BlockingIoTask {
702        pending: Some(NativeJob::Task(Box::new(move || {
703            let result = std::panic::catch_unwind(std::panic::AssertUnwindSafe(operation))
704                .map_err(|_| io::Error::other("native blocking I/O task panicked"));
705            finish_job(&completion, result, None)
706        }))),
707        state,
708        waiter: None,
709        terminated: false,
710    }
711}
712
713#[derive(Default)]
714struct OperationFence {
715    state: Mutex<OperationFenceState>,
716    settled: Condvar,
717}
718
719#[derive(Default)]
720struct OperationFenceState {
721    complete: bool,
722    successor: Option<Waker>,
723    skipped_successor: Option<Arc<OperationFence>>,
724}
725
726impl OperationFence {
727    #[cfg(any(windows, target_os = "linux", target_vendor = "apple"))]
728    fn wait_until_complete(&self) {
729        let mut state = self
730            .state
731            .lock()
732            .unwrap_or_else(std::sync::PoisonError::into_inner);
733        while !state.complete {
734            state = self
735                .settled
736                .wait(state)
737                .unwrap_or_else(std::sync::PoisonError::into_inner);
738        }
739    }
740
741    #[cfg(all(test, any(windows, target_os = "linux")))]
742    fn is_complete(&self) -> bool {
743        self.state
744            .lock()
745            .unwrap_or_else(std::sync::PoisonError::into_inner)
746            .complete
747    }
748
749    fn complete_after(self: &Arc<Self>, predecessor: Option<Arc<Self>>) {
750        let Some(predecessor) = predecessor else {
751            self.complete();
752            return;
753        };
754        let mut state = predecessor
755            .state
756            .lock()
757            .unwrap_or_else(std::sync::PoisonError::into_inner);
758        if state.complete {
759            drop(state);
760            self.complete();
761        } else {
762            // The waiting future has been dropped; its old task must not be
763            // woken when the predecessor finishes.
764            state.successor = None;
765            state.skipped_successor = Some(Arc::clone(self));
766        }
767    }
768
769    fn ready_or_register(&self, waker: &Waker) -> bool {
770        let mut state = self
771            .state
772            .lock()
773            .unwrap_or_else(std::sync::PoisonError::into_inner);
774        if state.complete {
775            true
776        } else {
777            state.successor = Some(waker.clone());
778            false
779        }
780    }
781
782    fn complete(&self) {
783        let mut next: Option<Arc<Self>> = None;
784        loop {
785            let current = next.as_deref().unwrap_or(self);
786            let mut state = current
787                .state
788                .lock()
789                .unwrap_or_else(std::sync::PoisonError::into_inner);
790            state.complete = true;
791            let successor = state.successor.take();
792            let following = state.skipped_successor.take();
793            drop(state);
794            current.settled.notify_all();
795            wake_capacity_waiter(successor);
796            let Some(following) = following else {
797                break;
798            };
799            next = Some(following);
800        }
801    }
802}
803
804struct CompletionState<T> {
805    result: Option<io::Result<T>>,
806    waker: Option<Waker>,
807}
808
809/// The only authority to finish an admitted file operation. Keeping the
810/// fence and observer state together prevents a backend from publishing the
811/// fence before it has reached a terminal native completion.
812struct FileCompletion<T> {
813    state: Arc<Mutex<CompletionState<T>>>,
814    fence: Option<Arc<OperationFence>>,
815    uncertain: Option<Arc<AtomicBool>>,
816    _tail: Option<FileSequencer>,
817    file: Option<Arc<File>>,
818    finished: bool,
819}
820
821impl<T> FileCompletion<T> {
822    fn new(
823        state: Arc<Mutex<CompletionState<T>>>,
824        fence: Option<Arc<OperationFence>>,
825        uncertain: Option<Arc<AtomicBool>>,
826        tail: Option<FileSequencer>,
827    ) -> Self {
828        Self {
829            state,
830            fence,
831            uncertain,
832            _tail: tail,
833            file: None,
834            finished: false,
835        }
836    }
837
838    fn for_file(mut self, file: Arc<File>) -> Self {
839        self.file = Some(file);
840        self
841    }
842
843    fn finish(mut self, result: io::Result<T>) -> Option<Waker> {
844        #[cfg(any(windows, target_os = "linux", target_vendor = "apple"))]
845        if result.as_ref().err().is_some_and(is_uncertain_io_error)
846            && let (Some(file), Some(uncertain)) = (&self.file, &self.uncertain)
847        {
848            poison_file_health(file, uncertain);
849        }
850        let waker = finish_file_job(
851            &self.state,
852            result,
853            self.fence.take(),
854            self.uncertain.as_deref(),
855        );
856        self.finished = true;
857        waker
858    }
859}
860
861impl<T> Drop for FileCompletion<T> {
862    fn drop(&mut self) {
863        if !self.finished {
864            // A backend must never abandon an admitted operation: its native
865            // request could still own buffers and the next file operation
866            // cannot safely cross this fence.
867            std::process::abort();
868        }
869    }
870}
871
872#[cfg(any(windows, target_os = "linux"))]
873fn submit_file_io<T: Send + 'static>(
874    completion: FileCompletion<T>,
875    submit: impl FnOnce(Box<dyn FnOnce(io::Result<T>) + Send>) -> io::Result<()>,
876) -> Option<Waker> {
877    // A platform's Err means it accepted no I/O. This cell leaves the common
878    // layer holding the completion authority until that decision is known;
879    // Ok transfers it to the callback. Dropping an accepted callback without
880    // terminal completion drops FileCompletion and fails stop.
881    let handoff = Arc::new(Mutex::new(Some(completion)));
882    let callback_handoff = Arc::clone(&handoff);
883    let callback = Box::new(move |result| {
884        let completion = callback_handoff
885            .lock()
886            .unwrap_or_else(std::sync::PoisonError::into_inner)
887            .take()
888            .unwrap_or_else(|| std::process::abort());
889        wake_capacity_waiter(completion.finish(result));
890    });
891    match submit(callback) {
892        Ok(()) => None,
893        Err(error) => handoff
894            .lock()
895            .unwrap_or_else(std::sync::PoisonError::into_inner)
896            .take()
897            .unwrap_or_else(|| std::process::abort())
898            .finish(Err(error)),
899    }
900}
901
902enum NativeJob {
903    Read {
904        file: Arc<File>,
905        reads: Vec<OwnedRead>,
906        #[cfg(windows)]
907        overlapped: bool,
908        tail: Option<FileSequencer>,
909        uncertain: Option<Arc<AtomicBool>>,
910        state: Arc<Mutex<CompletionState<Vec<Bytes>>>>,
911        completion: Option<Arc<OperationFence>>,
912    },
913    Unit {
914        file: Arc<File>,
915        operation: NativeUnitOperation,
916        #[cfg(windows)]
917        overlapped: bool,
918        tail: Option<FileSequencer>,
919        uncertain: Option<Arc<AtomicBool>>,
920        state: Arc<Mutex<CompletionState<()>>>,
921        completion: Option<Arc<OperationFence>>,
922    },
923    Task(Box<dyn FnOnce() -> Option<Waker> + Send>),
924}
925
926enum NativeUnitOperation {
927    Write(Vec<OwnedWrite>),
928    SetLen(u64),
929    Sync(Durability),
930    Control(FileControl),
931}
932
933type FileControl = Box<dyn FnOnce(&File) -> io::Result<()> + Send>;
934
935impl NativeJob {
936    fn set_completion(&mut self, fence: Arc<OperationFence>) {
937        match self {
938            Self::Read { completion, .. } | Self::Unit { completion, .. } => {
939                *completion = Some(fence);
940            }
941            Self::Task(_) => unreachable!("blocking tasks are not file-sequenced"),
942        }
943    }
944
945    fn run(self) -> Option<Waker> {
946        match self {
947            Self::Read {
948                file,
949                reads,
950                #[cfg(windows)]
951                overlapped,
952                tail,
953                uncertain,
954                state,
955                completion,
956            } => {
957                let finish = FileCompletion::new(state, completion, uncertain, tail)
958                    .for_file(Arc::clone(&file));
959                #[cfg(target_os = "linux")]
960                return submit_file_io(finish, |callback| {
961                    linux::submit_read(file, reads, callback)
962                });
963                #[cfg(windows)]
964                return submit_file_io(finish, |callback| {
965                    windows::submit_read(file, overlapped, reads, callback)
966                });
967                #[cfg(not(any(windows, target_os = "linux")))]
968                {
969                    let result = std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| {
970                        read_batch_impl(&file, &reads)
971                    }))
972                    // An unwind can happen after native submission, while the OS
973                    // still owns an OVERLAPPED or io_uring buffer. Converting it
974                    // to an ordinary error would release those allocations and
975                    // publish the file fence before terminal completion.
976                    .unwrap_or_else(|_| std::process::abort());
977                    finish.finish(result)
978                }
979            }
980            Self::Unit {
981                file,
982                operation,
983                #[cfg(windows)]
984                overlapped,
985                tail,
986                uncertain,
987                state,
988                completion,
989            } => {
990                let finish = FileCompletion::new(state, completion, uncertain, tail.clone())
991                    .for_file(Arc::clone(&file));
992                #[cfg(any(windows, target_os = "linux"))]
993                let operation = match operation {
994                    NativeUnitOperation::Write(writes) => {
995                        if let Err(error) = validate_write_batch(&writes) {
996                            return finish.finish(Err(error));
997                        }
998                        #[cfg(target_os = "linux")]
999                        return submit_file_io(finish, |callback| {
1000                            linux::submit_write(file, writes, callback)
1001                        });
1002                        #[cfg(windows)]
1003                        return submit_file_io(finish, |callback| {
1004                            windows::submit_write(file, overlapped, writes, callback)
1005                        });
1006                    }
1007                    operation => operation,
1008                };
1009                #[cfg(any(windows, target_os = "linux", target_vendor = "apple"))]
1010                let _scope = tail.as_ref().map(FileScope::enter);
1011                let result =
1012                    std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| match operation {
1013                        #[cfg(not(any(windows, target_os = "linux")))]
1014                        NativeUnitOperation::Write(writes) => write_all_batch_owned(&file, writes),
1015                        #[cfg(any(windows, target_os = "linux"))]
1016                        NativeUnitOperation::Write(_) => {
1017                            unreachable!("native writes are dispatched before bounded controls")
1018                        }
1019                        NativeUnitOperation::SetLen(size) => file.set_len(size),
1020                        NativeUnitOperation::Sync(durability) => sync_file(&file, durability),
1021                        NativeUnitOperation::Control(operation) => operation(&file),
1022                    }))
1023                    // A write may already be executing in the kernel when
1024                    // Rust unwinds. Fail-stop preserves buffer and fence
1025                    // soundness; durable service recovery can restart later.
1026                    .unwrap_or_else(|_| std::process::abort());
1027                finish.finish(result)
1028            }
1029            Self::Task(operation) => operation(),
1030        }
1031    }
1032}
1033
1034fn finish_file_job<T>(
1035    state: &Mutex<CompletionState<T>>,
1036    result: io::Result<T>,
1037    completion: Option<Arc<OperationFence>>,
1038    uncertain: Option<&AtomicBool>,
1039) -> Option<Waker> {
1040    if result.as_ref().err().is_some_and(is_uncertain_io_error)
1041        && let Some(uncertain) = uncertain
1042    {
1043        uncertain.store(true, Ordering::Release);
1044    }
1045    finish_job(state, result, completion)
1046}
1047
1048fn finish_job<T>(
1049    state: &Mutex<CompletionState<T>>,
1050    result: io::Result<T>,
1051    completion: Option<Arc<OperationFence>>,
1052) -> Option<Waker> {
1053    let mut state = state
1054        .lock()
1055        .unwrap_or_else(std::sync::PoisonError::into_inner);
1056    state.result = Some(result);
1057    let waker = state.waker.take();
1058    drop(state);
1059    if let Some(completion) = completion {
1060        completion.complete();
1061    }
1062    waker
1063}
1064
1065struct NativeWorkers {
1066    sender: mpsc::SyncSender<NativeJob>,
1067    admission: Arc<Mutex<AdmissionState>>,
1068}
1069
1070struct AdmissionState {
1071    waiters: VecDeque<(u64, Waker)>,
1072    queued_jobs: usize,
1073    next_waiter: u64,
1074}
1075
1076impl AdmissionState {
1077    fn new() -> Self {
1078        Self {
1079            waiters: VecDeque::new(),
1080            queued_jobs: 0,
1081            next_waiter: 1,
1082        }
1083    }
1084
1085    fn next_waiter(&mut self) -> u64 {
1086        let id = self.next_waiter;
1087        self.next_waiter = id.wrapping_add(1).max(1);
1088        id
1089    }
1090}
1091
1092impl NativeCompletion<Vec<Bytes>> {
1093    fn submit(
1094        file: Arc<File>,
1095        reads: Vec<OwnedRead>,
1096        sequencer: Option<FileSequencer>,
1097        uncertain: Option<Arc<AtomicBool>>,
1098        #[cfg(windows)] overlapped: bool,
1099    ) -> Self {
1100        let state = Arc::new(Mutex::new(CompletionState {
1101            result: None,
1102            waker: None,
1103        }));
1104        Self {
1105            pending: Some(NativeJob::Read {
1106                file,
1107                reads,
1108                #[cfg(windows)]
1109                overlapped,
1110                tail: sequencer.clone(),
1111                uncertain: uncertain.clone(),
1112                state: Arc::clone(&state),
1113                completion: None,
1114            }),
1115            state,
1116            waiter: None,
1117            sequencer,
1118            uncertain,
1119            predecessor: None,
1120            completion: None,
1121            terminated: false,
1122        }
1123    }
1124}
1125
1126impl<T> NativeCompletion<T> {
1127    #[cfg(any(windows, target_os = "linux", target_vendor = "apple"))]
1128    fn failed(error: io::Error) -> Self {
1129        Self {
1130            state: Arc::new(Mutex::new(CompletionState {
1131                result: Some(Err(error)),
1132                waker: None,
1133            })),
1134            pending: None,
1135            waiter: None,
1136            sequencer: None,
1137            uncertain: None,
1138            predecessor: None,
1139            completion: None,
1140            terminated: false,
1141        }
1142    }
1143}
1144
1145impl NativeCompletion<()> {
1146    fn submit(
1147        file: Arc<File>,
1148        operation: NativeUnitOperation,
1149        sequencer: Option<FileSequencer>,
1150        uncertain: Option<Arc<AtomicBool>>,
1151        #[cfg(windows)] overlapped: bool,
1152    ) -> Self {
1153        let state = Arc::new(Mutex::new(CompletionState {
1154            result: None,
1155            waker: None,
1156        }));
1157        Self {
1158            pending: Some(NativeJob::Unit {
1159                file,
1160                operation,
1161                #[cfg(windows)]
1162                overlapped,
1163                tail: sequencer.clone(),
1164                uncertain: uncertain.clone(),
1165                state: Arc::clone(&state),
1166                completion: None,
1167            }),
1168            state,
1169            waiter: None,
1170            sequencer,
1171            uncertain,
1172            predecessor: None,
1173            completion: None,
1174            terminated: false,
1175        }
1176    }
1177}
1178
1179fn reserve_if_needed(
1180    sequencer: &mut Option<FileSequencer>,
1181    predecessor: &mut Option<Arc<OperationFence>>,
1182    completion: &mut Option<Arc<OperationFence>>,
1183    pending: &mut Option<NativeJob>,
1184) {
1185    let Some(sequencer) = sequencer.take() else {
1186        return;
1187    };
1188    let mut tail = sequencer
1189        .lock()
1190        .unwrap_or_else(std::sync::PoisonError::into_inner);
1191    *predecessor = tail.clone();
1192    let fence = Arc::new(OperationFence::default());
1193    if let Some(job) = pending {
1194        job.set_completion(Arc::clone(&fence));
1195    }
1196    *tail = Some(Arc::clone(&fence));
1197    *completion = Some(fence);
1198}
1199
1200impl<T> Future for NativeCompletion<T> {
1201    type Output = io::Result<T>;
1202
1203    fn poll(self: Pin<&mut Self>, context: &mut Context<'_>) -> Poll<Self::Output> {
1204        let this = self.get_mut();
1205        assert!(!this.terminated, "native I/O polled after completion");
1206        reserve_if_needed(
1207            &mut this.sequencer,
1208            &mut this.predecessor,
1209            &mut this.completion,
1210            &mut this.pending,
1211        );
1212        if let Some(predecessor) = &this.predecessor {
1213            if !predecessor.ready_or_register(context.waker()) {
1214                return Poll::Pending;
1215            }
1216            this.predecessor = None;
1217        }
1218        if this
1219            .uncertain
1220            .as_ref()
1221            .is_some_and(|uncertain| uncertain.load(Ordering::Acquire))
1222        {
1223            this.pending = None;
1224            if let Some(completion) = &this.completion {
1225                completion.complete();
1226            }
1227            this.terminated = true;
1228            return Poll::Ready(Err(io::Error::other(
1229                "prior native I/O completion on this file is uncertain",
1230            )));
1231        }
1232        match poll_submission(&mut this.pending, &mut this.waiter, context) {
1233            Poll::Ready(Err(error)) => {
1234                this.pending = None;
1235                if let Some(completion) = &this.completion {
1236                    completion.complete();
1237                }
1238                this.terminated = true;
1239                return Poll::Ready(Err(error));
1240            }
1241            Poll::Ready(Ok(())) => {}
1242            Poll::Pending => return Poll::Pending,
1243        }
1244        let mut state = this
1245            .state
1246            .lock()
1247            .unwrap_or_else(std::sync::PoisonError::into_inner);
1248        if let Some(result) = state.result.take() {
1249            this.terminated = true;
1250            return Poll::Ready(result);
1251        }
1252        if !state
1253            .waker
1254            .as_ref()
1255            .is_some_and(|waker| waker.will_wake(context.waker()))
1256        {
1257            state.waker = Some(context.waker().clone());
1258        }
1259        Poll::Pending
1260    }
1261}
1262
1263impl<T> Future for BlockingIoTask<T> {
1264    type Output = io::Result<T>;
1265
1266    fn poll(self: Pin<&mut Self>, context: &mut Context<'_>) -> Poll<Self::Output> {
1267        let this = self.get_mut();
1268        assert!(!this.terminated, "host I/O task polled after completion");
1269        let workers = match blocking_workers() {
1270            Ok(workers) => workers,
1271            Err(error) => {
1272                this.terminated = true;
1273                return Poll::Ready(Err(error));
1274            }
1275        };
1276        match poll_submission_with(workers, &mut this.pending, &mut this.waiter, context) {
1277            Poll::Ready(Err(error)) => {
1278                this.terminated = true;
1279                return Poll::Ready(Err(error));
1280            }
1281            Poll::Ready(Ok(())) => {}
1282            Poll::Pending => return Poll::Pending,
1283        }
1284        let mut state = this
1285            .state
1286            .lock()
1287            .unwrap_or_else(std::sync::PoisonError::into_inner);
1288        if let Some(result) = state.result.take() {
1289            this.terminated = true;
1290            return Poll::Ready(result);
1291        }
1292        if !state
1293            .waker
1294            .as_ref()
1295            .is_some_and(|waker| waker.will_wake(context.waker()))
1296        {
1297            state.waker = Some(context.waker().clone());
1298        }
1299        Poll::Pending
1300    }
1301}
1302
1303fn poll_submission(
1304    pending: &mut Option<NativeJob>,
1305    waiter: &mut Option<u64>,
1306    context: &Context<'_>,
1307) -> Poll<io::Result<()>> {
1308    if pending.is_none() {
1309        return Poll::Ready(Ok(()));
1310    }
1311    let workers = match native_workers() {
1312        Ok(workers) => workers,
1313        Err(error) => return Poll::Ready(Err(error)),
1314    };
1315    poll_submission_with(workers, pending, waiter, context)
1316}
1317
1318fn poll_submission_with(
1319    workers: &NativeWorkers,
1320    pending: &mut Option<NativeJob>,
1321    waiter: &mut Option<u64>,
1322    context: &Context<'_>,
1323) -> Poll<io::Result<()>> {
1324    let Some(job) = pending.take() else {
1325        return Poll::Ready(Ok(()));
1326    };
1327    let mut admission = workers
1328        .admission
1329        .lock()
1330        .unwrap_or_else(std::sync::PoisonError::into_inner);
1331    let mut identity = *waiter;
1332    if let Some(identity) = identity {
1333        if let Some((_, waker)) = admission
1334            .waiters
1335            .iter_mut()
1336            .find(|(candidate, _)| *candidate == identity)
1337        {
1338            waker.clone_from(context.waker());
1339        } else {
1340            admission
1341                .waiters
1342                .push_back((identity, context.waker().clone()));
1343        }
1344        if admission.queued_jobs != 0
1345            && admission
1346                .waiters
1347                .front()
1348                .is_some_and(|(front, _)| *front != identity)
1349        {
1350            *pending = Some(job);
1351            return Poll::Pending;
1352        }
1353    } else if admission.queued_jobs != 0 && !admission.waiters.is_empty() {
1354        let queued = admission.next_waiter();
1355        *waiter = Some(queued);
1356        admission
1357            .waiters
1358            .push_back((queued, context.waker().clone()));
1359        *pending = Some(job);
1360        return Poll::Pending;
1361    }
1362    match workers.sender.try_send(job) {
1363        Ok(()) => {
1364            // The receiver synchronizes through this same mutex before it
1365            // decrements, so a fast dequeue cannot race this increment.
1366            admission.queued_jobs += 1;
1367            let next = identity.and_then(|submitted| {
1368                waiter.take();
1369                let was_front = admission
1370                    .waiters
1371                    .front()
1372                    .is_some_and(|(front, _)| *front == submitted);
1373                admission
1374                    .waiters
1375                    .retain(|(candidate, _)| *candidate != submitted);
1376                was_front
1377                    .then(|| admission.waiters.front().map(|(_, waker)| waker.clone()))
1378                    .flatten()
1379            });
1380            drop(admission);
1381            wake_capacity_waiter(next);
1382            Poll::Ready(Ok(()))
1383        }
1384        Err(mpsc::TrySendError::Full(job)) => {
1385            if identity.is_none() {
1386                let queued = admission.next_waiter();
1387                *waiter = Some(queued);
1388                identity = Some(queued);
1389                admission
1390                    .waiters
1391                    .push_back((queued, context.waker().clone()));
1392            }
1393            debug_assert!(identity.is_some());
1394            *pending = Some(job);
1395            Poll::Pending
1396        }
1397        Err(mpsc::TrySendError::Disconnected(_)) => {
1398            let next = identity.and_then(|submitted| {
1399                waiter.take();
1400                let was_front = admission
1401                    .waiters
1402                    .front()
1403                    .is_some_and(|(front, _)| *front == submitted);
1404                admission
1405                    .waiters
1406                    .retain(|(candidate, _)| *candidate != submitted);
1407                was_front
1408                    .then(|| admission.waiters.front().map(|(_, waker)| waker.clone()))
1409                    .flatten()
1410            });
1411            drop(admission);
1412            wake_capacity_waiter(next);
1413            Poll::Ready(Err(io::Error::new(
1414                io::ErrorKind::BrokenPipe,
1415                "native I/O workers stopped",
1416            )))
1417        }
1418    }
1419}
1420
1421fn remove_capacity_waiter(workers: &NativeWorkers, waiter: Option<u64>) {
1422    let Some(waiter) = waiter else {
1423        return;
1424    };
1425    let mut admission = workers
1426        .admission
1427        .lock()
1428        .unwrap_or_else(std::sync::PoisonError::into_inner);
1429    let was_front = admission
1430        .waiters
1431        .front()
1432        .is_some_and(|(identity, _)| *identity == waiter);
1433    admission
1434        .waiters
1435        .retain(|(identity, _)| *identity != waiter);
1436    let next = was_front
1437        .then(|| admission.waiters.front().map(|(_, waker)| waker.clone()))
1438        .flatten();
1439    drop(admission);
1440    wake_capacity_waiter(next);
1441}
1442
1443fn wake_capacity_waiter(waiter: Option<Waker>) {
1444    if let Some(waiter) = waiter {
1445        let _ = std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| waiter.wake()));
1446    }
1447}
1448
1449fn notify_capacity_released(admission: &Mutex<AdmissionState>) {
1450    let (first, others) = {
1451        let mut admission = admission
1452            .lock()
1453            .unwrap_or_else(std::sync::PoisonError::into_inner);
1454        debug_assert_ne!(admission.queued_jobs, 0, "native queue accounting lost");
1455        admission.queued_jobs = admission.queued_jobs.saturating_sub(1);
1456        let first = admission.waiters.front().map(|(_, waker)| waker.clone());
1457        // A waiter can be retained without ever being polled again. When the
1458        // channel drains, wake every other waiter so one inactive head cannot
1459        // strand unrelated work while all native workers are idle.
1460        let others = if admission.queued_jobs == 0 {
1461            admission
1462                .waiters
1463                .iter()
1464                .skip(1)
1465                .map(|(_, waker)| waker.clone())
1466                .collect()
1467        } else {
1468            Vec::new()
1469        };
1470        (first, others)
1471    };
1472    wake_capacity_waiter(first);
1473    for waiter in others {
1474        wake_capacity_waiter(Some(waiter));
1475    }
1476}
1477
1478fn native_workers() -> io::Result<&'static NativeWorkers> {
1479    static WORKERS: OnceLock<io::Result<NativeWorkers>> = OnceLock::new();
1480    let parallelism = std::thread::available_parallelism().map_or(1, std::num::NonZero::get);
1481    workers(&WORKERS, "acyclic-native-io", parallelism.min(4))
1482}
1483
1484fn blocking_workers() -> io::Result<&'static NativeWorkers> {
1485    static WORKERS: OnceLock<io::Result<NativeWorkers>> = OnceLock::new();
1486    let parallelism = std::thread::available_parallelism().map_or(1, std::num::NonZero::get);
1487    workers(
1488        &WORKERS,
1489        "acyclic-host-io",
1490        parallelism.saturating_mul(2).clamp(2, 16),
1491    )
1492}
1493
1494fn workers(
1495    slot: &'static OnceLock<io::Result<NativeWorkers>>,
1496    name: &str,
1497    worker_count: usize,
1498) -> io::Result<&'static NativeWorkers> {
1499    slot.get_or_init(|| {
1500        let (sender, receiver) = mpsc::sync_channel::<NativeJob>(worker_count * 4);
1501        let receiver = Arc::new(Mutex::new(receiver));
1502        let admission = Arc::new(Mutex::new(AdmissionState::new()));
1503        for worker_index in 0..worker_count {
1504            let receiver = Arc::clone(&receiver);
1505            let admission = Arc::clone(&admission);
1506            std::thread::Builder::new()
1507                .name(format!("{name}-{worker_index}"))
1508                .spawn(move || {
1509                    loop {
1510                        let job = {
1511                            let receiver = receiver
1512                                .lock()
1513                                .unwrap_or_else(std::sync::PoisonError::into_inner);
1514                            receiver.recv()
1515                        };
1516                        let Ok(job) = job else {
1517                            break;
1518                        };
1519                        notify_capacity_released(&admission);
1520                        let waker = job.run();
1521                        if let Some(waker) = waker {
1522                            let _ = std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| {
1523                                waker.wake();
1524                            }));
1525                        }
1526                    }
1527                })?;
1528        }
1529        Ok(NativeWorkers { sender, admission })
1530    })
1531    .as_ref()
1532    .map_err(|error| io::Error::new(error.kind(), error.to_string()))
1533}
1534
1535impl<T> Drop for NativeCompletion<T> {
1536    fn drop(&mut self) {
1537        if self.pending.is_some()
1538            && let Some(completion) = &self.completion
1539        {
1540            completion.complete_after(self.predecessor.take());
1541        }
1542        self.state
1543            .lock()
1544            .unwrap_or_else(std::sync::PoisonError::into_inner)
1545            .waker = None;
1546        let Some(waiter) = self.waiter else {
1547            return;
1548        };
1549        let Ok(workers) = native_workers() else {
1550            return;
1551        };
1552        remove_capacity_waiter(workers, Some(waiter));
1553    }
1554}
1555
1556impl<T> Drop for BlockingIoTask<T> {
1557    fn drop(&mut self) {
1558        self.state
1559            .lock()
1560            .unwrap_or_else(std::sync::PoisonError::into_inner)
1561            .waker = None;
1562        let Some(waiter) = self.waiter else {
1563            return;
1564        };
1565        let Ok(workers) = blocking_workers() else {
1566            return;
1567        };
1568        remove_capacity_waiter(workers, Some(waiter));
1569    }
1570}
1571
1572/// Bounded positional source for one file range.
1573pub struct RangeReader<'a> {
1574    file: &'a File,
1575    offset: u64,
1576    remaining: u64,
1577}
1578
1579/// Bounded asynchronous positional source backed by the native completion path.
1580pub struct AsyncRangeReader {
1581    file: NativeFile,
1582    offset: u64,
1583    remaining: u64,
1584}
1585
1586impl AsyncRangeReader {
1587    /// Creates a source for at most `length` bytes starting at `offset`.
1588    pub fn new(file: File, offset: u64, length: u64) -> io::Result<Self> {
1589        Ok(Self {
1590            file: NativeFile::from_file(file)?,
1591            offset,
1592            remaining: length,
1593        })
1594    }
1595
1596    /// Reads the next bounded chunk without blocking the caller's executor.
1597    pub async fn read(&mut self, maximum: usize) -> io::Result<Bytes> {
1598        let length = usize::try_from(self.remaining.min(maximum as u64))
1599            .map_err(|_| io::Error::new(io::ErrorKind::InvalidInput, "range too large"))?;
1600        if length == 0 {
1601            return Ok(Bytes::new());
1602        }
1603        let mut result = self
1604            .file
1605            .read_batch_async(vec![OwnedRead {
1606                offset: self.offset,
1607                length,
1608            }])
1609            .await?;
1610        let bytes = result
1611            .pop()
1612            .ok_or_else(|| io::Error::other("native read returned no result"))?;
1613        self.offset = self
1614            .offset
1615            .checked_add(bytes.len() as u64)
1616            .ok_or_else(|| io::Error::new(io::ErrorKind::InvalidInput, "read offset overflow"))?;
1617        self.remaining -= bytes.len() as u64;
1618        Ok(bytes)
1619    }
1620}
1621
1622impl<'a> RangeReader<'a> {
1623    /// Creates a source for at most `length` bytes starting at `offset`.
1624    pub const fn new(file: &'a File, offset: u64, length: u64) -> Self {
1625        Self {
1626            file,
1627            offset,
1628            remaining: length,
1629        }
1630    }
1631}
1632
1633impl Read for RangeReader<'_> {
1634    fn read(&mut self, destination: &mut [u8]) -> io::Result<usize> {
1635        let maximum = usize::try_from(self.remaining.min(destination.len() as u64))
1636            .map_err(|_| io::Error::new(io::ErrorKind::InvalidInput, "range too large"))?;
1637        if maximum == 0 {
1638            return Ok(0);
1639        }
1640        let selected = destination.get_mut(..maximum).ok_or_else(|| {
1641            io::Error::new(io::ErrorKind::InvalidInput, "range exceeds destination")
1642        })?;
1643        let count = read_at(self.file, self.offset, selected)?;
1644        self.offset = self
1645            .offset
1646            .checked_add(count as u64)
1647            .ok_or_else(|| io::Error::new(io::ErrorKind::InvalidInput, "read offset overflow"))?;
1648        self.remaining -= count as u64;
1649        Ok(count)
1650    }
1651}
1652
1653#[cfg(target_os = "linux")]
1654fn read_at_impl(file: &File, offset: u64, destination: &mut [u8]) -> io::Result<usize> {
1655    linux::read_at(file, offset, destination)
1656}
1657
1658#[cfg(target_os = "linux")]
1659fn write_all_at_impl(file: &File, offset: u64, bytes: &[u8]) -> io::Result<()> {
1660    linux::write_all_at(file, offset, bytes)
1661}
1662
1663#[cfg(not(any(windows, target_os = "linux")))]
1664fn write_all_batch_owned(file: &Arc<File>, writes: Vec<OwnedWrite>) -> io::Result<()> {
1665    validate_write_batch(&writes)?;
1666    write_all_batch_impl(file, writes)
1667}
1668
1669fn validate_write_batch(writes: &[OwnedWrite]) -> io::Result<()> {
1670    let mut ranges = Vec::new();
1671    ranges.try_reserve_exact(writes.len())?;
1672    for write in writes {
1673        if write.offset == u64::MAX {
1674            return Err(io::Error::new(
1675                io::ErrorKind::InvalidInput,
1676                "batch write offset aliases the shared file cursor",
1677            ));
1678        }
1679        let length = u64::try_from(write.bytes.len()).map_err(|_| {
1680            io::Error::new(io::ErrorKind::InvalidInput, "batch write length is invalid")
1681        })?;
1682        let end = write.offset.checked_add(length).ok_or_else(|| {
1683            io::Error::new(io::ErrorKind::InvalidInput, "batch write range overflows")
1684        })?;
1685        if !write.bytes.is_empty() {
1686            ranges.push((write.offset, end));
1687        }
1688    }
1689    ranges.sort_unstable();
1690    if ranges
1691        .windows(2)
1692        .any(|pair| matches!(pair, [first, second] if first.1 > second.0))
1693    {
1694        return Err(io::Error::new(
1695            io::ErrorKind::InvalidInput,
1696            "overlapping batch write ranges",
1697        ));
1698    }
1699    Ok(())
1700}
1701
1702#[cfg(all(unix, not(target_os = "linux")))]
1703fn read_at_impl(file: &File, offset: u64, destination: &mut [u8]) -> io::Result<usize> {
1704    use std::os::unix::fs::FileExt as _;
1705    file.read_at(destination, offset)
1706}
1707
1708#[cfg(all(unix, not(target_os = "linux")))]
1709fn write_all_at_impl(file: &File, offset: u64, bytes: &[u8]) -> io::Result<()> {
1710    use std::os::unix::fs::FileExt as _;
1711    file.write_all_at(bytes, offset)
1712}
1713
1714#[cfg(all(unix, not(target_os = "linux")))]
1715fn write_all_batch_impl(file: &File, writes: Vec<OwnedWrite>) -> io::Result<()> {
1716    apple::write_all_batch_owned(file, writes)
1717}
1718
1719#[cfg(all(unix, not(target_os = "linux")))]
1720fn read_batch_impl(file: &File, reads: &[OwnedRead]) -> io::Result<Vec<Bytes>> {
1721    apple::read_batch(file, reads)
1722}
1723
1724#[cfg(windows)]
1725fn wait_for_windows_io<T: Send + 'static>(
1726    submit: impl FnOnce(Box<dyn FnOnce(io::Result<T>) + Send>) -> io::Result<()>,
1727) -> io::Result<T> {
1728    let (sender, receiver) = mpsc::sync_channel(1);
1729    submit(Box::new(move |result| {
1730        let _ = sender.send(result);
1731    }))?;
1732    receiver.recv().map_err(|_| {
1733        io::Error::new(
1734            io::ErrorKind::BrokenPipe,
1735            "completion owner stopped before terminal I/O result",
1736        )
1737    })?
1738}
1739
1740#[cfg(windows)]
1741fn read_at_impl(file: &File, offset: u64, destination: &mut [u8]) -> io::Result<usize> {
1742    let file = Arc::new(file.try_clone()?);
1743    let mut reads = wait_for_windows_io(|finish| {
1744        windows::submit_read(
1745            file,
1746            false,
1747            vec![OwnedRead {
1748                offset,
1749                length: destination.len(),
1750            }],
1751            finish,
1752        )
1753    })?;
1754    let bytes = reads
1755        .pop()
1756        .ok_or_else(|| io::Error::other("native read returned no result"))?;
1757    destination
1758        .get_mut(..bytes.len())
1759        .ok_or_else(|| io::Error::other("native read exceeded submitted length"))?
1760        .copy_from_slice(&bytes);
1761    Ok(bytes.len())
1762}
1763
1764#[cfg(windows)]
1765fn write_all_at_impl(file: &File, offset: u64, bytes: &[u8]) -> io::Result<()> {
1766    let file = Arc::new(file.try_clone()?);
1767    wait_for_windows_io(|finish| {
1768        windows::submit_write(
1769            file,
1770            false,
1771            vec![OwnedWrite {
1772                offset,
1773                bytes: Bytes::copy_from_slice(bytes),
1774            }],
1775            finish,
1776        )
1777    })
1778}
1779
1780#[cfg(target_vendor = "apple")]
1781#[allow(unsafe_code)]
1782fn barrier_sync(file: &File) -> io::Result<()> {
1783    use std::os::fd::AsRawFd as _;
1784    // SAFETY: `F_BARRIERFSYNC` acts only on the live descriptor.
1785    if unsafe { libc::fcntl(file.as_raw_fd(), libc::F_BARRIERFSYNC) } == 0 {
1786        Ok(())
1787    } else {
1788        Err(io::Error::last_os_error())
1789    }
1790}
1791
1792#[cfg(not(target_vendor = "apple"))]
1793fn barrier_sync(_file: &File) -> io::Result<()> {
1794    Err(io::Error::new(
1795        io::ErrorKind::Unsupported,
1796        "ordered write barrier is unavailable on this platform",
1797    ))
1798}
1799
1800#[cfg(unix)]
1801fn sync_parent_impl(path: &Path, durability: Durability) -> io::Result<()> {
1802    sync_file(&File::open(path)?, durability)
1803}
1804
1805#[cfg(windows)]
1806fn sync_parent_impl(path: &Path, durability: Durability) -> io::Result<()> {
1807    use std::fs::OpenOptions;
1808    use std::os::windows::fs::OpenOptionsExt as _;
1809
1810    const FILE_FLAG_BACKUP_SEMANTICS: u32 = 0x0200_0000;
1811    const FILE_SHARE_READ_WRITE_DELETE: u32 = 0x0000_0001 | 0x0000_0002 | 0x0000_0004;
1812    let directory = OpenOptions::new()
1813        .read(true)
1814        .write(true)
1815        .share_mode(FILE_SHARE_READ_WRITE_DELETE)
1816        .custom_flags(FILE_FLAG_BACKUP_SEMANTICS)
1817        .open(path)?;
1818    sync_file(&directory, durability)
1819}
1820
1821#[cfg(target_os = "linux")]
1822#[allow(unsafe_code, reason = "renameat2 receives two live C paths")]
1823fn durable_rename_impl(from: &Path, to: &Path, mode: RenameMode) -> io::Result<()> {
1824    use std::os::unix::ffi::OsStrExt as _;
1825    let from_path = std::ffi::CString::new(from.as_os_str().as_bytes())
1826        .map_err(|_| io::Error::new(io::ErrorKind::InvalidInput, "rename path contains NUL"))?;
1827    let to_path = std::ffi::CString::new(to.as_os_str().as_bytes())
1828        .map_err(|_| io::Error::new(io::ErrorKind::InvalidInput, "rename path contains NUL"))?;
1829    let flags = match mode {
1830        RenameMode::NoReplace => libc::RENAME_NOREPLACE,
1831        RenameMode::Replace => 0,
1832    };
1833    let result = unsafe {
1834        libc::syscall(
1835            libc::SYS_renameat2,
1836            libc::AT_FDCWD,
1837            from_path.as_ptr(),
1838            libc::AT_FDCWD,
1839            to_path.as_ptr(),
1840            flags,
1841        )
1842    };
1843    if result != 0 {
1844        return Err(io::Error::last_os_error());
1845    }
1846    sync_rename_parents(from, to)
1847}
1848
1849#[cfg(target_os = "macos")]
1850#[allow(unsafe_code, reason = "renamex_np receives two live C paths")]
1851fn durable_rename_impl(from: &Path, to: &Path, mode: RenameMode) -> io::Result<()> {
1852    use std::os::unix::ffi::OsStrExt as _;
1853    let from_path = std::ffi::CString::new(from.as_os_str().as_bytes())
1854        .map_err(|_| io::Error::new(io::ErrorKind::InvalidInput, "rename path contains NUL"))?;
1855    let to_path = std::ffi::CString::new(to.as_os_str().as_bytes())
1856        .map_err(|_| io::Error::new(io::ErrorKind::InvalidInput, "rename path contains NUL"))?;
1857    let flags = match mode {
1858        RenameMode::NoReplace => libc::RENAME_EXCL,
1859        RenameMode::Replace => 0,
1860    };
1861    if unsafe { libc::renamex_np(from_path.as_ptr(), to_path.as_ptr(), flags) } != 0 {
1862        return Err(io::Error::last_os_error());
1863    }
1864    sync_rename_parents(from, to)
1865}
1866
1867#[cfg(unix)]
1868fn sync_rename_parents(from: &Path, to: &Path) -> io::Result<()> {
1869    fn namespace_parent(path: &Path) -> &Path {
1870        path.parent()
1871            .filter(|parent| !parent.as_os_str().is_empty())
1872            .unwrap_or_else(|| Path::new("."))
1873    }
1874    let to_parent = namespace_parent(to);
1875    sync_parent(to_parent, Durability::Full)?;
1876    let from_parent = namespace_parent(from);
1877    if from_parent != to_parent {
1878        sync_parent(from_parent, Durability::Full)?;
1879    }
1880    Ok(())
1881}
1882
1883#[cfg(windows)]
1884#[allow(unsafe_code, reason = "MoveFileExW receives terminated UTF-16 paths")]
1885fn durable_rename_impl(from: &Path, to: &Path, mode: RenameMode) -> io::Result<()> {
1886    use std::os::windows::ffi::OsStrExt as _;
1887    use windows_sys::Win32::Storage::FileSystem::{
1888        MOVEFILE_REPLACE_EXISTING, MOVEFILE_WRITE_THROUGH, MoveFileExW,
1889    };
1890    let mut from: Vec<u16> = from.as_os_str().encode_wide().collect();
1891    let mut to: Vec<u16> = to.as_os_str().encode_wide().collect();
1892    if from.contains(&0) || to.contains(&0) {
1893        return Err(io::Error::new(
1894            io::ErrorKind::InvalidInput,
1895            "rename path contains NUL",
1896        ));
1897    }
1898    from.push(0);
1899    to.push(0);
1900    let flags = MOVEFILE_WRITE_THROUGH
1901        | if mode == RenameMode::Replace {
1902            MOVEFILE_REPLACE_EXISTING
1903        } else {
1904            0
1905        };
1906    // SAFETY: both arguments are live, NUL-terminated UTF-16 path buffers.
1907    if unsafe { MoveFileExW(from.as_ptr(), to.as_ptr(), flags) } == 0 {
1908        Err(io::Error::last_os_error())
1909    } else {
1910        Ok(())
1911    }
1912}
1913
1914#[cfg(test)]
1915mod tests {
1916    use super::*;
1917    use std::fs::OpenOptions;
1918    use std::io::{Seek as _, SeekFrom};
1919    use std::sync::Arc;
1920    use std::sync::atomic::{AtomicU64, Ordering};
1921    use std::task::{Poll, Wake};
1922    use std::thread::Thread;
1923    use std::time::{Duration, Instant};
1924
1925    #[cfg(any(windows, target_os = "linux"))]
1926    #[test]
1927    fn platform_submission_transfers_completion_only_after_acceptance() -> io::Result<()> {
1928        let state = Arc::new(Mutex::new(CompletionState::<usize> {
1929            result: None,
1930            waker: None,
1931        }));
1932        let fence = Arc::new(OperationFence::default());
1933        let uncertain = Arc::new(AtomicBool::new(false));
1934        let mut callback = None;
1935        let wake = submit_file_io(
1936            FileCompletion::new(
1937                Arc::clone(&state),
1938                Some(Arc::clone(&fence)),
1939                Some(Arc::clone(&uncertain)),
1940                None,
1941            ),
1942            |finish| {
1943                callback = Some(finish);
1944                Ok(())
1945            },
1946        );
1947        assert!(wake.is_none());
1948        assert!(!fence.is_complete());
1949        callback
1950            .take()
1951            .ok_or_else(|| io::Error::other("accepted callback missing"))?(Ok(7));
1952        assert!(fence.is_complete());
1953        let actual = state
1954            .lock()
1955            .map_err(|_| io::Error::other("completion state poisoned"))?
1956            .result
1957            .take()
1958            .ok_or_else(|| io::Error::other("accepted result missing"))??;
1959        assert_eq!(actual, 7);
1960        assert!(!uncertain.load(Ordering::Acquire));
1961
1962        let rejected = Arc::new(Mutex::new(CompletionState::<usize> {
1963            result: None,
1964            waker: None,
1965        }));
1966        let rejected_fence = Arc::new(OperationFence::default());
1967        assert!(
1968            submit_file_io(
1969                FileCompletion::new(
1970                    Arc::clone(&rejected),
1971                    Some(Arc::clone(&rejected_fence)),
1972                    None,
1973                    None,
1974                ),
1975                |_finish| Err(io::Error::other("not submitted")),
1976            )
1977            .is_none()
1978        );
1979        assert!(rejected_fence.is_complete());
1980        let rejected_result = rejected
1981            .lock()
1982            .map_err(|_| io::Error::other("rejected completion state poisoned"))?
1983            .result
1984            .take()
1985            .ok_or_else(|| io::Error::other("rejected result missing"))?;
1986        assert!(matches!(rejected_result, Err(error) if error.to_string() == "not submitted"));
1987        Ok(())
1988    }
1989
1990    #[cfg(any(windows, target_os = "linux"))]
1991    #[test]
1992    fn accepted_callback_cannot_disappear_without_terminal_completion() -> io::Result<()> {
1993        const MARKER: &str = "ACYCLIC_DROPPED_COMPLETION_CHILD";
1994        if std::env::var_os(MARKER).is_some() {
1995            let state = Arc::new(Mutex::new(CompletionState::<()> {
1996                result: None,
1997                waker: None,
1998            }));
1999            eprintln!("dropping accepted native callback");
2000            let _ = submit_file_io(FileCompletion::new(state, None, None, None), |callback| {
2001                drop(callback);
2002                Ok(())
2003            });
2004            return Err(io::Error::other("missing terminal callback was accepted"));
2005        }
2006        let output = std::process::Command::new(std::env::current_exe()?)
2007            .arg("--exact")
2008            .arg("tests::accepted_callback_cannot_disappear_without_terminal_completion")
2009            .arg("--nocapture")
2010            .env(MARKER, "1")
2011            .output()?;
2012        assert!(!output.status.success());
2013        assert!(
2014            String::from_utf8_lossy(&output.stderr).contains("dropping accepted native callback")
2015        );
2016        Ok(())
2017    }
2018
2019    #[test]
2020    fn native_file_worker_unwind_is_fail_stop() -> io::Result<()> {
2021        const CHILD_MARKER: &str = "ACYCLIC_NATIVE_UNWIND_CHILD_TEST";
2022        if std::env::var_os(CHILD_MARKER).is_some() {
2023            let directory = tempfile::tempdir()?;
2024            let file = NativeFile::from_file(
2025                OpenOptions::new()
2026                    .create_new(true)
2027                    .read(true)
2028                    .write(true)
2029                    .open(directory.path().join("panic-worker"))?,
2030            )?;
2031            let _ = complete_write(file.control_async(|_| {
2032                eprintln!("injected native worker unwind");
2033                std::panic::resume_unwind(Box::new("injected native worker unwind"));
2034            }));
2035            return Err(io::Error::other(
2036                "native worker unwind returned to observer",
2037            ));
2038        }
2039        let output = std::process::Command::new(std::env::current_exe()?)
2040            .arg("--exact")
2041            .arg("tests::native_file_worker_unwind_is_fail_stop")
2042            .arg("--nocapture")
2043            .env(CHILD_MARKER, "1")
2044            .output()?;
2045        assert!(
2046            !output.status.success(),
2047            "native worker unwind escaped fail-stop"
2048        );
2049        assert!(
2050            String::from_utf8_lossy(&output.stderr).contains("injected native worker unwind"),
2051            "child did not reach the injected worker panic"
2052        );
2053        Ok(())
2054    }
2055
2056    struct ThreadWake(Thread);
2057
2058    impl Wake for ThreadWake {
2059        fn wake(self: Arc<Self>) {
2060            self.0.unpark();
2061        }
2062
2063        fn wake_by_ref(self: &Arc<Self>) {
2064            self.0.unpark();
2065        }
2066    }
2067
2068    fn completion_waker() -> Waker {
2069        Waker::from(Arc::new(ThreadWake(std::thread::current())))
2070    }
2071
2072    #[cfg(unix)]
2073    fn direct_write_at(file: &File, bytes: &[u8], offset: u64) -> io::Result<()> {
2074        use std::os::unix::fs::FileExt as _;
2075        file.write_all_at(bytes, offset)
2076    }
2077
2078    #[cfg(windows)]
2079    fn direct_write_at(file: &File, mut bytes: &[u8], mut offset: u64) -> io::Result<()> {
2080        use std::os::windows::fs::FileExt as _;
2081        while !bytes.is_empty() {
2082            let count = file.seek_write(bytes, offset)?;
2083            if count == 0 {
2084                return Err(io::Error::new(
2085                    io::ErrorKind::WriteZero,
2086                    "direct write returned zero",
2087                ));
2088            }
2089            offset = offset.checked_add(count as u64).ok_or_else(|| {
2090                io::Error::new(io::ErrorKind::InvalidInput, "write offset overflow")
2091            })?;
2092            bytes = bytes
2093                .get(count..)
2094                .ok_or_else(|| io::Error::other("overlong direct write"))?;
2095        }
2096        Ok(())
2097    }
2098
2099    #[cfg(unix)]
2100    fn direct_read_at(file: &File, bytes: &mut [u8], offset: u64) -> io::Result<usize> {
2101        use std::os::unix::fs::FileExt as _;
2102        file.read_at(bytes, offset)
2103    }
2104
2105    #[cfg(windows)]
2106    fn direct_read_at(file: &File, bytes: &mut [u8], offset: u64) -> io::Result<usize> {
2107        use std::os::windows::fs::FileExt as _;
2108        file.seek_read(bytes, offset)
2109    }
2110
2111    #[test]
2112    fn exclusive_lock_contention_is_not_a_generic_io_failure() {
2113        assert!(is_exclusive_lock_contention(&io::Error::from(
2114            io::ErrorKind::WouldBlock
2115        )));
2116        assert!(!is_exclusive_lock_contention(&io::Error::from(
2117            io::ErrorKind::PermissionDenied
2118        )));
2119        assert!(!is_exclusive_lock_contention(&io::Error::from(
2120            io::ErrorKind::Unsupported
2121        )));
2122        #[cfg(windows)]
2123        assert!(is_exclusive_lock_contention(&io::Error::from_raw_os_error(
2124            33
2125        )));
2126    }
2127
2128    #[test]
2129    fn ownership_release_barrier_waits_for_the_final_clone() {
2130        let anchor = OwnershipAnchor::new(());
2131        let clone = anchor.clone();
2132        let barrier = anchor.release_barrier();
2133        drop(anchor);
2134        assert!(!barrier.is_released());
2135        drop(clone);
2136        barrier.wait();
2137    }
2138
2139    #[test]
2140    fn durable_rename_moves_and_replaces_entries() -> io::Result<()> {
2141        let directory = tempfile::tempdir()?;
2142        let source = directory.path().join("source");
2143        let destination = directory.path().join("destination");
2144        std::fs::write(&source, b"first")?;
2145        durable_rename(&source, &destination, RenameMode::NoReplace)?;
2146        assert!(!source.exists());
2147        assert_eq!(std::fs::read(&destination)?, b"first");
2148
2149        std::fs::write(&source, b"second")?;
2150        durable_rename(&source, &destination, RenameMode::Replace)?;
2151        assert!(!source.exists());
2152        assert_eq!(std::fs::read(destination)?, b"second");
2153        Ok(())
2154    }
2155
2156    #[cfg(unix)]
2157    #[test]
2158    fn durable_rename_accepts_relative_sibling_paths() -> io::Result<()> {
2159        let temporary = tempfile::tempdir()?;
2160        let original = std::env::current_dir()?;
2161        std::env::set_current_dir(temporary.path())?;
2162        let result = (|| {
2163            std::fs::write("from", b"relative")?;
2164            durable_rename(Path::new("from"), Path::new("to"), RenameMode::NoReplace)?;
2165            assert_eq!(std::fs::read("to")?, b"relative");
2166            Ok(())
2167        })();
2168        std::env::set_current_dir(original)?;
2169        result
2170    }
2171
2172    #[test]
2173    fn no_replace_preserves_both_entries() -> io::Result<()> {
2174        let directory = tempfile::tempdir()?;
2175        let source = directory.path().join("source");
2176        let destination = directory.path().join("destination");
2177        std::fs::write(&source, b"source")?;
2178        std::fs::write(&destination, b"destination")?;
2179        let Err(error) = durable_rename(&source, &destination, RenameMode::NoReplace) else {
2180            return Err(io::Error::other(
2181                "existing destination accepted no-replace rename",
2182            ));
2183        };
2184        assert_eq!(error.kind(), io::ErrorKind::AlreadyExists);
2185        assert_eq!(std::fs::read(source)?, b"source");
2186        assert_eq!(std::fs::read(destination)?, b"destination");
2187        Ok(())
2188    }
2189
2190    fn complete_read(mut read: ReadBatch) -> io::Result<Vec<Bytes>> {
2191        let deadline = Instant::now() + Duration::from_secs(10);
2192        let waker = completion_waker();
2193        loop {
2194            match Pin::new(&mut read).poll(&mut Context::from_waker(&waker)) {
2195                Poll::Ready(result) => return result,
2196                Poll::Pending if Instant::now() < deadline => {
2197                    std::thread::park_timeout(deadline.saturating_duration_since(Instant::now()));
2198                }
2199                Poll::Pending => {
2200                    return Err(io::Error::new(
2201                        io::ErrorKind::TimedOut,
2202                        "owned native read did not complete",
2203                    ));
2204                }
2205            }
2206        }
2207    }
2208
2209    fn complete_write(mut write: UnitCompletion) -> io::Result<()> {
2210        let deadline = Instant::now() + Duration::from_secs(10);
2211        let waker = completion_waker();
2212        loop {
2213            match Pin::new(&mut write).poll(&mut Context::from_waker(&waker)) {
2214                Poll::Ready(result) => return result,
2215                Poll::Pending if Instant::now() < deadline => {
2216                    std::thread::park_timeout(deadline.saturating_duration_since(Instant::now()));
2217                }
2218                Poll::Pending => {
2219                    return Err(io::Error::new(
2220                        io::ErrorKind::TimedOut,
2221                        "owned native write did not complete",
2222                    ));
2223                }
2224            }
2225        }
2226    }
2227
2228    #[test]
2229    fn completed_native_futures_reject_repoll() -> io::Result<()> {
2230        fn finish<T>(future: &mut (impl Future<Output = io::Result<T>> + Unpin)) -> io::Result<T> {
2231            let deadline = Instant::now() + Duration::from_secs(10);
2232            let waker = completion_waker();
2233            loop {
2234                match Pin::new(&mut *future).poll(&mut Context::from_waker(&waker)) {
2235                    Poll::Ready(result) => return result,
2236                    Poll::Pending if Instant::now() < deadline => {
2237                        std::thread::park_timeout(
2238                            deadline.saturating_duration_since(Instant::now()),
2239                        );
2240                    }
2241                    Poll::Pending => {
2242                        return Err(io::Error::new(
2243                            io::ErrorKind::TimedOut,
2244                            "native future stalled",
2245                        ));
2246                    }
2247                }
2248            }
2249        }
2250
2251        fn repoll_panics(future: &mut (impl Future + Unpin)) {
2252            assert!(
2253                std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| {
2254                    let _ = Pin::new(future).poll(&mut Context::from_waker(Waker::noop()));
2255                }))
2256                .is_err()
2257            );
2258        }
2259
2260        let temporary = tempfile::tempdir()?;
2261        let file = OpenOptions::new()
2262            .create_new(true)
2263            .read(true)
2264            .write(true)
2265            .open(temporary.path().join("completed-native-futures"))?;
2266        let mut read = read_batch_async(file.try_clone()?, vec![]);
2267        assert!(finish(&mut read)?.is_empty());
2268        repoll_panics(&mut read);
2269
2270        let mut write = write_all_batch_async(file, vec![]);
2271        finish(&mut write)?;
2272        repoll_panics(&mut write);
2273
2274        let mut host_task = run_blocking_io(|| 7);
2275        assert_eq!(finish(&mut host_task)?, 7);
2276        repoll_panics(&mut host_task);
2277        Ok(())
2278    }
2279
2280    #[test]
2281    fn positional_io_preserves_cursor_and_bounds_ranges() -> io::Result<()> {
2282        let temporary = tempfile::tempdir()?;
2283        let path = temporary.path().join("positional");
2284        let file = OpenOptions::new()
2285            .create_new(true)
2286            .read(true)
2287            .write(true)
2288            .open(path)?;
2289        write_all_at(&file, 2, &[0x22; 3])?;
2290        write_all_at(&file, 16, &[0x33; 5])?;
2291        sync_file(&file, Durability::Full)?;
2292
2293        let mut bytes = [0_u8; 24];
2294        assert_eq!(read_at(&file, 0, &mut bytes)?, 21);
2295        assert_eq!(&bytes[..8], &[0, 0, 0x22, 0x22, 0x22, 0, 0, 0]);
2296        assert_eq!(&bytes[8..16], &[0; 8]);
2297        assert_eq!(&bytes[16..21], &[0x33; 5]);
2298        assert_eq!(read_at(&file, 21, &mut bytes)?, 0);
2299
2300        let mut range = RangeReader::new(&file, 15, 8);
2301        let mut selected = Vec::new();
2302        range.read_to_end(&mut selected)?;
2303        assert_eq!(selected, [0, 0x33, 0x33, 0x33, 0x33, 0x33]);
2304
2305        complete_write(write_all_batch_async(
2306            file.try_clone()?,
2307            vec![
2308                OwnedWrite {
2309                    offset: 32,
2310                    bytes: Bytes::from_static(&[0x44; 5]),
2311                },
2312                OwnedWrite {
2313                    offset: 48,
2314                    bytes: Bytes::from_static(&[0x55; 7]),
2315                },
2316            ],
2317        ))?;
2318        let mut batch = [0_u8; 23];
2319        assert_eq!(read_at(&file, 32, &mut batch)?, 23);
2320        assert_eq!(&batch[..5], &[0x44; 5]);
2321        assert_eq!(&batch[5..16], &[0; 11]);
2322        assert_eq!(&batch[16..], &[0x55; 7]);
2323
2324        // Native batch reads are positional even after another handle sharing
2325        // the open file description has moved its cursor to EOF.
2326        let mut cursor = file.try_clone()?;
2327        cursor.seek(SeekFrom::End(0))?;
2328        let selected = complete_read(read_batch_async(
2329            file.try_clone()?,
2330            vec![OwnedRead {
2331                offset: 16,
2332                length: 5,
2333            }],
2334        ))?;
2335        assert_eq!(selected, [Bytes::from_static(&[0x33; 5])]);
2336        assert_eq!(cursor.stream_position()?, 55);
2337
2338        // Batch writes must be independent of the shared file cursor too.
2339        complete_write(write_all_batch_async(
2340            file.try_clone()?,
2341            vec![OwnedWrite {
2342                offset: 4,
2343                bytes: Bytes::from_static(b"cursor-independent"),
2344            }],
2345        ))?;
2346        let mut written = [0_u8; 18];
2347        assert_eq!(read_at(&file, 4, &mut written)?, written.len());
2348        assert_eq!(&written, b"cursor-independent");
2349        assert_eq!(cursor.stream_position()?, 55);
2350        Ok(())
2351    }
2352
2353    #[test]
2354    fn batch_larger_than_native_queue_preserves_every_write() -> io::Result<()> {
2355        let temporary = tempfile::tempdir()?;
2356        let file = OpenOptions::new()
2357            .create_new(true)
2358            .read(true)
2359            .write(true)
2360            .open(temporary.path().join("large-batch"))?;
2361        let writes: Vec<_> = (0_u8..17)
2362            .map(|value| OwnedWrite {
2363                offset: u64::from(value) * 4,
2364                bytes: Bytes::from(vec![value; 3]),
2365            })
2366            .collect();
2367        complete_write(write_all_batch_async(file.try_clone()?, writes))?;
2368
2369        let mut actual = [0_u8; 67];
2370        assert_eq!(read_at(&file, 0, &mut actual)?, actual.len());
2371        for value in 0_u8..17 {
2372            let start = usize::from(value) * 4;
2373            assert_eq!(actual.get(start..start + 3), Some([value; 3].as_slice()));
2374            if value != 16 {
2375                assert_eq!(actual.get(start + 3), Some(&0));
2376            }
2377        }
2378        Ok(())
2379    }
2380
2381    #[test]
2382    fn overlapping_batch_writes_fail_before_any_write() -> io::Result<()> {
2383        let temporary = tempfile::tempdir()?;
2384        let path = temporary.path().join("overlapping-batch");
2385        let file = OpenOptions::new()
2386            .create_new(true)
2387            .read(true)
2388            .write(true)
2389            .open(&path)?;
2390        std::fs::write(&path, b"unchanged")?;
2391        let result = complete_write(write_all_batch_async(
2392            file,
2393            vec![
2394                OwnedWrite {
2395                    offset: 0,
2396                    bytes: Bytes::from_static(b"first"),
2397                },
2398                OwnedWrite {
2399                    offset: 3,
2400                    bytes: Bytes::from_static(b"second"),
2401                },
2402            ],
2403        ));
2404        assert!(matches!(result, Err(error) if error.kind() == io::ErrorKind::InvalidInput));
2405        assert_eq!(std::fs::read(&path)?, b"unchanged");
2406        Ok(())
2407    }
2408
2409    #[cfg(target_os = "linux")]
2410    #[test]
2411    fn completed_batches_preserve_ring_reuse() -> io::Result<()> {
2412        let temporary = tempfile::tempdir()?;
2413        let file = OpenOptions::new()
2414            .create_new(true)
2415            .read(true)
2416            .write(true)
2417            .open(temporary.path().join("ring-reuse"))?;
2418        for value in 0_u16..256 {
2419            complete_write(write_all_batch_async(
2420                file.try_clone()?,
2421                vec![OwnedWrite {
2422                    offset: u64::from(value),
2423                    bytes: Bytes::copy_from_slice(&[value.to_le_bytes()[0]]),
2424                }],
2425            ))?;
2426        }
2427        let contents = complete_read(read_batch_async(
2428            file,
2429            vec![OwnedRead {
2430                offset: 0,
2431                length: 256,
2432            }],
2433        ))?;
2434        assert_eq!(contents.first().map(Bytes::len), Some(256));
2435        Ok(())
2436    }
2437
2438    #[cfg(windows)]
2439    #[test]
2440    fn native_write_batch_preserves_empty_writes_and_port_reuse() -> io::Result<()> {
2441        let temporary = tempfile::tempdir()?;
2442        let file = OpenOptions::new()
2443            .create_new(true)
2444            .read(true)
2445            .write(true)
2446            .open(temporary.path().join("write-batch-reuse"))?;
2447        for value in 0_u8..8 {
2448            complete_write(write_all_batch_async(
2449                file.try_clone()?,
2450                vec![
2451                    OwnedWrite {
2452                        offset: u64::from(value) * 2,
2453                        bytes: Bytes::copy_from_slice(&[value]),
2454                    },
2455                    OwnedWrite {
2456                        offset: u64::from(value) * 2 + 1,
2457                        bytes: Bytes::new(),
2458                    },
2459                ],
2460            ))?;
2461        }
2462        let mut actual = [0_u8; 15];
2463        assert_eq!(read_at(&file, 0, &mut actual)?, actual.len());
2464        for value in 0_u8..8 {
2465            assert_eq!(actual.get(usize::from(value) * 2), Some(&value));
2466        }
2467        Ok(())
2468    }
2469
2470    #[test]
2471    fn owned_write_outlives_the_callers_file_and_buffers() -> io::Result<()> {
2472        let temporary = tempfile::tempdir()?;
2473        let path = temporary.path().join("owned-write");
2474        let file = OpenOptions::new()
2475            .create_new(true)
2476            .read(true)
2477            .write(true)
2478            .open(&path)?;
2479        let write = write_all_batch_async(
2480            file,
2481            vec![OwnedWrite {
2482                offset: 3,
2483                bytes: Bytes::from_static(b"owned"),
2484            }],
2485        );
2486        complete_write(write)?;
2487        let mut actual = Vec::new();
2488        File::open(path)?.read_to_end(&mut actual)?;
2489        assert_eq!(actual, [0, 0, 0, b'o', b'w', b'n', b'e', b'd']);
2490        Ok(())
2491    }
2492
2493    #[test]
2494    fn owned_native_file_preserves_positional_contract() -> io::Result<()> {
2495        let temporary = tempfile::tempdir()?;
2496        let path = temporary.path().join("owned-native-file");
2497        let file = OpenOptions::new()
2498            .create_new(true)
2499            .read(true)
2500            .write(true)
2501            .open(&path)?;
2502        let native = NativeFile::from_file(file)?;
2503        complete_write(native.set_len_async(32))?;
2504        complete_write(native.write_all_batch_async(vec![OwnedWrite {
2505            offset: 7,
2506            bytes: Bytes::from_static(b"native"),
2507        }]))?;
2508        complete_write(native.write_all_batch_async(vec![
2509            OwnedWrite {
2510                offset: 0,
2511                bytes: Bytes::from_static(b"one"),
2512            },
2513            OwnedWrite {
2514                offset: 20,
2515                bytes: Bytes::from_static(b"two"),
2516            },
2517        ]))?;
2518        complete_write(native.sync_async(Durability::Full))?;
2519
2520        let actual = complete_read(native.read_batch_async(vec![OwnedRead {
2521            offset: 0,
2522            length: 32,
2523        }]))?
2524        .remove(0);
2525        assert_eq!(actual.len(), 32);
2526        assert_eq!(actual.get(..3), Some(b"one".as_slice()));
2527        assert_eq!(actual.get(7..13), Some(b"native".as_slice()));
2528        assert_eq!(actual.get(20..23), Some(b"two".as_slice()));
2529        Ok(())
2530    }
2531
2532    #[test]
2533    fn owned_file_operation_keeps_its_handle_after_owner_drop() -> io::Result<()> {
2534        let temporary = tempfile::tempdir()?;
2535        let path = temporary.path().join("owned-file-lifetime");
2536        let file = OpenOptions::new()
2537            .create_new(true)
2538            .read(true)
2539            .write(true)
2540            .open(&path)?;
2541        let native = NativeFile::from_file(file)?;
2542        let resize = native.set_len_async(64);
2543        drop(native);
2544        complete_write(resize)?;
2545        assert_eq!(std::fs::metadata(&path)?.len(), 64);
2546        Ok(())
2547    }
2548
2549    #[cfg(windows)]
2550    #[test]
2551    fn healthy_registry_does_not_retain_exclusive_handle() -> io::Result<()> {
2552        use std::os::windows::fs::OpenOptionsExt as _;
2553
2554        let temporary = tempfile::tempdir()?;
2555        let path = temporary.path().join("exclusive-native-file");
2556        let file = OpenOptions::new()
2557            .create_new(true)
2558            .read(true)
2559            .write(true)
2560            .share_mode(0)
2561            .open(&path)?;
2562        let native = NativeFile::from_file(file)?;
2563        drop(native);
2564        // No unrelated admission or registry sweep is required to release
2565        // the caller's exclusive Windows share lock.
2566        File::open(&path)?;
2567        Ok(())
2568    }
2569
2570    #[cfg(target_os = "linux")]
2571    #[test]
2572    fn uncertain_completion_retains_identity_after_owner_drop() -> io::Result<()> {
2573        let temporary = tempfile::tempdir()?;
2574        let path = temporary.path().join("uncertain-identity");
2575        let native = NativeFile::from_file(
2576            OpenOptions::new()
2577                .create_new(true)
2578                .read(true)
2579                .write(true)
2580                .open(&path)?,
2581        )?;
2582        let completion = FileCompletion::new(
2583            Arc::new(Mutex::new(CompletionState::<()> {
2584                result: None,
2585                waker: None,
2586            })),
2587            None,
2588            Some(Arc::clone(&native.uncertain)),
2589            Some(Arc::clone(&native.tail)),
2590        )
2591        .for_file(Arc::clone(&native.file));
2592        completion.finish(Err(linux::uncertain_completion(
2593            io::ErrorKind::TimedOut.into(),
2594        )));
2595        drop(native);
2596        let alias = NativeFile::from_file(OpenOptions::new().read(true).open(&path)?)?;
2597        assert!(alias.uncertain.load(Ordering::Acquire));
2598        assert!(
2599            complete_read(alias.read_batch_async(vec![OwnedRead {
2600                offset: 0,
2601                length: 1,
2602            }]))
2603            .is_err()
2604        );
2605        Ok(())
2606    }
2607
2608    #[cfg(any(windows, target_os = "linux", target_vendor = "apple"))]
2609    #[test]
2610    fn concurrent_alias_registration_uses_one_gate() -> io::Result<()> {
2611        let temporary = tempfile::tempdir()?;
2612        let path = temporary.path().join("concurrent-identity");
2613        std::fs::write(&path, b"shared")?;
2614        let start = Arc::new(std::sync::Barrier::new(16));
2615        let mut handles = Vec::new();
2616        for _ in 0..16 {
2617            let path = path.clone();
2618            let start = Arc::clone(&start);
2619            handles.push(std::thread::spawn(move || {
2620                let file = OpenOptions::new().read(true).write(true).open(path)?;
2621                start.wait();
2622                NativeFile::from_file(file)
2623            }));
2624        }
2625        let mut files = Vec::new();
2626        for handle in handles {
2627            files.push(
2628                handle
2629                    .join()
2630                    .map_err(|_| io::Error::other("registration panicked"))??,
2631            );
2632        }
2633        assert_eq!(files.len(), 16);
2634        if let Some(first) = files.first() {
2635            for file in files.iter().skip(1) {
2636                assert!(Arc::ptr_eq(&first.tail, &file.tail));
2637                assert!(Arc::ptr_eq(&first.uncertain, &file.uncertain));
2638            }
2639        }
2640        Ok(())
2641    }
2642
2643    #[cfg(any(windows, target_os = "linux", target_vendor = "apple"))]
2644    #[test]
2645    fn independently_opened_aliases_share_fencing_and_uncertainty() -> io::Result<()> {
2646        let temporary = tempfile::tempdir()?;
2647        let path = temporary.path().join("identity");
2648        let alias = temporary.path().join("hard-link");
2649        let first = NativeFile::from_file(
2650            OpenOptions::new()
2651                .create_new(true)
2652                .read(true)
2653                .write(true)
2654                .open(&path)?,
2655        )?;
2656        std::fs::hard_link(&path, &alias)?;
2657        let second =
2658            NativeFile::from_file(OpenOptions::new().read(true).write(true).open(&alias)?)?;
2659        assert!(Arc::ptr_eq(&first.tail, &second.tail));
2660        assert!(Arc::ptr_eq(&first.uncertain, &second.uncertain));
2661        poison_file_health(&first.file, &first.uncertain);
2662        drop(first);
2663        let result = complete_write(second.set_len_async(1));
2664        assert!(result.is_err(), "an alias bypassed an uncertain completion");
2665        drop(second);
2666        let bare_result = complete_read(read_batch_async(
2667            OpenOptions::new().read(true).open(&path)?,
2668            vec![OwnedRead {
2669                offset: 0,
2670                length: 1,
2671            }],
2672        ));
2673        assert!(
2674            bare_result.is_err(),
2675            "the free read API bypassed the file fence"
2676        );
2677        let direct_alias = OpenOptions::new().read(true).write(true).open(&alias)?;
2678        let mut byte = [0];
2679        assert!(read_at(&direct_alias, 0, &mut byte).is_err());
2680        assert!(write_all_at(&direct_alias, 0, b"x").is_err());
2681        assert!(sync_file(&direct_alias, Durability::Full).is_err());
2682        assert!(sync_data(&direct_alias, Durability::Full).is_err());
2683        assert_eq!(std::fs::metadata(&path)?.len(), 0);
2684
2685        let independent = NativeFile::from_file(
2686            OpenOptions::new()
2687                .create_new(true)
2688                .read(true)
2689                .write(true)
2690                .open(temporary.path().join("clean"))?,
2691        )?;
2692        complete_write(independent.set_len_async(1))?;
2693        Ok(())
2694    }
2695
2696    #[cfg(any(windows, target_os = "linux", target_vendor = "apple"))]
2697    #[test]
2698    fn admitted_job_keeps_alias_registry_alive_after_owner_drop() -> io::Result<()> {
2699        let temporary = tempfile::tempdir()?;
2700        let path = temporary.path().join("outstanding");
2701        let alias_path = temporary.path().join("outstanding-link");
2702        let native = NativeFile::from_file(
2703            OpenOptions::new()
2704                .create_new(true)
2705                .read(true)
2706                .write(true)
2707                .open(&path)?,
2708        )?;
2709
2710        std::fs::hard_link(&path, &alias_path)?;
2711        let tail = Arc::downgrade(&native.tail);
2712        let (started_sender, started_receiver) = mpsc::sync_channel(1);
2713        let (release_sender, release_receiver) = mpsc::sync_channel::<()>(1);
2714        let mut pending = native.control_async(move |_| {
2715            started_sender.send(()).map_err(io::Error::other)?;
2716            release_receiver.recv().map_err(io::Error::other)?;
2717            Ok(())
2718        });
2719        let waker = completion_waker();
2720        assert!(matches!(
2721            Pin::new(&mut pending).poll(&mut Context::from_waker(&waker)),
2722            Poll::Pending
2723        ));
2724        started_receiver
2725            .recv_timeout(Duration::from_secs(5))
2726            .map_err(io::Error::other)?;
2727        drop(native);
2728        let alias = NativeFile::from_file(
2729            OpenOptions::new()
2730                .read(true)
2731                .write(true)
2732                .open(&alias_path)?,
2733        )?;
2734        assert!(Arc::ptr_eq(
2735            &tail
2736                .upgrade()
2737                .ok_or_else(|| io::Error::other("admitted job lost its alias tail"))?,
2738            &alias.tail
2739        ));
2740        release_sender.send(()).map_err(io::Error::other)?;
2741        complete_write(pending)?;
2742        Ok(())
2743    }
2744
2745    #[test]
2746    fn owned_file_operations_follow_first_poll_order() -> io::Result<()> {
2747        let temporary = tempfile::tempdir()?;
2748        let path = temporary.path().join("owned-file-order");
2749        let native = NativeFile::from_file(
2750            OpenOptions::new()
2751                .create_new(true)
2752                .read(true)
2753                .write(true)
2754                .open(&path)?,
2755        )?;
2756        let write = native.write_all_batch_async(vec![OwnedWrite {
2757            offset: 0,
2758            bytes: Bytes::from_static(b"ordered"),
2759        }]);
2760        let mut sync = native.sync_async(Durability::Full);
2761        let waker = completion_waker();
2762        assert!(matches!(
2763            Pin::new(&mut sync).poll(&mut Context::from_waker(&waker)),
2764            Poll::Pending
2765        ));
2766        complete_write(sync)?;
2767        complete_write(write)?;
2768        assert_eq!(std::fs::read(&path)?, b"ordered");
2769
2770        let discarded = native.write_all_batch_async(vec![OwnedWrite {
2771            offset: 0,
2772            bytes: Bytes::from_static(b"discarded"),
2773        }]);
2774        let resize = native.set_len_async(16);
2775        drop(discarded);
2776        complete_write(resize)?;
2777        assert_eq!(std::fs::metadata(&path)?.len(), 16);
2778        Ok(())
2779    }
2780
2781    #[cfg(target_os = "linux")]
2782    #[test]
2783    fn uncertain_completion_poison_is_file_local_and_fences_successors() -> io::Result<()> {
2784        let temporary = tempfile::tempdir()?;
2785        let poisoned_path = temporary.path().join("poisoned");
2786        let poisoned = NativeFile::from_file(
2787            OpenOptions::new()
2788                .create_new(true)
2789                .read(true)
2790                .write(true)
2791                .open(&poisoned_path)?,
2792        )?;
2793        let prior = Arc::new(OperationFence::default());
2794        *poisoned
2795            .tail
2796            .lock()
2797            .unwrap_or_else(std::sync::PoisonError::into_inner) = Some(Arc::clone(&prior));
2798        let state = Mutex::new(CompletionState::<()> {
2799            result: None,
2800            waker: None,
2801        });
2802        poison_file_health(&poisoned.file, &poisoned.uncertain);
2803        finish_file_job(
2804            &state,
2805            Err(linux::uncertain_completion(io::Error::other(
2806                "injected ambiguous completion",
2807            ))),
2808            Some(prior),
2809            Some(&poisoned.uncertain),
2810        );
2811        let Err(error) = complete_write(poisoned.set_len_async(4)) else {
2812            return Err(io::Error::other(
2813                "an uncertain prior operation did not fence its successor",
2814            ));
2815        };
2816        assert!(error.to_string().contains("prior native I/O completion"));
2817        assert_eq!(std::fs::metadata(poisoned_path)?.len(), 0);
2818
2819        let unrelated_path = temporary.path().join("unrelated");
2820        let unrelated = NativeFile::from_file(
2821            OpenOptions::new()
2822                .create_new(true)
2823                .read(true)
2824                .write(true)
2825                .open(&unrelated_path)?,
2826        )?;
2827        complete_write(unrelated.set_len_async(4))?;
2828        assert_eq!(std::fs::metadata(unrelated_path)?.len(), 4);
2829        Ok(())
2830    }
2831
2832    #[test]
2833    fn platform_control_is_offloaded_and_file_sequenced() -> io::Result<()> {
2834        let temporary = tempfile::tempdir()?;
2835        let native = NativeFile::from_file(
2836            OpenOptions::new()
2837                .create_new(true)
2838                .read(true)
2839                .write(true)
2840                .open(temporary.path().join("ordered-control"))?,
2841        )?;
2842        let mut write = native.write_all_batch_async(vec![OwnedWrite {
2843            offset: 0,
2844            bytes: Bytes::from_static(b"ready"),
2845        }]);
2846        let waker = completion_waker();
2847        let deadline = Instant::now() + Duration::from_secs(10);
2848        let write_completed = loop {
2849            match Pin::new(&mut write).poll(&mut Context::from_waker(&waker)) {
2850                Poll::Ready(result) => {
2851                    result?;
2852                    break true;
2853                }
2854                Poll::Pending if write.pending.is_none() => break false,
2855                Poll::Pending if Instant::now() < deadline => {
2856                    std::thread::park_timeout(deadline.saturating_duration_since(Instant::now()));
2857                }
2858                Poll::Pending => {
2859                    return Err(io::Error::new(
2860                        io::ErrorKind::TimedOut,
2861                        "write admission stalled",
2862                    ));
2863                }
2864            }
2865        };
2866        let (sender, receiver) = mpsc::channel();
2867        let control = native.control_async(move |file| {
2868            let mut bytes = [0; 5];
2869            read_at(file, 0, &mut bytes)?;
2870            sender
2871                .send(bytes)
2872                .map_err(|error| io::Error::other(error.to_string()))
2873        });
2874        complete_write(control)?;
2875        assert_eq!(receiver.recv().map_err(io::Error::other)?, *b"ready");
2876        if !write_completed {
2877            complete_write(write)?;
2878        }
2879        Ok(())
2880    }
2881
2882    #[test]
2883    fn unpolled_operation_does_not_delay_a_later_awaited_operation() -> io::Result<()> {
2884        let temporary = tempfile::tempdir()?;
2885        let path = temporary.path().join("skipped-operation-order");
2886        let native = NativeFile::from_file(
2887            OpenOptions::new()
2888                .create_new(true)
2889                .read(true)
2890                .write(true)
2891                .open(&path)?,
2892        )?;
2893        let never_polled = native.set_len_async(8);
2894        let last = native.set_len_async(32);
2895        complete_write(last)?;
2896        assert_eq!(std::fs::metadata(&path)?.len(), 32);
2897        drop(never_polled);
2898        assert_eq!(std::fs::metadata(&path)?.len(), 32);
2899        Ok(())
2900    }
2901
2902    #[test]
2903    fn skipped_fence_chain_completes_iteratively() -> io::Result<()> {
2904        let first = Arc::new(OperationFence::default());
2905        let mut prior = Arc::clone(&first);
2906        for _ in 0..100_000 {
2907            let skipped = Arc::new(OperationFence::default());
2908            skipped.complete_after(Some(prior));
2909            prior = skipped;
2910        }
2911        first.complete();
2912        assert!(prior.ready_or_register(Waker::noop()));
2913        Ok(())
2914    }
2915
2916    #[test]
2917    fn skipped_fence_does_not_wake_a_dropped_waiter() {
2918        struct CountWake(AtomicU64);
2919        impl Wake for CountWake {
2920            fn wake(self: Arc<Self>) {
2921                self.0.fetch_add(1, Ordering::Relaxed);
2922            }
2923            fn wake_by_ref(self: &Arc<Self>) {
2924                self.0.fetch_add(1, Ordering::Relaxed);
2925            }
2926        }
2927
2928        let predecessor = Arc::new(OperationFence::default());
2929        let skipped = Arc::new(OperationFence::default());
2930        let count = Arc::new(CountWake(AtomicU64::new(0)));
2931        let waker = Waker::from(Arc::clone(&count));
2932        assert!(!predecessor.ready_or_register(&waker));
2933        skipped.complete_after(Some(Arc::clone(&predecessor)));
2934        predecessor.complete();
2935        assert_eq!(count.0.load(Ordering::Relaxed), 0);
2936        assert!(skipped.ready_or_register(Waker::noop()));
2937    }
2938
2939    #[test]
2940    fn saturated_host_transactions_do_not_starve_native_file_io() -> io::Result<()> {
2941        struct GateRelease(Arc<(Mutex<bool>, Condvar)>);
2942        impl Drop for GateRelease {
2943            fn drop(&mut self) {
2944                let (lock, ready) = &*self.0;
2945                *lock
2946                    .lock()
2947                    .unwrap_or_else(std::sync::PoisonError::into_inner) = true;
2948                ready.notify_all();
2949            }
2950        }
2951
2952        let parallelism = std::thread::available_parallelism().map_or(1, std::num::NonZero::get);
2953        let count = parallelism.saturating_mul(2).clamp(2, 16);
2954        let gate = Arc::new((Mutex::new(false), Condvar::new()));
2955        let release = GateRelease(Arc::clone(&gate));
2956        let (entered_tx, entered_rx) = mpsc::channel();
2957        let (finished_tx, finished_rx) = mpsc::channel();
2958        let mut observers = Vec::new();
2959        let waker = completion_waker();
2960        for _ in 0..count {
2961            let gate = Arc::clone(&gate);
2962            let entered_tx = entered_tx.clone();
2963            let finished_tx = finished_tx.clone();
2964            let mut task = run_blocking_io(move || {
2965                let _ = entered_tx.send(());
2966                let (lock, ready) = &*gate;
2967                let mut released = lock
2968                    .lock()
2969                    .unwrap_or_else(std::sync::PoisonError::into_inner);
2970                while !*released {
2971                    released = ready
2972                        .wait(released)
2973                        .unwrap_or_else(std::sync::PoisonError::into_inner);
2974                }
2975                let _ = finished_tx.send(());
2976            });
2977            assert!(matches!(
2978                Pin::new(&mut task).poll(&mut Context::from_waker(&waker)),
2979                Poll::Pending
2980            ));
2981            observers.push(task);
2982        }
2983        for _ in 0..count {
2984            entered_rx
2985                .recv_timeout(Duration::from_secs(10))
2986                .map_err(|error| io::Error::other(error.to_string()))?;
2987        }
2988        let temporary = tempfile::tempdir()?;
2989        let file = OpenOptions::new()
2990            .create_new(true)
2991            .read(true)
2992            .write(true)
2993            .open(temporary.path().join("independent-native-io"))?;
2994        complete_read(read_batch_async(
2995            file,
2996            vec![OwnedRead {
2997                offset: 0,
2998                length: 1,
2999            }],
3000        ))?;
3001        drop(release);
3002        for _ in 0..count {
3003            finished_rx
3004                .recv_timeout(Duration::from_secs(10))
3005                .map_err(|error| io::Error::other(error.to_string()))?;
3006        }
3007        drop(observers);
3008        Ok(())
3009    }
3010
3011    #[cfg(not(target_vendor = "apple"))]
3012    #[test]
3013    fn barrier_durability_rejects_targets_without_an_exact_primitive() -> io::Result<()> {
3014        let temporary = tempfile::tempdir()?;
3015        let file = File::create(temporary.path().join("barrier"))?;
3016        assert!(matches!(
3017            sync_file(&file, Durability::Barrier),
3018            Err(error) if error.kind() == io::ErrorKind::Unsupported
3019        ));
3020        assert!(matches!(
3021            sync_data(&file, Durability::Barrier),
3022            Err(error) if error.kind() == io::ErrorKind::Unsupported
3023        ));
3024        Ok(())
3025    }
3026
3027    #[test]
3028    fn native_read_batch_preserves_order_and_short_reads() -> io::Result<()> {
3029        let temporary = tempfile::tempdir()?;
3030        let file = OpenOptions::new()
3031            .create_new(true)
3032            .read(true)
3033            .write(true)
3034            .open(temporary.path().join("read-batch"))?;
3035        write_all_at(&file, 0, b"abcdefgh")?;
3036        let reads = complete_read(read_batch_async(
3037            file,
3038            vec![
3039                OwnedRead {
3040                    offset: 5,
3041                    length: 8,
3042                },
3043                OwnedRead {
3044                    offset: 1,
3045                    length: 3,
3046                },
3047            ],
3048        ))?;
3049        assert_eq!(
3050            reads,
3051            [Bytes::from_static(b"fgh"), Bytes::from_static(b"bcd")]
3052        );
3053        Ok(())
3054    }
3055
3056    #[cfg(target_vendor = "apple")]
3057    #[test]
3058    fn native_batches_reject_all_invalid_offsets_before_submission() -> io::Result<()> {
3059        let temporary = tempfile::tempdir()?;
3060        let path = temporary.path().join("invalid-offset-batch");
3061        let file = OpenOptions::new()
3062            .create_new(true)
3063            .read(true)
3064            .write(true)
3065            .open(&path)?;
3066        write_all_at(&file, 0, b"stable")?;
3067
3068        let read = complete_read(read_batch_async(
3069            file.try_clone()?,
3070            vec![
3071                OwnedRead {
3072                    offset: 0,
3073                    length: 1,
3074                },
3075                OwnedRead {
3076                    offset: u64::MAX,
3077                    length: 1,
3078                },
3079            ],
3080        ));
3081        assert!(matches!(read, Err(error) if error.kind() == io::ErrorKind::InvalidInput));
3082
3083        let write = complete_write(write_all_batch_async(
3084            file,
3085            vec![
3086                OwnedWrite {
3087                    offset: 0,
3088                    bytes: Bytes::from_static(b"changed"),
3089                },
3090                OwnedWrite {
3091                    offset: u64::MAX,
3092                    bytes: Bytes::from_static(b"x"),
3093                },
3094            ],
3095        ));
3096        assert!(matches!(write, Err(error) if error.kind() == io::ErrorKind::InvalidInput));
3097        assert_eq!(std::fs::read(path)?, b"stable");
3098        Ok(())
3099    }
3100
3101    #[cfg(target_vendor = "apple")]
3102    #[test]
3103    fn apple_batches_preserve_unlinked_open_files() -> io::Result<()> {
3104        let temporary = tempfile::tempdir()?;
3105        let path = temporary.path().join("unlinked-open-file");
3106        let file = OpenOptions::new()
3107            .create_new(true)
3108            .read(true)
3109            .write(true)
3110            .open(&path)?;
3111        std::fs::remove_file(path)?;
3112        complete_write(write_all_batch_async(
3113            file.try_clone()?,
3114            vec![OwnedWrite {
3115                offset: 3,
3116                bytes: Bytes::from_static(b"kept"),
3117            }],
3118        ))?;
3119        assert_eq!(
3120            complete_read(read_batch_async(
3121                file,
3122                vec![OwnedRead {
3123                    offset: 3,
3124                    length: 4,
3125                }],
3126            ))?,
3127            [Bytes::from_static(b"kept")]
3128        );
3129        Ok(())
3130    }
3131
3132    #[cfg(target_os = "linux")]
3133    #[test]
3134    fn io_uring_rejects_cursor_aliases_before_any_submission() -> io::Result<()> {
3135        let temporary = tempfile::tempdir()?;
3136        let path = temporary.path().join("invalid-io-uring-offset");
3137        let mut file = OpenOptions::new()
3138            .create_new(true)
3139            .read(true)
3140            .write(true)
3141            .open(&path)?;
3142        write_all_at(&file, 0, b"stable")?;
3143        file.seek(SeekFrom::Start(3))?;
3144
3145        let mut byte = [0_u8; 1];
3146        assert!(matches!(
3147            read_at(&file, u64::MAX, &mut byte),
3148            Err(error) if error.kind() == io::ErrorKind::InvalidInput
3149        ));
3150        assert!(matches!(
3151            write_all_at(&file, u64::MAX, b"x"),
3152            Err(error) if error.kind() == io::ErrorKind::InvalidInput
3153        ));
3154        assert!(matches!(
3155            complete_read(read_batch_async(
3156                file.try_clone()?,
3157                vec![
3158                    OwnedRead {
3159                        offset: 0,
3160                        length: 1,
3161                    },
3162                    OwnedRead {
3163                        offset: u64::MAX,
3164                        length: 1,
3165                    },
3166                ],
3167            )),
3168            Err(error) if error.kind() == io::ErrorKind::InvalidInput
3169        ));
3170        assert!(matches!(
3171            complete_write(write_all_batch_async(
3172                file.try_clone()?,
3173                vec![
3174                    OwnedWrite {
3175                        offset: 0,
3176                        bytes: Bytes::from_static(b"changed"),
3177                    },
3178                    OwnedWrite {
3179                        offset: u64::MAX,
3180                        bytes: Bytes::from_static(b"x"),
3181                    },
3182                ],
3183            )),
3184            Err(error) if error.kind() == io::ErrorKind::InvalidInput
3185        ));
3186        assert_eq!(file.stream_position()?, 3);
3187        assert_eq!(std::fs::read(path)?, b"stable");
3188        Ok(())
3189    }
3190
3191    #[test]
3192    fn owned_read_outlives_the_callers_file_and_request() -> io::Result<()> {
3193        let temporary = tempfile::tempdir()?;
3194        let path = temporary.path().join("owned-read");
3195        let file = OpenOptions::new()
3196            .create_new(true)
3197            .read(true)
3198            .write(true)
3199            .open(path)?;
3200        write_all_at(&file, 0, b"abcdefgh")?;
3201        let read = read_batch_async(
3202            file,
3203            vec![OwnedRead {
3204                offset: 2,
3205                length: 4,
3206            }],
3207        );
3208        let actual = complete_read(read)?;
3209        assert_eq!(actual, [Bytes::from_static(b"cdef")]);
3210        Ok(())
3211    }
3212
3213    #[test]
3214    fn admitted_write_outlives_dropped_observer() -> io::Result<()> {
3215        let temporary = tempfile::tempdir()?;
3216        let path = temporary.path().join("detached-write");
3217        let file = OpenOptions::new()
3218            .create_new(true)
3219            .read(true)
3220            .write(true)
3221            .open(&path)?;
3222        let mut write = write_all_batch_async(
3223            file,
3224            vec![OwnedWrite {
3225                offset: 0,
3226                bytes: Bytes::from_static(b"persisted"),
3227            }],
3228        );
3229        let (sender, receiver) = mpsc::sync_channel(1);
3230        let admitted = write
3231            .pending
3232            .take()
3233            .ok_or_else(|| io::Error::other("write was not pending admission"))?;
3234        let completed = Arc::clone(&write.state);
3235        sender
3236            .try_send(admitted)
3237            .map_err(|_| io::Error::other("write admission failed"))?;
3238
3239        drop(write);
3240        let job = receiver
3241            .recv()
3242            .map_err(|_| io::Error::other("admitted write disappeared"))?;
3243        let _ = job.run();
3244
3245        let deadline = Instant::now() + Duration::from_secs(5);
3246        loop {
3247            if let Some(result) = completed
3248                .lock()
3249                .unwrap_or_else(std::sync::PoisonError::into_inner)
3250                .result
3251                .take()
3252            {
3253                result?;
3254                break;
3255            }
3256            if Instant::now() >= deadline {
3257                return Err(io::Error::new(
3258                    io::ErrorKind::TimedOut,
3259                    "detached admitted write did not complete",
3260                ));
3261            }
3262            std::thread::park_timeout(Duration::from_millis(1));
3263        }
3264
3265        assert_eq!(std::fs::read(path)?, b"persisted");
3266        Ok(())
3267    }
3268
3269    #[test]
3270    fn dropped_admission_waiters_do_not_block_later_reads() -> io::Result<()> {
3271        enum FirstPoll {
3272            Ready(io::Result<Vec<Bytes>>),
3273            Pending(ReadBatch),
3274        }
3275
3276        let temporary = tempfile::tempdir()?;
3277        let path = temporary.path().join("admission-drop");
3278        let file = OpenOptions::new()
3279            .create_new(true)
3280            .read(true)
3281            .write(true)
3282            .open(path)?;
3283        write_all_at(&file, 0, b"x")?;
3284        let mut reads = Vec::new();
3285        for _ in 0..64 {
3286            let mut read = read_batch_async(
3287                file.try_clone()?,
3288                vec![OwnedRead {
3289                    offset: 0,
3290                    length: 1,
3291                }],
3292            );
3293            reads.push(
3294                match Pin::new(&mut read).poll(&mut Context::from_waker(Waker::noop())) {
3295                    Poll::Ready(result) => FirstPoll::Ready(result),
3296                    Poll::Pending => FirstPoll::Pending(read),
3297                },
3298            );
3299        }
3300        for read in &reads {
3301            if let FirstPoll::Ready(result) = read {
3302                match result {
3303                    Ok(completed) => assert_eq!(completed, &[Bytes::from_static(b"x")]),
3304                    Err(error) => {
3305                        return Err(io::Error::new(
3306                            error.kind(),
3307                            format!("initial native read failed: {error}"),
3308                        ));
3309                    }
3310                }
3311            }
3312        }
3313        reads.truncate(8);
3314        for read in reads {
3315            let completed = match read {
3316                FirstPoll::Ready(result) => result?,
3317                FirstPoll::Pending(read) => complete_read(read)?,
3318            };
3319            assert_eq!(completed, [Bytes::from_static(b"x")]);
3320        }
3321        let final_read = read_batch_async(
3322            file,
3323            vec![OwnedRead {
3324                offset: 0,
3325                length: 1,
3326            }],
3327        );
3328        assert_eq!(complete_read(final_read)?, [Bytes::from_static(b"x")]);
3329        Ok(())
3330    }
3331
3332    #[test]
3333    #[allow(clippy::too_many_lines)]
3334    fn bounded_native_admission_is_work_conserving_and_cancellation_wakes_the_successor()
3335    -> io::Result<()> {
3336        use std::sync::atomic::AtomicUsize;
3337
3338        struct CountWake(AtomicUsize);
3339
3340        impl Wake for CountWake {
3341            fn wake(self: Arc<Self>) {
3342                self.0.fetch_add(1, Ordering::Relaxed);
3343            }
3344
3345            fn wake_by_ref(self: &Arc<Self>) {
3346                self.0.fetch_add(1, Ordering::Relaxed);
3347            }
3348        }
3349
3350        fn job(file: &File) -> io::Result<NativeJob> {
3351            Ok(NativeJob::Read {
3352                file: Arc::new(file.try_clone()?),
3353                reads: vec![OwnedRead {
3354                    offset: 0,
3355                    length: 1,
3356                }],
3357                #[cfg(windows)]
3358                overlapped: false,
3359                tail: None,
3360                uncertain: None,
3361                state: Arc::new(Mutex::new(CompletionState {
3362                    result: None,
3363                    waker: None,
3364                })),
3365                completion: None,
3366            })
3367        }
3368
3369        let temporary = tempfile::tempdir()?;
3370        let file = OpenOptions::new()
3371            .create_new(true)
3372            .read(true)
3373            .write(true)
3374            .open(temporary.path().join("fifo-admission"))?;
3375        write_all_at(&file, 0, b"x")?;
3376        let (sender, receiver) = mpsc::sync_channel(1);
3377        let workers = NativeWorkers {
3378            sender,
3379            admission: Arc::new(Mutex::new(AdmissionState::new())),
3380        };
3381        workers
3382            .sender
3383            .try_send(job(&file)?)
3384            .map_err(|_| io::Error::other("failed to saturate test queue"))?;
3385        workers
3386            .admission
3387            .lock()
3388            .map_err(|_| io::Error::other("admission mutex poisoned"))?
3389            .queued_jobs += 1;
3390
3391        let mut first = Some(job(&file)?);
3392        let mut first_waiter = None;
3393        let mut second = Some(job(&file)?);
3394        let mut second_waiter = None;
3395        let noop = Waker::noop();
3396        let context = Context::from_waker(noop);
3397        assert!(matches!(
3398            poll_submission_with(&workers, &mut first, &mut first_waiter, &context),
3399            Poll::Pending
3400        ));
3401        assert!(matches!(
3402            poll_submission_with(&workers, &mut second, &mut second_waiter, &context),
3403            Poll::Pending
3404        ));
3405        let _ = receiver
3406            .recv()
3407            .map_err(|_| io::Error::other("queue closed"))?;
3408        notify_capacity_released(&workers.admission);
3409        assert!(matches!(
3410            poll_submission_with(&workers, &mut second, &mut second_waiter, &context),
3411            Poll::Ready(Ok(()))
3412        ));
3413        assert!(matches!(
3414            poll_submission_with(&workers, &mut first, &mut first_waiter, &context),
3415            Poll::Pending
3416        ));
3417        let _ = receiver
3418            .recv()
3419            .map_err(|_| io::Error::other("queue closed"))?;
3420        notify_capacity_released(&workers.admission);
3421        assert!(matches!(
3422            poll_submission_with(&workers, &mut first, &mut first_waiter, &context),
3423            Poll::Ready(Ok(()))
3424        ));
3425        let _ = receiver
3426            .recv()
3427            .map_err(|_| io::Error::other("queue closed"))?;
3428        notify_capacity_released(&workers.admission);
3429
3430        workers
3431            .sender
3432            .try_send(job(&file)?)
3433            .map_err(|_| io::Error::other("failed to saturate cancellation queue"))?;
3434        workers
3435            .admission
3436            .lock()
3437            .map_err(|_| io::Error::other("admission mutex poisoned"))?
3438            .queued_jobs += 1;
3439        let mut cancelled = Some(job(&file)?);
3440        let mut cancelled_waiter = None;
3441        assert!(matches!(
3442            poll_submission_with(&workers, &mut cancelled, &mut cancelled_waiter, &context),
3443            Poll::Pending
3444        ));
3445        let wake_count = Arc::new(CountWake(AtomicUsize::new(0)));
3446        let successor_waker = Waker::from(Arc::clone(&wake_count));
3447        let successor_context = Context::from_waker(&successor_waker);
3448        let mut successor = Some(job(&file)?);
3449        let mut successor_waiter = None;
3450        assert!(matches!(
3451            poll_submission_with(
3452                &workers,
3453                &mut successor,
3454                &mut successor_waiter,
3455                &successor_context
3456            ),
3457            Poll::Pending
3458        ));
3459        remove_capacity_waiter(&workers, cancelled_waiter.take());
3460        assert_eq!(wake_count.0.load(Ordering::Relaxed), 1);
3461        assert_eq!(
3462            workers
3463                .admission
3464                .lock()
3465                .unwrap_or_else(std::sync::PoisonError::into_inner)
3466                .waiters
3467                .front()
3468                .map(|(identity, _)| *identity),
3469            successor_waiter
3470        );
3471        Ok(())
3472    }
3473
3474    #[test]
3475    fn inactive_admission_waiter_cannot_strand_other_files() -> io::Result<()> {
3476        use std::sync::atomic::AtomicUsize;
3477
3478        struct CountWake(AtomicUsize);
3479        impl Wake for CountWake {
3480            fn wake(self: Arc<Self>) {
3481                self.0.fetch_add(1, Ordering::Relaxed);
3482            }
3483
3484            fn wake_by_ref(self: &Arc<Self>) {
3485                self.0.fetch_add(1, Ordering::Relaxed);
3486            }
3487        }
3488
3489        let (sender, receiver) = mpsc::sync_channel(1);
3490        let workers = NativeWorkers {
3491            sender,
3492            admission: Arc::new(Mutex::new(AdmissionState::new())),
3493        };
3494        let job = || NativeJob::Task(Box::new(|| None));
3495        workers
3496            .sender
3497            .try_send(job())
3498            .map_err(|_| io::Error::other("failed to saturate admission queue"))?;
3499        workers
3500            .admission
3501            .lock()
3502            .map_err(|_| io::Error::other("admission mutex poisoned"))?
3503            .queued_jobs += 1;
3504
3505        let mut inactive = Some(job());
3506        let mut inactive_waiter = None;
3507        assert!(matches!(
3508            poll_submission_with(
3509                &workers,
3510                &mut inactive,
3511                &mut inactive_waiter,
3512                &Context::from_waker(Waker::noop()),
3513            ),
3514            Poll::Pending
3515        ));
3516        let wakes = Arc::new(CountWake(AtomicUsize::new(0)));
3517        let active_waker = Waker::from(Arc::clone(&wakes));
3518        let context = Context::from_waker(&active_waker);
3519        let mut active = Some(job());
3520        let mut active_waiter = None;
3521        assert!(matches!(
3522            poll_submission_with(&workers, &mut active, &mut active_waiter, &context),
3523            Poll::Pending
3524        ));
3525
3526        let _ = receiver.recv().map_err(io::Error::other)?;
3527        notify_capacity_released(&workers.admission);
3528        assert!(wakes.0.load(Ordering::Relaxed) > 0);
3529        assert!(matches!(
3530            poll_submission_with(&workers, &mut active, &mut active_waiter, &context),
3531            Poll::Ready(Ok(()))
3532        ));
3533        let _ = receiver.recv().map_err(io::Error::other)?;
3534        notify_capacity_released(&workers.admission);
3535        assert!(matches!(
3536            poll_submission_with(
3537                &workers,
3538                &mut inactive,
3539                &mut inactive_waiter,
3540                &Context::from_waker(Waker::noop()),
3541            ),
3542            Poll::Ready(Ok(()))
3543        ));
3544        let _ = receiver.recv().map_err(io::Error::other)?;
3545        notify_capacity_released(&workers.admission);
3546        assert_eq!(
3547            workers
3548                .admission
3549                .lock()
3550                .map_err(|_| io::Error::other("admission mutex poisoned"))?
3551                .queued_jobs,
3552            0
3553        );
3554        Ok(())
3555    }
3556
3557    #[cfg(windows)]
3558    #[test]
3559    fn native_read_batch_preserves_empty_and_eof_ranges() -> io::Result<()> {
3560        let temporary = tempfile::tempdir()?;
3561        let file = OpenOptions::new()
3562            .create_new(true)
3563            .read(true)
3564            .write(true)
3565            .open(temporary.path().join("read-batch-eof"))?;
3566        write_all_at(&file, 0, b"abc")?;
3567        let reads = complete_read(read_batch_async(
3568            file.try_clone()?,
3569            vec![
3570                OwnedRead {
3571                    offset: 0,
3572                    length: 0,
3573                },
3574                OwnedRead {
3575                    offset: 3,
3576                    length: 4,
3577                },
3578                OwnedRead {
3579                    offset: 30,
3580                    length: 4,
3581                },
3582            ],
3583        ))?;
3584        assert_eq!(reads, [Bytes::new(), Bytes::new(), Bytes::new()]);
3585        for (offset, expected) in b"abc".iter().copied().enumerate() {
3586            let repeated = complete_read(read_batch_async(
3587                file.try_clone()?,
3588                vec![OwnedRead {
3589                    offset: offset as u64,
3590                    length: 1,
3591                }],
3592            ))?;
3593            assert_eq!(repeated, [Bytes::copy_from_slice(&[expected])]);
3594        }
3595        Ok(())
3596    }
3597
3598    #[test]
3599    #[ignore = "local backend comparison; run with --ignored --nocapture on each target"]
3600    fn owned_io_backend_baseline() -> io::Result<()> {
3601        const BLOCK_BYTES: usize = 64 * 1024;
3602        const BLOCKS: usize = 256;
3603        let directory = tempfile::tempdir()?;
3604        let payload = Bytes::from(vec![0x5a; BLOCK_BYTES]);
3605        let baseline = OpenOptions::new()
3606            .create_new(true)
3607            .read(true)
3608            .write(true)
3609            .open(directory.path().join("baseline"))?;
3610        let native = NativeFile::from_file(
3611            OpenOptions::new()
3612                .create_new(true)
3613                .read(true)
3614                .write(true)
3615                .open(directory.path().join("native"))?,
3616        )?;
3617
3618        let copy_start = Instant::now();
3619        for _ in 0..BLOCKS {
3620            std::hint::black_box(Bytes::copy_from_slice(&payload));
3621        }
3622        let copy_elapsed = copy_start.elapsed();
3623
3624        let baseline_start = Instant::now();
3625        for block in 0..BLOCKS {
3626            direct_write_at(&baseline, &payload, (block * BLOCK_BYTES) as u64)?;
3627        }
3628        let baseline_write = baseline_start.elapsed();
3629        baseline.sync_all()?;
3630        let baseline_sync = baseline_start.elapsed();
3631        let mut buffer = vec![0_u8; BLOCK_BYTES];
3632        for block in 0..BLOCKS {
3633            assert_eq!(
3634                direct_read_at(&baseline, &mut buffer, (block * BLOCK_BYTES) as u64)?,
3635                BLOCK_BYTES
3636            );
3637            assert_eq!(buffer, payload);
3638        }
3639        let baseline_elapsed = baseline_start.elapsed();
3640
3641        complete_read(native.read_batch_async(Vec::new()))?;
3642        let native_start = Instant::now();
3643        complete_write(
3644            native.write_all_batch_async(
3645                (0..BLOCKS)
3646                    .map(|block| OwnedWrite {
3647                        offset: (block * BLOCK_BYTES) as u64,
3648                        bytes: payload.clone(),
3649                    })
3650                    .collect(),
3651            ),
3652        )?;
3653        let native_write = native_start.elapsed();
3654        complete_write(native.sync_async(Durability::Full))?;
3655        let native_sync = native_start.elapsed();
3656        let reads = complete_read(
3657            native.read_batch_async(
3658                (0..BLOCKS)
3659                    .map(|block| OwnedRead {
3660                        offset: (block * BLOCK_BYTES) as u64,
3661                        length: BLOCK_BYTES,
3662                    })
3663                    .collect(),
3664            ),
3665        )?;
3666        assert_eq!(reads.len(), BLOCKS);
3667        assert!(reads.iter().all(|bytes| bytes == &payload));
3668        let native_elapsed = native_start.elapsed();
3669        let serial_start = Instant::now();
3670        for block in 0..BLOCKS {
3671            let read = complete_read(native.read_batch_async(vec![OwnedRead {
3672                offset: (block * BLOCK_BYTES) as u64,
3673                length: BLOCK_BYTES,
3674            }]))?;
3675            assert_eq!(read.as_slice(), std::slice::from_ref(&payload));
3676        }
3677        let native_serial_read = serial_start.elapsed();
3678        eprintln!(
3679            "owned-io-baseline os={} arch={} bytes={} copy_us={} baseline_us={}/{}/{} native_us={}/{}/{} native_serial_read_us={}",
3680            std::env::consts::OS,
3681            std::env::consts::ARCH,
3682            BLOCKS * BLOCK_BYTES * 2,
3683            copy_elapsed.as_micros(),
3684            baseline_write.as_micros(),
3685            baseline_sync.as_micros() - baseline_write.as_micros(),
3686            baseline_elapsed.as_micros() - baseline_sync.as_micros(),
3687            native_write.as_micros(),
3688            native_sync.as_micros() - native_write.as_micros(),
3689            native_elapsed.as_micros() - native_sync.as_micros(),
3690            native_serial_read.as_micros(),
3691        );
3692        Ok(())
3693    }
3694
3695    #[cfg(target_os = "linux")]
3696    #[test]
3697    fn native_read_batch_chunks_large_ranges() -> io::Result<()> {
3698        let temporary = tempfile::tempdir()?;
3699        let file = OpenOptions::new()
3700            .create_new(true)
3701            .read(true)
3702            .write(true)
3703            .open(temporary.path().join("large-read-batch"))?;
3704        let expected = Bytes::from(vec![0x5a; 1024 * 1024 + 1]);
3705        write_all_at(&file, 7, &expected)?;
3706        let actual = complete_read(read_batch_async(
3707            file,
3708            vec![OwnedRead {
3709                offset: 7,
3710                length: expected.len(),
3711            }],
3712        ))?;
3713        assert_eq!(actual, [expected]);
3714        Ok(())
3715    }
3716
3717    #[cfg(target_os = "linux")]
3718    #[test]
3719    fn native_read_batch_windows_requests_beyond_the_ring() -> io::Result<()> {
3720        let temporary = tempfile::tempdir()?;
3721        let file = OpenOptions::new()
3722            .create_new(true)
3723            .read(true)
3724            .write(true)
3725            .open(temporary.path().join("windowed-read-batch"))?;
3726        let expected: Vec<u8> = (0..17).collect();
3727        write_all_at(&file, 0, &expected)?;
3728        let requests = (0_u64..17)
3729            .map(|offset| OwnedRead { offset, length: 1 })
3730            .collect();
3731        let actual = complete_read(read_batch_async(file, requests))?;
3732        assert_eq!(
3733            actual,
3734            expected
3735                .into_iter()
3736                .map(|value| Bytes::copy_from_slice(&[value]))
3737                .collect::<Vec<_>>()
3738        );
3739        Ok(())
3740    }
3741
3742    #[cfg(target_os = "linux")]
3743    #[test]
3744    fn native_read_batch_windows_one_range_beyond_the_ring() -> io::Result<()> {
3745        let temporary = tempfile::tempdir()?;
3746        let file = OpenOptions::new()
3747            .create_new(true)
3748            .read(true)
3749            .write(true)
3750            .open(temporary.path().join("windowed-large-read"))?;
3751        let expected = Bytes::from(vec![0x69; 16 * 1024 * 1024 + 1]);
3752        write_all_at(&file, 3, &expected)?;
3753        let actual = complete_read(read_batch_async(
3754            file,
3755            vec![OwnedRead {
3756                offset: 3,
3757                length: expected.len(),
3758            }],
3759        ))?;
3760        assert_eq!(actual, [expected]);
3761        Ok(())
3762    }
3763}