# Shuttle makes Rust concurrency bugs reproducible by controlling the scheduler

> Shuttle is a Rust library that runs your test under a scheduler it controls, so an interleaving that triggers a race can be replayed deterministically instead of waited for. It trades exhaustiveness for scale, and the repository shows the price in a workspace split across an engine, schedulers, a standard library shim and a large set of wrappers.

**awslabs/shuttle** — Shuttle is a library for testing concurrent Rust code

- Repository: https://github.com/awslabs/shuttle
- Stars: 1,095 · Forks: 62
- Language: Rust
- License: Apache-2.0
- Published: 2026-10-19 · Updated: 2026-10-19 · Language: en
- Canonical page: https://hysenlabs.com/projects/awslabs-shuttle

## The problem Shuttle attacks is a race you cannot reproduce

The README opens with a piece of concurrent Rust code and the reason it is hard to test. A value is protected by an Arc<Mutex<u64>>, a spawned thread writes 1 into it, and the main thread asserts the value is still 0. If the thread runs first, the assertion fails. If it runs second, the test passes.

Writing a unit test that reliably finds that execution is the difficult part. The obvious approach, running the test many times and hoping to get lucky, is unreliable. And a lucky failure is still painful to work with, because you cannot easily catch it in a debugger, and every time you change the code you have to run the test many times again to decide whether you fixed anything.

Shuttle's answer is to take control of scheduling. It controls the scheduling of each thread in the program and schedules those threads randomly. Controlling the schedule is what makes a failing interleaving reproducible, and randomising it is what lets the tool explore many orders without a search of the full state space. The project is explicit that this is randomized concurrency testing, with the probabilistic guarantee described in the research literature it links, and not an exhaustive checker.

## check_random turns a unit test into a scheduled replay

The change to the test is small and mechanical. The test body is wrapped in a call to Shuttle's check_random function, and the concurrency related imports from std are replaced with imports from shuttle.

```rust
use shuttle::sync::{Arc, Mutex};
use shuttle::thread;

shuttle::check_random(|| {
    let lock = Arc::new(Mutex::new(0u64));
    let lock2 = lock.clone();

    thread::spawn(move || {
        *lock.lock().unwrap() = 1;
    });

    assert_eq!(0, *lock2.lock().unwrap());
}, 100);
```

The second argument is the number of iterations, 100 in this example. That number is the budget of random schedules the tool will try, and it is the knob that decides how long the test takes and how likely it is to hit the bad interleaving. The README states that this test detects the assertion failure with extremely high probability, over 99.9999 percent.

Note what the wrapper does not change. The assertion still says the same thing, and the failure is still an assertion failure. The difference is that the failure now arrives on a schedule Shuttle chose, so a failing run can be reproduced rather than chased.

## The wrappers are the price of admission for std and tokio types

A scheduler can only control threads it knows about, so any type that spawns or synchronises has to come from Shuttle rather than from the standard library. The repository therefore houses a collection of wrappers and their implementations, and the README points to a separate wrappers README for how to use them.

The scope is wide. The wrappers cover popular libraries such as tokio and rand, as well as synchronisation primitives from std::sync and collections from std::collections. The workspace manifest shows the concrete list: a tokio wrapper family including shuttle-tokio, shuttle-tokio-stream, shuttle-tokio-retry and shuttle-tokio-util, plus impls for tokio, tokio-macros, tokio-stream, tokio-test and tokio-util. There are wrappers for parking_lot, lazy_static, async_stream and dashmap, a rand wrapper for version 0.8, and two collections wrappers, one for the standard collections and one for deterministic collections.

That last entry is a clue about the design. Collections have their own wrapper because iteration order and hash seeds can change behaviour between runs, and a concurrency test needs those sources of variation pinned or controlled. The name of the package suggests the collections wrapper makes iteration deterministic, which is exactly what you need to replay a schedule and get the same result twice.

## Four crates and a dozen wrappers in one workspace

The top level directory listing shows how the project is decomposed. There is shuttle/, which is the facade crate you depend on, and behind it shuttle-engine/, shuttle-schedulers/ and shuttle-std/. There is also shuttle-explorer/, and a wrappers/ directory that holds the wrapper crates listed in the workspace manifest.

The split is readable from the names. The engine is the runtime that owns the controlled execution, the schedulers directory holds the scheduling strategies, and shuttle-std is the substitute for the standard library pieces that the engine needs to control. The explorer is a separate component from the library itself, which suggests an interactive way to look at schedules rather than only running them inside a test.

The workspace uses resolver version 2 and lists every wrapper as an explicit member, so each wrapper is its own crate with its own version and its own dependency on the engine. The repository also carries a CHANGELOG.md, a CODE_OF_CONDUCT.md, a CONTRIBUTING.md with a security issue notification section, a NOTICE file, a THIRD-PARTY directory and a rustfmt.toml, which is the usual set of files for a project that is prepared to be consumed by others.

## Shuttle against Loom: randomized search versus exhaustive search

The README names the alternative explicitly. Shuttle is inspired by Loom, the library for testing concurrent Rust code, and the two make different bets.

Loom offers exhaustive testing. It enumerates the possible interleavings of the operations in your test and checks them all, so when Loom says a test passes you have covered the state space you wrote. The cost is that the state space grows fast, and a test that touches enough synchronisation operations becomes too slow to run on every commit.

Shuttle focuses on randomized testing instead. The README calls this a soundness and scalability trade-off and is blunt about the consequence: Shuttle is not sound, meaning a passing Shuttle test does not prove the code is correct, but it scales to much larger test cases than Loom.

The empirical claim is that randomized testing succeeds at finding most concurrency bugs, on the grounds that real bugs tend not to be adversarial. That is a judgement about bug shape rather than a guarantee.

The two are complementary rather than competing. A property that Shuttle keeps failing is a candidate for a Loom model, and a Loom model that is too slow to run is a candidate for Shuttle.

## What Shuttle does not check

There is a category of problem Shuttle will not find, and it follows directly from the design.

Shuttle randomises the schedule of the threads it controls. It does not explore every ordering, so there are interleavings it will not visit during a given run. The iteration count in check_random bounds the search, and a larger count raises the chance of hitting a rare bad order at the cost of test time.

The same limitation applies to the code under test. Anything that spawns a thread or schedules work outside the Shuttle wrappers is invisible to the scheduler. A component that uses std::thread directly, or a dependency that reaches for a real async runtime, will run on the operating system scheduler and break the model's assumptions. The README's approach of replacing the std imports with shuttle imports is therefore not a style preference but a correctness requirement for the test to mean anything.

The other limitation is about what a green run tells you. The project states it directly: Shuttle is not sound, so a passing test is evidence that no bug was found in the schedules tried, not evidence that the code is free of races.

## A fast moving 0.9 line under Apache-2.0

The project is not archived, and the last push was on 2026-10-15. Releases have been frequent on the 0.9 line: v0.9.7 on 2026-10-14, v0.9.6 on 2026-10-10 and v0.9.5 on 2026-10-01. Three patch releases inside a fortnight is a lot of movement for a testing library, and it implies that the wrapper surface is still settling, particularly the tokio family, which is where the underlying runtime moves fastest.

A version number under 1.0 also means the public API can still change. The repository is published on crates.io and documented on docs.rs, both of which carry version badges, and the crates.io badge reflects the released version rather than the trunk build.

The licence is Apache-2.0, so Shuttle can be used inside proprietary products, and the NOTICE and THIRD-PARTY files in the repository cover the attribution requirements for the bundled code. Security issues are handled through the contribution documentation rather than a separate process.

For a team adding Shuttle to CI, the practical advice is to pin the version rather than track the 0.9 line, since a patch release can change which schedules a test explores and a test that starts failing on an unrelated commit is expensive to diagnose.

## Conclusion

Adopt Shuttle for a Rust codebase whose concurrency bugs are intermittent and expensive to reproduce, especially one that uses tokio, rand or the std synchronisation primitives it wraps. Do not adopt it as a proof of correctness, since the project states that a passing Shuttle test does not prove the code correct, and do not reach for it when Loom style exhaustive checking is affordable on the state space you care about. Verify first that the code under test uses the shuttle wrappers rather than std types directly, since a stray std::sync::Arc inside a Shuttle test escapes the controlled scheduler, and that the iteration count passed to check_random is high enough to clear the detection probability you need.

## FAQ

### What is Shuttle used for in Rust?

It is a library for testing concurrent Rust code. It controls the scheduling of each thread and schedules them randomly, which lets a race condition or other concurrency failure be reproduced deterministically instead of appearing occasionally.

### How do I write a Shuttle test?

Wrap the test body in shuttle::check_random with an iteration count, and replace the concurrency related std imports with shuttle ones, for example use shuttle::sync::{Arc, Mutex} and use shuttle::thread. The assertion inside stays the same as in a normal unit test.

### Does a passing Shuttle test prove my concurrent code is correct?

No. The README states that Shuttle is not sound, so a passing test does not prove the code correct. It randomises schedules rather than exhaustively enumerating them, which is the trade that lets it scale to much larger test cases.

### How does Shuttle differ from Loom?

Shuttle is inspired by Loom but focuses on randomized testing rather than the exhaustive testing Loom offers. The README describes this as a soundness and scalability trade-off, and argues that randomized testing still finds most concurrency bugs because they tend not to be adversarial.

### Which libraries have Shuttle wrappers?

The wrappers cover tokio and its ecosystem, rand, parking_lot, lazy_static, async_stream, dashmap, and the standard library synchronisation primitives and collections, including a deterministic collections wrapper. The workspace manifest lists each wrapper as its own crate.

### What licence does Shuttle use?

Apache-2.0, with the NOTICE and THIRD-PARTY files in the repository covering attribution for bundled code.

## Sources

- [awslabs/shuttle on GitHub](https://github.com/awslabs/shuttle)
- [Issues](https://github.com/awslabs/shuttle/issues)
- [License: Apache-2.0](https://github.com/awslabs/shuttle/blob/main/LICENSE)
- [README](https://github.com/awslabs/shuttle/blob/main/README.md)
- [Releases](https://github.com/awslabs/shuttle/releases)

---

Hysen Labs editorial analysis, written from the project's own repository and release notes. Cite the canonical page: https://hysenlabs.com/projects/awslabs-shuttle
