turmoil

repository·main·Indexed 22 days ago

https://github.com/tokio-rs/turmoil

A deterministic simulation testing framework for distributed systems in Rust. It enables developers to inject network and filesystem hardships—such as partitions, crashes, latency, and torn writes—into a single-threaded, multi-host environment to ensure robust and reproducible distributed logic. The ecosystem includes turmoil-net for simulated socket stacks, turmoil-fs for deterministic filesystem shims, and turmoil-io-uring for simulated io_uring interfaces.

Tokens
27.6K
Snippets
34
Records
170
Agent score
78%

What's inside turmoil

  1. Overview of Turmoil crates

    main

    Turmoil is split into several specialized crates depending on the level of simulation required:

    • turmoil: The full simulation-testing framework. This is the primary entry point for users.
    • turmoil-net: A deterministic simulated socket stack. It is designed as a drop-in replacement for tokio::net.
    • turmoil-fs: A deterministic simulated filesystem. It is designed as a drop-in replacement for std::fs or tokio::fs.
    • turmoil-io-uring: A deterministic simulated io_uring. It includes an optional fs feature that integrates it with turmoil-fs.
  2. Use the turmoil crate for a complete simulation harness

    main

    The turmoil crate serves as the full-stack entry point for the turmoil ecosystem. It bundles a runtime, a simulated network, and a simulated filesystem behind a single Builder. Use this crate if you want a complete, ready-to-use simulation harness.

    If you are assembling a custom runtime and do not need the full bundle, you should instead use turmoil-net for networking or turmoil-fs for filesystem simulation directly.

    [dev-dependencies]
    turmoil = "0.7"
  3. What is turmoil-io-uring?

    main

    turmoil-io-uring is a simulated implementation of the io_uring interface designed for deterministic testing in Rust. It is intended to allow developers to run code that normally relies on the Linux io_uring kernel interface on other platforms like macOS or Windows within a Turmoil simulation.

    Key Characteristics:

    • API Parity: It closely mirrors the io-uring 0.7 crate API. You can often swap use io_uring::*; for use turmoil_io_uring::*; via feature flags.
    • Simulated Execution: It is not a real kernel interface. There are no syscalls or memory-mapped rings. Instead, Submission Queue Entries (SQEs) are queued in per-host Rust state, and Completion Queue Entries (CQEs) are made observable as simulated time advances.
    • Platform Agnostic: Enables testing of io_uring-based logic in environments without a Linux kernel.
  4. What is Turmoil and how does it work?

    main

    Turmoil is a family of crates designed for deterministic simulation testing of distributed systems. It allows you to run multiple concurrent hosts within a single thread, enabling you to simulate complex distributed environments reliably.

    Key capabilities include:

    • Deterministic Simulation: Runs multiple hosts in a single thread to ensure reproducible test runs.
    • Hardship Injection: You can manually control or use a seeded RNG to inject network and filesystem failures such as:
      • Latency
      • Packet drops
      • Network partitions
      • Node crashes
      • Torn writes
  5. Simulate networking in async Rust tests with turmoil-net

    main

    turmoil-net provides a deterministic, simulated socket stack for testing async Rust code. Instead of using real OS sockets which are nondeterministic due to timing and scheduling, you use a shim that implements real protocols (handshake, FIN, RST, MSS, backpressure) on a controlled substrate.

    To use it, swap your tokio::net imports for turmoil_net::shim::tokio::net imports using #[cfg] attributes in your code. This allows the same logic to run against real networking in production and a simulated network during tests.

    #[cfg(not(test))]
    use tokio::net::TcpStream;
    #[cfg(test)]
    use turmoil_net::shim::tokio::net::TcpStream;
  6. Use Barriers for deterministic event scheduling

    main

    The barriers feature (currently unstable) allows tests to observe and control specific points in the code under test.

    By using a trigger(event) call in your production code (which is conditionally compiled), the execution will block until the test resolves that specific event. This enables deterministic scheduling of arbitrary events without needing to thread them through the network layer.

    Note: This requires enabling the unstable-barriers feature.

  7. How turmoil-fs models durability and crashes

    main

    Unlike a standard mock that simply returns primed bytes, turmoil-fs models the distinction between data in the page cache and data on stable storage:

    1. Writes: Land in a per-host 'pending' state.
    2. fsync: Promotes pending writes to a 'durable' state.
    3. Simulated Crash: Discards the pending state, simulating data loss for writes that were not yet synced.

    By using a seeded RNG to drive the interleaving of pending/sync operations, you can create deterministic and replayable crash-consistency tests.

  8. Use turmoil-fs as a deterministic filesystem shim

    main

    turmoil-fs provides a simulated filesystem designed for testing async Rust code deterministically. It allows you to simulate crash-consistency scenarios (like failures between write and fsync) by explicitly modeling the split between pending writes and durable storage.

    To use it, you swap standard library or tokio filesystem imports for turmoil_fs::shim imports using #[cfg(test)]. This allows the same production code to run against a controlled, simulated filesystem during testing.

    Supported shim modules:

    • turmoil_fs::shim::std (Standard library replacements)
    • turmoil_fs::shim::tokio (Tokio replacements)
    #[cfg(not(test))]
    use std::fs::OpenOptions;
    #[cfg(test)]
    use turmoil_fs::shim::std::fs::OpenOptions;