Skip to content

Background worker panics on io_uring init failure (Fargate / seccomp-restricted Linux) instead of falling back #25

Description

@patrickemoore

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.

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions