windows-ioring-sys 0.1.1

Memory-safe Rust over the Windows IoRing submission/completion ring, with zero-allocation buffer handoff.
Documentation
windows-ioring-sys-0.1.1 has been yanked.

windows-ioring-sys

Memory-safe Rust over the Windows IoRing submission/completion ring.

Windows only. Every item is behind cfg(windows); the crate builds to an empty shell on other platforms.

Windows 11 and Server 2022 added IoRing, a submission/completion ring for file I/O closer in shape to io_uring than to anything else Windows offers. This crate raises those primitives into safe Rust with the minimum additional CPU and memory cost: a completion hands the caller's buffer back without the crate having allocated anything to track it.

Example

Submit a read, then pop its completion once it is ready:

use windows_ioring_sys::{Batch, IoRing, SharedFile};
use std::os::windows::io::OwnedHandle;

let file = std::fs::File::open(r"C:\some\file.bin")?;
let shared = SharedFile::new(OwnedHandle::from(file));
let mut ring = IoRing::new(8, 8)?;

let token = {
    let mut batch = Batch::new(&mut ring);
    let token = batch.read(&shared, vec![0_u8; 4096], 0, Default::default())?;
    batch.submit_and_wait(1, 5_000)?;
    token
};

let completion = ring.try_pop()?.expect("a completion is ready");
completion.result()?;
let (buffer, _file) = token.claim_if(&completion).expect("token claims its own completion");
println!("read {} bytes", buffer.len());
# Ok::<(), std::io::Error>(())

[EventDelivery] wires completions to the thread pool instead, so no thread ever calls try_pop itself; see examples/model_a_delivery.rs for that shape, and the "Choosing a delivery architecture" section below for when to reach for each.

Scope: a file data plane, not a general completion backend

The kernel's operation table is fixed at seven entries -- no-op, read, write, flush, register-files, register-buffers, and cancel. There is no ioctl operation, no socket operation, and no directory-change operation, and unlike Linux's io_uring -- which grew to roughly fifty opcodes including full socket support -- Windows IoRing has not grown beyond file I/O.

So this crate does not replace windows-overlapped-io-sys, which remains the crate for arbitrary I/O on arbitrary handles. Use this one for high-volume file reads and writes; use that one for everything else. Neither can subsume the other, and the division is the kernel's rather than this repository's.

Availability is a runtime question

IoRing ships with Windows 11 and Server 2022, but three separate facts about it are decided at runtime rather than at compile time:

  • which ring version the system supports;
  • whether the ring is a real kernel ring or a user-mode emulation with no kernel benefit;
  • whether the completion-event feature the thread-pool delivery path depends on is available at all.

All three are answerable without creating a ring, and this crate surfaces them rather than hiding them -- a consumer reaching for a ring to maximize throughput needs to know if they are getting an emulation.

Choosing a delivery architecture

There are two coherent high-performance shapes, and they are mutually exclusive on the hot path. Picking the wrong one costs more than any API detail in this crate, so the trade-off is documented in full under "Two delivery architectures" in DESIGN-NOTES.md, including why the NUMA node is the wrong key for partitioning rings and why buffer placement likely matters more than thread placement.

Model A -- shared queue, kernel load-balances. EventDelivery wires the ring's completion event to a ThreadpoolWait from windows-threadpool-sys, so no thread of yours ever blocks on I/O. Start here; see examples/model_a_delivery.rs for a full worked example that submits without waiting and receives every completion on a pool thread.

Model B -- shared-nothing execution domains. One pinned thread per domain, owning its ring, its buffer pool, and its shard of the work, parked directly in Batch::submit_and_wait -- the fused submit-and-wait is the event loop. This is the shape IoRing's own API is built for.

Most real applications want Model B on the hot data path and Model A everywhere else -- the control plane, background work, cold paths -- where the thread pool's quiescence is worth more than locality. This crate supports both as first-class; neither is a degraded form of the other.

Topology guidance

This crate does not partition anything for you (D-8): it makes a ring cheap and correct, makes its affinity explicit, and leaves sizing a Model B execution domain to the caller.

  • Size a domain by last-level (L3) cache, not by NUMA node. Node count is a firmware setting a process cannot see, and most real deployments are virtualized, where NUMA topology is often invisible entirely. See examples/l3_domains.rs for a runnable enumeration, built on the safe GetLogicalProcessorInformationEx wrapper in windows-topology-sys.
  • Processor groups are a hard floor. A thread's affinity is a GROUP_AFFINITY and a ring's waiter lives in exactly one group, so above 64 logical processors the partition is forced whether or not it is wanted.
  • Buffer placement likely dominates thread placement. VirtualAllocExNuma on the node closest to the device, registered once into that domain's ring via Batch::register_buffers, is very likely the highest-leverage locality decision available -- independent of everything above about completion routing.

examples/ring_copy is where these three points become runnable policy: it copies one file to another through per-domain rings, sized by a named ByL3/ByNode/ByPackage/ByCore/Single policy, with buffers placed via VirtualAllocExNuma and a --placement local|remote switch to make the placement effect measurable. It is a sample, not library surface -- this crate itself depends on no partitioning policy and does not depend on windows-topology-sys; only the sample does.

License

MIT. Copyright (c) Mike Grier.