Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion CONTRIBUTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -60,7 +60,7 @@ using this cargo binary (not the musl `target/release/` install).
### What works on each OS

Linux is the **supported operator IO** target (`io_uring` when the ring opens).
Darwin defaults to the **pool** session; Windows to **IOCP** (no IoRing). Matrix:
Darwin defaults to libc **pread** (`RBITCOIN_IO=pool` opts into the pool session); Windows to **IOCP** (no IoRing). Matrix:
[`docs/io-modality.md`](./docs/io-modality.md). PR CI still smokes native store
IO on Windows and Darwin.

Expand Down
8 changes: 8 additions & 0 deletions changelog.d/darwin-pread-default.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
Changed

- **macOS reads with libc pread by default.**
The worker-pool session handed every read to a pool thread through one
shared lock. In signet IBD that cost more than the reads: about 20% slower
end to end with a warm or cold cache, and four times the system CPU.
`RBITCOIN_IO=pool` still opens the pool session. Linux and Windows are
unchanged.
40 changes: 28 additions & 12 deletions crates/rbitcoin-store/src/bulk_io.rs
Original file line number Diff line number Diff line change
Expand Up @@ -23,8 +23,9 @@
//!
//! # Non-Linux
//!
//! Darwin default is the **pool** completion session (kqueue is not a
//! regular-file backend). Windows uses IOCP.
//! Darwin default is libc **pread** (kqueue is not a regular-file backend,
//! and the pool session's per-read handoff cost more than the reads in IBD);
//! `RBITCOIN_IO=pool` opens the pool session there. Windows uses IOCP.
//! Machines stay staged; they do not flatten to one-shot `pread`. See
//! `docs/io-modality.md` and `docs/concurrency.md`.

Expand Down Expand Up @@ -66,11 +67,7 @@ pub fn io_uring_enabled() -> bool {
1 => true,
2 => false,
_ => {
let want = match parse_io_token() {
Some(IoToken::Pread) => false,
Some(_) | None => true,
};
if !want {
if !session_wanted(parse_io_token()) {
URING_MODE.store(2, Ordering::Relaxed);
return false;
}
Expand All @@ -87,6 +84,16 @@ pub fn io_uring_enabled() -> bool {
}
}

fn session_wanted(token: Option<IoToken>) -> bool {
match token {
Some(IoToken::Pread) => false,
Some(_) => true,
// Darwin's only session is the worker pool. Its per-read handoff cost
// more than the read in signet IBD (about 20% slower, warm or cold).
None => !cfg!(target_os = "macos"),
}
}

#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub(crate) enum IoToken {
Uring,
Expand Down Expand Up @@ -129,15 +136,11 @@ fn default_session_kind() -> crate::uring_session::SessionKind {
{
SessionKind::Uring
}
#[cfg(target_os = "macos")]
{
SessionKind::Pool
}
#[cfg(windows)]
{
SessionKind::Iocp
}
#[cfg(not(any(target_os = "linux", target_os = "macos", windows)))]
#[cfg(not(any(target_os = "linux", windows)))]
{
SessionKind::Pool
}
Expand Down Expand Up @@ -600,6 +603,19 @@ fn pwrite_one(op: &mut WriteOp<'_>) {
#[cfg(test)]
mod tests {
use super::*;

#[test]
fn unset_io_env_opens_a_session_except_on_darwin() {
#[cfg(target_os = "macos")]
assert!(!session_wanted(None), "Darwin defaults to libc pread");
#[cfg(not(target_os = "macos"))]
assert!(session_wanted(None));
assert!(
session_wanted(Some(IoToken::Pool)),
"RBITCOIN_IO=pool still opens the pool session"
);
assert!(!session_wanted(Some(IoToken::Pread)));
}
use std::io::Write;

fn dummy_handle() -> IoHandle {
Expand Down
5 changes: 3 additions & 2 deletions crates/rbitcoin-store/src/io_session_pool.rs
Original file line number Diff line number Diff line change
@@ -1,8 +1,9 @@
//! Completion-ring facade over a bounded worker pool.
//!
//! Same harvest contract as io_uring (`user_data`, drain, in-flight cap).
//! Darwin's file AIO is a thread pool; this is the honest session there and
//! the Linux CI pin (`RBITCOIN_IO=pool`).
//! Darwin's file AIO is a thread pool; this is the honest session there when
//! `RBITCOIN_IO=pool` asks for it (Darwin defaults to libc pread), and the
//! Linux CI pin.
//!
//! Workers are **process-shared**. Each [`PoolEngine`] is a session handle
//! with its own CQE queue so harvest does not steal another thread's
Expand Down
5 changes: 2 additions & 3 deletions crates/rbitcoin-store/src/uring_session.rs
Original file line number Diff line number Diff line change
Expand Up @@ -176,7 +176,7 @@ fn finish_drain_hard_cap(pending: usize) -> Result<(), StoreError> {
pub enum SessionKind {
/// Linux io_uring. On Windows this opens IOCP.
Uring,
/// Worker-pool completion ring (Darwin default; Linux `RBITCOIN_IO=pool`).
/// Worker-pool completion ring (`RBITCOIN_IO=pool` on Darwin or Linux).
Pool,
/// Windows IOCP.
Iocp,
Expand Down Expand Up @@ -1835,14 +1835,13 @@ mod tests {
}

#[test]
#[cfg(any(target_os = "linux", windows))]
fn default_kind_follows_os_when_env_unset() {
if std::env::var_os("RBITCOIN_IO").is_some() {
return;
}
#[cfg(target_os = "linux")]
assert_eq!(crate::bulk_io::resolved_session_kind(), SessionKind::Uring);
#[cfg(target_os = "macos")]
assert_eq!(crate::bulk_io::resolved_session_kind(), SessionKind::Pool);
#[cfg(windows)]
assert_eq!(crate::bulk_io::resolved_session_kind(), SessionKind::Iocp);
}
Expand Down
13 changes: 8 additions & 5 deletions docs/io-modality.md
Original file line number Diff line number Diff line change
Expand Up @@ -37,11 +37,11 @@ Unknown tokens (including deleted `mmap`) fall through to the platform default.
| Token | Backend |
|-------|---------|
| `uring` / `io_uring` | Linux `io_uring`. On Windows the token opens **IOCP** |
| `pool` | Worker-pool completion ring (Darwin **default**; Linux CI pin) |
| `pool` | Worker-pool completion ring (opt-in on Darwin; Linux CI pin) |
| `iocp` | Windows IOCP (Windows **default**) |
| `pread` / `fd` / `libc` / `pwrite` | Disable session; libc positional IO (+ workers for reads) |

**Defaults:** Linux `io_uring` if the ring opens, else libc **pread** (session off). Darwin **pool**.
**Defaults:** Linux `io_uring` if the ring opens, else libc **pread** (session off). Darwin libc **pread** (session off).
Windows **IOCP**. Windows IoRing is not supported.

**Windows table handles** are `FILE_FLAG_OVERLAPPED` so IOCP can bind.
Expand All @@ -56,8 +56,11 @@ heap OVERLAPPEDs).
**kqueue is not a regular-file backend.** Darwin files report ready immediately;
`read` still blocks. POSIX AIO (`EVFILT_AIO`) and `dispatch_io` are also
thread pools (`kern.aiomax` default 16) — same class as `pool`, not an
SQ/CQ. The pool session is the honest Darwin completion ring. Pool
**workers** are process-shared; each TLS session keeps its own CQE queue.
SQ/CQ. The pool session is the honest Darwin completion ring, but each read
costs a shared-queue lock and two thread wakeups. In signet IBD that cost more
than the reads, warm or cold cache (about 20% slower end to end, four times the
system CPU), so Darwin defaults to libc pread and `RBITCOIN_IO=pool` opts in.
Pool **workers** are process-shared; each TLS session keeps its own CQE queue.

**TLS session:** one completion session per OS thread (`with_thread_local`).
Nested calls panic. Harvest tracks pending `(kind, epoch, slot)`. A CQE that
Expand Down Expand Up @@ -149,7 +152,7 @@ RAM fence (~15 MiB at 1M blocks), not a file.
| Block index build (filters + tweaks) | `RBITCOIN_IO` | `read_index_window`: one held TLS session per window, four stages (`create.loc` / `input.loc` / `seqsigwit.loc` windows → block `txout` / `input.body` / `txid.body` spans → parent `create.loc` + P2TR-output `seqsigwit.body` + parent `txid.body` → parent `txout`), offsets from RAM checkpoints so nothing nests. Parents read once per window. Short/errno on a live session libc-completes; no session → serial pread through the same stages. Writes are not on the ring: one filter put and one tweak put per window. |
| SP-tweak hole serve | `RBITCOIN_IO` | `tweaks_for_height` for heights the index has not sealed: idx ranges **before** TLS ring; uring/pread `txout.body`, then `seqsigwit` + parent `txout` for P2TR only. |

Default: Linux uring if the ring opens else libc pread; Darwin pool; Windows
Default: Linux uring if the ring opens else libc pread; Darwin pread; Windows
IOCP. Ring depth **128** (merge may grow). `RBITCOIN_IO=pread` forces libc.

### Table transport (all fd)
Expand Down
4 changes: 3 additions & 1 deletion scripts/ci-os-smoke.sh
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ STORE_PLATFORM_FILTERS=(
sorted_run::tests::host_mem
sorted_run::tests::darwin_vm
io_backend::tests
uring_session::tests::default_kind_follows_os
bulk_io::tests::unset_io_env_opens_a_session
uring_session::tests::pool_
fuse8_filter::tests::no_false_negatives_and_roundtrip
segmented_head::tests::insert_roll_seal_lookup_roundtrip
Expand Down Expand Up @@ -44,6 +44,8 @@ case "${CI_OS_SMOKE_UNAME:-$(uname -s)}" in
STORE_PLATFORM_SKIPS+=(concurrent_readers_during_append_and_grow)
# `mod io_session_iocp` is #[cfg(windows)] — the tests do not exist elsewhere.
STORE_PLATFORM_FILTERS+=(io_session_iocp)
# Linux and Windows only: Darwin opens no session by default.
STORE_PLATFORM_FILTERS+=(uring_session::tests::default_kind_follows_os)
PRIMITIVES_PLATFORM_FILTERS+=(loc_simd::tests)
;;
Darwin*)
Expand Down
8 changes: 6 additions & 2 deletions scripts/ci-os-smoke.test.sh
Original file line number Diff line number Diff line change
Expand Up @@ -36,8 +36,10 @@ assert_ok "dry-run lists SH RAM / host_mem probes" \
grep -q "sorted_run::tests::host_mem" <<<"$out"
assert_ok "dry-run lists Darwin vm page math" \
grep -q "sorted_run::tests::darwin_vm" <<<"$out"
assert_ok "dry-run lists default session kind" \
grep -q "uring_session::tests::default_kind_follows_os" <<<"$out"
assert_ok "dry-run lists the default IO session per OS" \
grep -q "bulk_io::tests::unset_io_env_opens_a_session" <<<"$out"
assert_ok "only Windows lists the default session kind test" \
test "$(grep -c 'uring_session::tests::default_kind_follows_os' <<<"$out" || true)" = "0"
assert_ok "dry-run lists pool session tests" \
grep -q "uring_session::tests::pool_" <<<"$out"

Expand All @@ -54,6 +56,8 @@ assert_ok "Windows dry-run still confirms a few blocks" \
grep -q "chain_view_pin_asof_reorg" <<<"$out"
assert_ok "Windows dry-run lists IOCP session tests" \
grep -q "io_session_iocp" <<<"$out"
assert_ok "Windows dry-run pins the IOCP default session kind" \
grep -q "uring_session::tests::default_kind_follows_os" <<<"$out"
assert_ok "Windows dry-run pins loc SIMD (SSE2) vs scalar" \
grep -qx "primitives=loc_simd::tests" <<<"$out"

Expand Down
Loading