pausable-tokio 1.52.1

A fork of tokio that adds a runtime-controllable pause/resume primitive on its clock. Drop-in replacement for tokio with a few extra methods on the Builder and Runtime types.
Documentation

pausable-tokio

A fork of tokio that adds a runtime- controllable pause/resume primitive on top of its clock.

When the runtime is built with Builder::pausable_time, its notion of time is backed by pausable_clock and can be paused or resumed at any moment by calling methods on the Runtime. Sleeps, intervals, timeouts, and anything else that uses tokio's clock will not advance while the runtime is paused.

The rest of the tokio API is unchanged, so pausable-tokio is a drop-in replacement: most code only needs a one-line Cargo.toml change.

Crates.io Docs.rs MIT licensed

When to use this

  • Building a simulator or game loop where you want the entire async system to halt when the user pauses, without threading "is paused" through every async function.
  • Snapshot-and-resume of a long-running async system.
  • Diagnostic tooling that needs to freeze a running runtime to take a consistent measurement.
  • Production code that needs real pause/resume rather than the test-only tokio::time::pause (which requires test-util and the current-thread runtime).

Quick start

Add to your Cargo.toml. Renaming the dependency to tokio lets you keep using tokio::... paths everywhere; the rest of your code does not need to change.

[dependencies]
tokio = { package = "pausable-tokio", version = "1.52.1", features = ["full"] }

Then enable pausable time on the runtime builder, and pause/resume whenever you like:

use std::sync::Arc;
use std::thread;
use std::time::Duration;
use tokio::runtime::Builder;

fn main() {
    let rt = Arc::new(
        Builder::new_multi_thread()
            .enable_all()
            // start unpaused, no preset elapsed time
            .pausable_time(false, Duration::from_secs(0))
            .build()
            .unwrap(),
    );

    // From an outside thread, pause the runtime after 100ms of real
    // time and resume it 200ms later.
    {
        let rt = rt.clone();
        thread::spawn(move || {
            thread::sleep(Duration::from_millis(100));
            rt.pause();
            thread::sleep(Duration::from_millis(200));
            rt.resume();
        });
    }

    rt.block_on(async {
        // Sleeps don't advance while the runtime is paused, so even
        // though we asked for 50ms here, the *real* wall-clock time
        // this takes is closer to 250ms.
        let start = std::time::Instant::now();
        tokio::time::sleep(Duration::from_millis(50)).await;
        println!("real elapsed: {:?}", start.elapsed());
    });
}

API additions

pausable-tokio adds a few methods to Builder and Runtime. Everything else is identical to upstream tokio.

Method What it does
Builder::pausable_time(start_paused, elapsed_time) Enable the pausable clock when constructing the runtime. elapsed_time is a starting offset for Runtime::elapsed_millis.
Runtime::pause() Pause the clock. Returns true if it actually flipped the state.
Runtime::resume() Resume the clock. Returns true if it actually flipped the state.
Runtime::is_paused() / is_paused_ordered(Ordering) Query whether the clock is paused.
Runtime::wait_for_pause() / wait_for_resume() Block the calling thread until the clock changes state.
Runtime::run_unpausable(f) Run a closure while preventing the clock from being paused (used internally to wrap every task poll).
Runtime::run_unresumable(f) Mirror of the above for the resumed-state side.
Runtime::run_if_paused(f) / run_if_resumed(f) Atomic conditional run.
Runtime::now() The runtime's notion of the current Instant.
Runtime::elapsed_millis() Elapsed time on the pausable clock, in milliseconds.

Differences from tokio::time::pause

pausable-tokio upstream tokio::time::pause
Feature gate time (default in full) test-util
Intended use Production Tests
Schedulers supported both current-thread and multi-thread current-thread only
Behavior while paused clock literally stops "auto-advance" jumps time forward to next pending sleep
Driven from any thread, any time only via tokio::time::* API

Source / how this is built

pausable-tokio is generated by applying a small set of patches to upstream tokio at a specific release tag. The patches and the workflow are at https://github.com/RookAndPawn/pausable-tokio; see that repo if you want to update the upstream base or contribute to the fork.

License

MIT, same as upstream tokio.