Summary
On any Linux environment where io_uring is unavailable — AWS Fargate, AWS
Lambda, gVisor, or a Docker seccomp profile that denies io_uring_setup —
walrus-rust 0.2.0 panics its background flush/cleanup worker thread. The README
documents io_uring as an optional acceleration with fallbacks, but the
implementation hard-.expect()s on it.
Environment
- walrus-rust 0.2.0 (crates.io)
- Linux container on AWS Fargate (platform seccomp policy denies io_uring syscalls)
What happens
Walrus::with_consistency(ReadConsistency::AtLeastOnce { persist_every: 100 })
spawns the background worker, which immediately panics:
thread '' panicked at src/wal/runtime/background.rs:43:53:
Failed to create io_uring: Os { code: 1, kind: PermissionDenied, message: "Operation not permitted" }
src/wal/runtime/background.rs:43:
#[cfg(target_os = "linux")]
let mut ring = io_uring::IoUring::new(2048).expect("Failed to create io_uring");
This runs unconditionally when the background worker starts (only
#[cfg(target_os = "linux")]-gated). The panic kills the whole flush + cleanup
thread, so periodic fsync and WAL file compaction/deletion silently stop.
Walrus::new() itself still returns Ok, so the failure is silent — the WAL
looks healthy while its background maintenance is dead.
Why this is a bug
The README frames io_uring as optional ("io_uring acceleration on Linux";
"disable_fd_backend() switches to the mmap backend"; "other platforms fall
back to sequential writes"). io_uring is genuinely absent in many standard
environments, so it shouldn't be assumed present on Linux. The fallback
machinery already exists — USE_FD_BACKEND / disable_fd_backend() and the
non-io_uring flush path in the same function. The worker just never engages it
on init failure; and disable_fd_backend() doesn't help because line 43 runs
before/independent of the USE_FD_BACKEND check.
Suggested fix
Make IoUring::new() failure fall back instead of panicking:
#[cfg(target_os = "linux")]
let mut ring = match io_uring::IoUring::new(2048) {
Ok(r) => Some(r),
Err(e) => {
eprintln!("walrus: io_uring unavailable ({e}); using blocking fsync fallback");
crate::wal::config::disable_fd_backend();
None
}
};
then gate the io_uring submission block on ring.as_mut() (the if USE_FD_BACKEND branch is already skipped once disable_fd_backend() is called).
Happy to send a PR.
Summary
On any Linux environment where
io_uringis unavailable — AWS Fargate, AWSLambda, gVisor, or a Docker seccomp profile that denies
io_uring_setup—walrus-rust 0.2.0 panics its background flush/cleanup worker thread. The README
documents io_uring as an optional acceleration with fallbacks, but the
implementation hard-
.expect()s on it.Environment
What happens
Walrus::with_consistency(ReadConsistency::AtLeastOnce { persist_every: 100 })spawns the background worker, which immediately panics:
thread '' panicked at src/wal/runtime/background.rs:43:53:
Failed to create io_uring: Os { code: 1, kind: PermissionDenied, message: "Operation not permitted" }
src/wal/runtime/background.rs:43:This runs unconditionally when the background worker starts (only
#[cfg(target_os = "linux")]-gated). The panic kills the whole flush + cleanup
thread, so periodic fsync and WAL file compaction/deletion silently stop.
Walrus::new() itself still returns Ok, so the failure is silent — the WAL
looks healthy while its background maintenance is dead.
Why this is a bug
The README frames io_uring as optional ("io_uring acceleration on Linux";
"disable_fd_backend() switches to the mmap backend"; "other platforms fall
back to sequential writes"). io_uring is genuinely absent in many standard
environments, so it shouldn't be assumed present on Linux. The fallback
machinery already exists — USE_FD_BACKEND / disable_fd_backend() and the
non-io_uring flush path in the same function. The worker just never engages it
on init failure; and disable_fd_backend() doesn't help because line 43 runs
before/independent of the USE_FD_BACKEND check.
Suggested fix
Make IoUring::new() failure fall back instead of panicking:
then gate the io_uring submission block on ring.as_mut() (the if USE_FD_BACKEND branch is already skipped once disable_fd_backend() is called).
Happy to send a PR.