1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
//! [`AsyncSubstrate`] — which runtime substrate the async layer uses.
//!
//! The async layer (gated behind the `async` Cargo feature) has two
//! execution paths since `0.7.0`:
//!
//! 1. **Native io_uring substrate** — Linux only, when
//! [`Method::Direct`](crate::Method::Direct) is in use, the
//! per-handle io_uring ring is constructed, and the
//! `FSYS_DISABLE_NATIVE_ASYNC=1` environment override is **not**
//! set. Async ops submit directly to the ring and `.await` a
//! `oneshot` driven by a per-handle completion driver task; no
//! `spawn_blocking` thread-pool hop.
//! 2. **`spawn_blocking` fallback** — every other configuration
//! (Windows, macOS, sync method, io_uring unavailable, override
//! set). Mirrors the 0.6.0 baseline.
//!
//! The enum + accessor are always present. On non-Linux builds and
//! on Linux without the `async` feature, [`Handle::async_substrate`](crate::Handle::async_substrate)
//! returns [`AsyncSubstrate::SpawnBlocking`] (the substrate that
//! *would* be used if the caller enabled the feature) so consumers
//! can always read the value without `cfg`-gating their match arms.
//!
//! ## Asymmetry by design
//!
//! Per locked decision `D-1` in `.dev/DECISIONS-0.7.0.md`, the
//! native substrate is Linux-only. Windows and macOS async stay on
//! `spawn_blocking` indefinitely — there's no clean primitive for a
//! native async fast path on those platforms (Windows IOCP would
//! work but isn't justified by demand; macOS has no equivalent).
//! The asymmetry is documented honestly rather than papered over.
/// Which runtime substrate the async layer uses for a given handle.
///
/// Read at runtime via [`crate::Handle::async_substrate`]. The value
/// is computed on each call (it's cheap — three atomic loads and a
/// pointer check) so callers always see the current truth: e.g.
/// when the io_uring ring is lazily constructed on the first Direct
/// op, the substrate transitions from `SpawnBlocking` (pre-probe)
/// to `NativeIoUring` (post-probe) without further configuration.
///
/// # Examples
///
/// ```no_run
/// # async fn example() -> fsys::Result<()> {
/// let fs = std::sync::Arc::new(fsys::builder().build()?);
/// match fs.async_substrate() {
/// fsys::AsyncSubstrate::NativeIoUring => {
/// // Linux + Method::Direct + io_uring active.
/// // Async ops submit directly to the ring.
/// }
/// fsys::AsyncSubstrate::SpawnBlocking => {
/// // Cross-platform fallback — async ops use
/// // tokio::task::spawn_blocking.
/// }
/// // `AsyncSubstrate` is `#[non_exhaustive]` for forward
/// // compatibility (e.g. a future Windows IOCP substrate).
/// _ => unreachable!(),
/// }
/// # Ok(())
/// # }
/// ```