Skip to main content

Crate tokio_cancel_guard

Crate tokio_cancel_guard 

Source
Expand description

Explicit cancellation-safety primitives for the Tokio async runtime.

When a future loses a tokio::select! race, Tokio drops it immediately — at any internal .await point, without giving the future a chance to clean up. This is correct-by-spec but dangerous in practice: staged database writes get abandoned, locks are left held, and the drop happens without any trace in your telemetry.

This crate gives you three focused tools to make select-driven code safe:

PrimitiveWhat it does
DeferGuardRuns a synchronous rollback closure if the future is dropped mid-execution
ShieldGuarantees the future runs to completion even if select! drops it
[CancelTrack]Emits a tracing error event at the exact drop site (requires tracing feature)

§Quick start

§DeferGuard — rollback on cancellation

use tokio_cancel_guard::DeferGuard;
use std::sync::{Arc, Mutex};

let dirty = Arc::new(Mutex::new(false));
let dirty_clone = dirty.clone();

let write_op = async move {
    *dirty_clone.lock().unwrap() = true;  // stage
    tokio::time::sleep(std::time::Duration::from_millis(50)).await;
    *dirty_clone.lock().unwrap() = false; // commit
};

let rollback = {
    let dirty = dirty.clone();
    move || { *dirty.lock().unwrap() = false; }
};

let guarded = DeferGuard::new(write_op, rollback);

tokio::select! {
    _ = guarded => {}
    _ = tokio::time::sleep(std::time::Duration::from_millis(10)) => {
        // timeout wins — rollback fires automatically
    }
}

§Shield — guaranteed completion

use tokio_cancel_guard::Shield;

let critical = async {
    tokio::time::sleep(std::time::Duration::from_millis(50)).await;
    // this always runs — even if select! drops the Shield handle
};

tokio::select! {
    _ = Shield::new(critical) => {}
    _ = tokio::time::sleep(std::time::Duration::from_millis(10)) => {
        // Shield migrates the future to a background task and lets it finish
    }
}

§CancelTrack — observability (feature tracing)

use tokio_cancel_guard::CancelTrack;

let op = async {
    tokio::time::sleep(std::time::Duration::from_millis(50)).await;
};

let tracked = CancelTrack::new(op, "my_write_task");
// On cancellation, emits a tracing::error! with task name, span ID,
// and OpenTelemetry-compatible otel.status_code / otel.status_description fields.

§Feature flags

FlagEnables
tracing[CancelTrack] — cancellation telemetry via the tracing crate

§Testing utilities

The test_utils module ships two helpers for writing deterministic cancellation tests in your own crate:

Re-exports§

pub use defer_guard::DeferGuard;
pub use shield::Shield;

Modules§

defer_guard
shield
test_utils
Testing utilities for cancellation-safety verification.