NewYour coding agent can read the release notes before it upgrades.Set up the MCP server →
crates.io · #4432 most downloaded on crates.io
A library for testing concurrent Rust code
Last release 6 days ago
01 Oct 2026
Release timing varies
gaps range from 8 days to 10 months
Nearly every release is documented
notes for 25 of 26 stable releases
Nothing withdrawn
no release was ever pulled
6 years old
26 releases · first in 2020
One column per quarter.
Fix a process abort when a Drop handler touches a modelled Mutex , RwLock or semaphore outside a running task. Every BatchSemaphore operation looks up
Fix a process abort when a Drop handler touches a modelled Mutex, RwLock or semaphore outside a running task. Every BatchSemaphore operation looks up the running task's vector clock, and current::clock() panicked when there was no running task, when ExecutionState was already borrowed, or outside a Shuttle execution. The first case is reached whenever ExecutionState::cleanup force-unwinds a task that was still parked when the execution ended, for example after an ordinary test failure. The panic came from a destructor, so the process aborted, and the failure report and persisted schedule were lost with it. current::clock() now returns an empty clock in all three cases: an operation that belongs to no task has no causality to record. (#357)
Fix spans leaking on the thread's entered-span stack while any thread in the process holds a scoped default subscriber, such as one installed with tracing::subscriber::set_default by a concurrently running test. Shuttle exited a task's spans by calling Span::current() inside tracing::dispatcher::get_default, where it returns Span::none() while a scoped default exists anywhere in the process, so it exited nothing but still entered the execution's span. Every scheduling step leaked one entry and every Runner::run another: a span entered before a context switch was no longer current after it, and once the leaked spans had closed, a later Span::current() panicked with tried to clone a span (Id(..)) that already closed. Spans are now exited and entered through Span::with_subscriber. (#359)
A task's default tracing dispatcher is now saved and restored across context switches, like its span stack. The default dispatcher is per OS thread and every task runs on the same one, so a task that yielded inside tracing::dispatcher::with_default left its dispatcher installed while the scheduler and every other task ran: their events went to that dispatcher instead of the test's subscriber, and the task's spans stayed entered, so later events were attributed to the wrong task. If the execution was stopped or panicked while such a task was switched out, the task's default stayed installed after the execution ended too. Shuttle only parks a task's default when it may differ from the execution's, so runs without a scoped default subscriber skip it. (#359)
Deadlock reports now print blocked tasks' backtraces (captured when SHUTTLE_CAPTURE_BACKTRACE is set) with Display, in the numbered layout panics use under RUST_BACKTRACE=1, instead of with {:#?}, which printed one Debug record per frame wrapped in Some(Backtrace [ .. ]). A task without a captured backtrace prints <not captured>. Output without SHUTTLE_CAPTURE_BACKTRACE is unchanged. (#360)
Publish shuttle-engine 0.1.3, which has all of the changes above. shuttle-std and shuttle-schedulers are unchanged at 0.1.2 and 0.1.1; they take shuttle-engine as ^0.1.2 and ^0.1.1, so they build against 0.1.3 as they stand. shuttle's own source is unchanged too; it now requires shuttle-engine 0.1.3, so that upgrading to 0.9.5 brings the fixes with it.
Fix shuttle-parking_lot 's upgradable read locks letting a writer in part-way through an upgrade. RwLockUpgradableReadGuard::upgrade released the perm
Fix shuttle-parking_lot's upgradable read locks letting a writer in part-way through an upgrade. RwLockUpgradableReadGuard::upgrade released the permits it held before taking the rest, so a writer already blocked on the lock was granted it first by the strictly fair semaphore: the value an upgradable reader had just read could change underneath it before its own upgrade completed. Real parking_lot guarantees the opposite — it swaps ONE_READER | UPGRADABLE_BIT for WRITER_BIT in a single atomic step and then waits only for existing readers to drain — and that guarantee is the reason to use an upgradable read at all. The lock is now modelled as permit counts on a single semaphore (shared takes 1, upgradable a strict majority, exclusive all of them), which keeps every transition between the three states atomic. Two further consequences of the old two-semaphore model are fixed along with it: try_upgrade no longer fails spuriously when a writer is merely queued, and downgrade_to_upgradable no longer deadlocks against a task that is waiting to take an upgradable read. (#351)
BatchSemaphore::upgrade now keeps the permits it already holds and acquires only the missing ones, with priority over queued waiters, instead of releasing its permits and re-acquiring the full count from the back of the queue. An upgrade therefore blocks only on tasks that currently hold permits, and cannot be overtaken by a waiter that arrived first. BatchSemaphore::try_upgrade is added as the non-blocking counterpart. (#351)
Fix a process abort when the portfolio runner aborts the remaining executions after finding a counterexample. The drop handlers of the aborted execution then ran in the context of a stopped execution, and any that touched a Shuttle primitive panicked from a drop. A stopped execution now leaks its state on the way out, as a panicking one already did. (#346)
Performance: BatchSemaphore no longer takes a std::sync::Mutex in a release-mode assertion on every Acquire poll, and allocates its Waiter only when an acquire actually blocks. Uncontended synchronization operations (Mutex, RwLock, Semaphore, channels) are 43-50% faster. (#321)
Performance: backtrace_enabled no longer reads the environment on every call. It is called from Task::block and Task::sleep, so on every block and every Poll::Pending, and std::env::var takes a lock on the environment and allocates. Lock-heavy workloads are 9-12% faster. (#322)
Better instrument backtraces for blocked futures. (#215)
Fix the annotation feature. (#334)
Publish shuttle-engine, shuttle-std and shuttle-parking_lot-impl 0.1.2. shuttle-schedulers is unchanged at 0.1.1; it takes shuttle-engine as ^0.1.1, so it builds against 0.1.2 as it stands. shuttle-parking_lot itself stays at 0.12.5, mirroring the parking_lot version it wraps: it requires the impl as ^0.1.0 and re-exports it with a glob, so it picks the RwLock fix up without being republished. The impl now requires shuttle >=0.9.4, since the fix is built on the new BatchSemaphore::upgrade.
Fix BatchSemaphore waking the wrong task when an Acquire future is polled by a task other than the one that created it (the motivating case is an in-f
BatchSemaphore waking the wrong task when an Acquire future is polled by a task other than the one that created it (the motivating case is an in-flight acquire cached inside a longer-lived object, such as a tokio Receiver that is moved between tasks). Waiters left behind by a cancelled Acquire whose task has since finished are now also treated as stale instead of consuming permits or blocking a finished task. (#317)schedule no longer iterates over tasks that have already finished. This does not change scheduling decisions. (#318)shuttle-engine, shuttle-schedulers, shuttle-std and the wrapper crates. Make shuttle-async-stream-impl publishable. (#315)shuttle-engine, shuttle-schedulers and shuttle-std 0.1.1.Add support for 128-bit atomics ( AtomicI128 / AtomicU128 )
AtomicI128/AtomicU128) (#299)Display for TaskId to match tokio's task::Id (#307)shuttle crate into separate internal crates: shuttle-engine (core runtime and the Scheduler trait), shuttle-schedulers (built-in schedulers and check/replay helpers), and shuttle-std (the std replacement primitives) (#286, #290, #292, #294). This is an internal reorganization and does not change the shuttle public API.shuttle-std, shuttle-schedulers and shuttle-engine.Readd README that was lost in refactoring.
Fix: JoinHandle<T> is now Send and Sync even if T is not.
JoinHandle<T> is now Send and Sync even if T is not.vector-clocks feature flag. (#187)Once can now be moved (#188 and #208)std::sync::{LockResult, PoisonError, TryLockError, TryLockResult} are now exported from shuttle::sync (#198)DEBUG (down from INFO) (#211)UniformRandomWalk scheduler added (#200)Config::immediately_return_on_panic is set then we will return immediately on a failure and not finish unwinding the panic. (#202)SHUTTLE_PERSIST_SEED in the RandomScheduler to persist schedule before running the test (to be used for aborting tests) (#201)BatchSemaphore::close_no_scheduling_point (#227)0.8.1 (Jun 19, 2025) Fix bug in BatchSemaphore ( #167 ) Fix bug in RwLock ( #170 ) Add current::reset_step_count ( #175 ) Add spawn_local ( #176 ) Add
block_on now has one less thread switch point, which breaks schedules.
BatchSemaphore (#151)block_on now has one less thread switch point, which breaks schedules. (#155)ReplayScheduler::set_target_clock added (#156)Tasks instead of TaskIds (#156)check_random_with_seed (#161)check_random optionally take a seed by providing the environment variable SHUTTLE_RANDOM_SEED (#161)AnnotationScheduler and annotated schedule support added under feature "annotation" (#163)Implement try_send and iterators for mpsc channels
try_send and iterators for mpsc channels (#120)get_mut for Mutex and RwLock (#120)In the meantime, Tag s are now implemented with a trait. This is a breaking change from 0.6.1.
Add scheduler to check for uncontrolled nondeterminism ( #96 , #97 )
thread::park (#101)sync::Barrier is reused (#102){Mutex, Condvar, RwLock}::new const (#106)FuturesUnordered (#105)futures dependency (#107)This version renames the silence_atomic_ordering_warning configuration option to silence_warnings , as well as the corresponding environment variables
This version renames the silence_atomic_ordering_warning configuration option to silence_warnings, as well as the corresponding environment variables, to enable future warnings to be controlled by the same mechanism.
lazy_static support (#93)Tests that use shuttle::rand will need to update to the v0.8 interface of rand , which included some breaking changes.
This version updates the embedded rand library to v0.8. Tests that use shuttle::rand will need to update to the v0.8 interface of rand, which included some breaking changes.
Make PCT scheduling not linear in max number of tasks
* Dependency updates
Note that clients using async primitives provided by Shuttle (task spawn, block_on, yield_now) will need to be updated due to the renaming of the asyn
Note that clients using async primitives provided by Shuttle (task spawn, block_on, yield_now) will
need to be updated due to the renaming of the asynch module to future in this release.
thread::park and thread::unpark (#77)std::hint (#78)asynch module to future (#79)Note that failing test schedules created by versions of Shuttle before 0.2.0 will not successfully replay on version 0.2.0, and vice versa, as the cha
Note that failing test schedules created by versions of Shuttle before 0.2.0 will not successfully
replay on version 0.2.0, and vice versa, as the changes below affect Mutex and RwLock
scheduling decisions.
Mutex::try_lock (#71)RwLock::{try_read, try_write} (#72)std::sync::Weak (#69)Implement Condvar::wait_while and Condvar::wait_timeout_while
Condvar::wait_while and Condvar::wait_timeout_while (#59)Sized bounds on Mutex and RwLock (#62)Fix a number of issues in support for async tasks (#50, #51, #52, #54)
thread_local! macro) (#43, #53)Once cells (#49)context_switches and my_clock functions into a new current module (#56)Add shuttle::context_switches to get a logical clock for an execution
std::sync::atomic (#33)shuttle::context_switches to get a logical clock for an execution (#37)Fix a performance regression with tracing introduced by #24
tracing introduced by #24 (#31)rand crate to fix compilation issues (#29)Add a timeout option to run tests for a fixed amount of time
tracing log output (#24)thread::current (#23)Add option to bound how many steps a test runs on each iterations
yield_now a hint to the scheduler to allow validating busy loops (#18)ReplayScheduler::new_from_file (#20)Add option to persist schedules to a file
* Initial release
Nothing published for this version
Your coding agent can read these notes before it upgrades. Set up the MCP server →