Provides two APIs for easily cancelling futures, with the option to fallback to a timeout cancellation

Overview

Maintenance MIT licensed Released API docs

tokio-context

Provides two different methods for cancelling futures with a provided handle for cancelling all related futures, with a fallback timeout mechanism. This is accomplished either with the Context API, or with the TaskController API depending on a users needs.

Context

Provides Golang like Context functionality. A Context in this respect is an object that is passed around, primarily to async functions, that is used to determine if long running asynchronous tasks should continue to run, or terminate.

You build a new Context by calling its new constructor, which returns the new Context along with a Handle. The Handle can either have its cancel method called, or it can simply be dropped to cancel the context.

Please note that dropping the Handle will cancel the context.

If you would like to create a Context that automatically cancels after a given duration has passed, use the with_timeout constructor. Using this constructor will still give you a handle that can be used to immediately cancel the context as well.

Examples

return, _ = task_that_takes_too_long() => panic!("should never have gotten here"), } } ">
use tokio::time;
use tokio_context::context::Context;
use std::time::Duration;

async fn task_that_takes_too_long() {
    time::sleep(time::Duration::from_secs(60)).await;
    println!("done");
}

#[tokio::main]
async fn main() {
    // We've decided that we want a long running asynchronous task to last for a maximum of 1
    // second.
    let (mut ctx, _handle) = Context::with_timeout(Duration::from_secs(1));

    tokio::select! {
        _ = ctx.done() => return,
        _ = task_that_takes_too_long() => panic!("should never have gotten here"),
    }
}

While this may look no different than simply using tokio::time::timeout, we have retained a handle that we can use to explicitly cancel the context, and any additionally spawned contexts.

println!("done"), } } #[tokio::main] async fn main() { let (_, mut handle) = Context::new(); let mut join_handles = vec![]; for i in 0..10 { let mut ctx = handle.spawn_ctx(); let handle = task::spawn(async { task_that_takes_too_long(ctx).await }); join_handles.push(handle); } // Will cancel all spawned contexts. handle.cancel(); // Now all join handles should gracefully close. for join in join_handles { join.await.unwrap(); } } ">
use std::time::Duration;
use tokio::time;
use tokio::task;
use tokio_context::context::Context;

async fn task_that_takes_too_long(mut ctx: Context) {
    tokio::select! {
        _ = ctx.done() => println!("cancelled early due to context"),
        _ = time::sleep(time::Duration::from_secs(60)) => println!("done"),
    }
}

#[tokio::main]
async fn main() {
    let (_, mut handle) = Context::new();

    let mut join_handles = vec![];

    for i in 0..10 {
        let mut ctx = handle.spawn_ctx();
        let handle = task::spawn(async { task_that_takes_too_long(ctx).await });
        join_handles.push(handle);
    }

    // Will cancel all spawned contexts.
    handle.cancel();

    // Now all join handles should gracefully close.
    for join in join_handles {
        join.await.unwrap();
    }
}

Contexts may also be chained by using the with_parent constructor in conjunction with RefContexts. Chaining a context means that the context will be cancelled if a parent context is cancelled. A RefContext is simple a wrapper type around an Arc > with an identical API to Context. Here are a few examples to demonstrate how chainable contexts work:

use std::time::Duration;
use tokio::time;
use tokio::task;
use tokio_context::context::RefContext;

#[tokio::test]
async fn cancelling_parent_ctx_cancels_child() {
    // Note that we can't simply drop the handle here or the context will be cancelled.
    let (parent_ctx, parent_handle) = RefContext::new();
    let (mut ctx, _handle) = Context::with_parent(&parent_ctx, None);

    parent_handle.cancel();

    // Cancelling a parent will cancel the child context.
    tokio::select! {
        _ = ctx.done() => assert!(true),
        _ = tokio::time::sleep(Duration::from_millis(15)) => assert!(false),
    }
}

#[tokio::test]
async fn cancelling_child_ctx_doesnt_cancel_parent() {
    // Note that we can't simply drop the handle here or the context will be cancelled.
    let (mut parent_ctx, _parent_handle) = RefContext::new();
    let (_ctx, handle) = Context::with_parent(&parent_ctx, None);

    handle.cancel();

    // Cancelling a child will not cancel the parent context.
    tokio::select! {
        _ = parent_ctx.done() => assert!(false),
        _ = async {} => assert!(true),
    }
}

#[tokio::test]
async fn parent_timeout_cancels_child() {
    // Note that we can't simply drop the handle here or the context will be cancelled.
    let (parent_ctx, _parent_handle) = RefContext::with_timeout(Duration::from_millis(5));
    let (mut ctx, _handle) =
        Context::with_parent(&parent_ctx, Some(Duration::from_millis(10)));

    tokio::select! {
        _ = ctx.done() => assert!(true),
        _ = tokio::time::sleep(Duration::from_millis(7)) => assert!(false),
    }
}

The Context pattern is useful if your child future needs to know about the cancel signal. This is highly useful in many situations where a child future needs to perform graceful termination.

In instances where graceful termination of child futures is not needed, the API provided by TaskController is much nicer to use. It doesn't pollute children with an extra function argument of the context. It will however perform abrupt future termination, which may not always be desired.

TaskController

Handles spawning tasks which can also be cancelled by calling cancel on the task controller. If a std::time::Duration is supplied using the with_timeout constructor, then any tasks spawned by the TaskController will automatically be cancelled after the supplied duration has elapsed.

This provides a different API from Context for the same end result. It's nicer to use when you don't need child futures to gracefully shutdown. In cases that you do require graceful shutdown of child futures, you will need to pass a Context down, and incorporate the context into normal program flow for the child function so that they can react to it as needed and perform custom asynchronous cleanup logic.

Examples

use std::time::Duration;
use tokio::time;
use tokio_context::task::TaskController;

async fn task_that_takes_too_long() {
    time::sleep(time::Duration::from_secs(60)).await;
    println!("done");
}

#[tokio::main]
async fn main() {
    let mut controller = TaskController::new();

    let mut join_handles = vec![];

    for i in 0..10 {
        let handle = controller.spawn(async { task_that_takes_too_long().await });
        join_handles.push(handle);
    }

    // Will cancel all spawned contexts.
    controller.cancel();

    // Now all join handles should gracefully close.
    for join in join_handles {
        join.await.unwrap();
    }
}

License: MIT

You might also like...
global allocator that provides hooks for tracking allocation events

tracking-allocator A GlobalAlloc-compatible allocator implementation that provides the ability to track allocation events. examples As allocators are

`fugit` provides a comprehensive library of `Duration` and `Instant` for the handling of time in embedded systems, doing all it can at compile time.

fugit fugit provides a comprehensive library of Duration and Instant for the handling of time in embedded systems, doing all it can at compile time. T

cooptex provides deadlock-free Mutexes.

cooptex provides deadlock-free Mutexes. The [CoopMutex::lock] method wraps the [std::sync::Mutex] return value with a Result that will request

Provides a Suricata Eve output for Kafka with Suricate Eve plugin

Suricata Eve Kafka Output Plugin for Suricata 6.0.x This plugin provides a Suricata Eve output for Kafka. Base on suricata-redis-output: https://githu

Provides a wrapper to deserialize clap app using serde.

clap-serde Provides a wrapper to deserialize clap app using serde. API Reference toml const CLAP_TOML: &'static str = r#" name = "app_clap_serde" vers

A (mostly) drop-in replacement for Rust's Result that provides backtrace support

Errant A (mostly) drop-in replacement for Rust's Result that provides backtrace support. Please note that Errant is still very early in development an

This repository provides an emulator for iterated prisoner's dilemma.

Iterated Prisoner's Dilemma Emulator Name This repository provides an emulator for iterated prisoner's dilemma. Description You can run the program by

Memory.lol - a tiny web service that provides historical information about social media accounts

memory.lol Overview This project is a tiny web service that provides historical information about social media accounts. It can currently be used to l

Easy c̵̰͠r̵̛̠ö̴̪s̶̩̒s̵̭̀-t̶̲͝h̶̯̚r̵̺͐e̷̖̽ḁ̴̍d̶̖̔ ȓ̵͙ė̶͎ḟ̴͙e̸̖͛r̶̖͗ë̶̱́ṉ̵̒ĉ̷̥e̷͚̍ s̷̹͌h̷̲̉a̵̭͋r̷̫̊ḭ̵̊n̷̬͂g̵̦̃ f̶̻̊ơ̵̜ṟ̸̈́ R̵̞̋ù̵̺s̷̖̅ţ̸͗!̸̼͋

Rust S̵̓i̸̓n̵̉ I̴n̴f̶e̸r̵n̷a̴l mutability! Howdy, friendly Rust developer! Ever had a value get m̵̯̅ð̶͊v̴̮̾ê̴̼͘d away right under your nose just when

This code generate coinjoin transaction whici has two inputs and two outputs

How to create coinjoin transaction This code generate coinjoin transaction whici has two inputs and two outputs. First, we create two trasactions. The

excss is a small, simple, zero-runtime CSS-in-JS library with just two APIs.

excss excss is a small, simple, zero-runtime CSS-in-JS library with just two APIs.

hy-rs, pronounced high rise, provides a unified and portable to the hypervisor APIs provided by various platforms.

Introduction The hy-rs crate, pronounced as high rise, provides a unified and portable interface to the hypervisor APIs provided by various platforms.

Afterglow-Server provides back-end APIs for the Afterglow workflow engine.

Afterglow-Server 🚧 Note: This project is still heavily in development and is at an early stage. Afterglow-Server provides back-end APIs for the After

Crates.io library that provides high-level APIs for obtaining information on various entertainment media such as books, movies, comic books, anime, manga, and so on.
Crates.io library that provides high-level APIs for obtaining information on various entertainment media such as books, movies, comic books, anime, manga, and so on.

Crates.io library that provides high-level APIs for obtaining information on various entertainment media such as books, movies, comic books, anime, manga, and so on.

Application microframework with command-line option parsing, configuration, error handling, logging, and shell interactions
Application microframework with command-line option parsing, configuration, error handling, logging, and shell interactions

Abscissa is a microframework for building Rust applications (either CLI tools or network/web services), aiming to provide a large number of features w

nothing::[Probably] is a better [Option].
nothing::[Probably] is a better [Option].

nothing::[Probably] is a better [Option].

Option and Either types with variants known at compile time.

Const Either Some types to allow deciding at compile time if an option contains a value or which variant from the either type is active. This might be

Provide CRUD CLI for Moco Activities with Jira Cloud Sync Option for faster time tracking.

Moco CLI Provide CRUD CLI for Moco Activities with Jira Cloud Sync Option for faster time tracking. Available commands Login Jira Must be called befor

Errable is an Option with inverted Try-semantics.

Fallible Fallible is an Option with inverted Try-semantics. What this means is that using the ? operator on a FallibleE will exit early if an error

Comments
  • Build a way to chain contexts

    Build a way to chain contexts

    This provides a means by which we can chain contexts together, such that cancelling a parent context will cancel a child context, but not visa versa. May be chained as many times as you wish.

    This PR also simplifies the API for both Context and TaskController. Now there are separate constructors to construct without vs with a timeout. This allows for simpler construction at call sites.

    opened by PrismaPhonic 0
Owner
Peter Farr
Software Engineer who loves working on backend technologies and distributed systems. Huge lover of Rust.
Peter Farr
Explore the WWW and find the shortest path between two HTML documents

explore Find shortest path between two web resources. About I decided to create this project because some day I started to wonder: In how many clicks

Eryk Andrzejewski 1 Apr 10, 2022
Pool is a befunge inspired, two-dimensional esolang

Pool is a befunge inspired, two-dimensional esolang

null 1 Nov 1, 2021
This repository simulates and renders fluid particles in two dimensions, in Rust.

mlsmpm-particles-rs This repository simulates and renders fluid particles in two dimensions, in Rust. My matching implementation in Go is mlsmpm-parti

null 6 Oct 31, 2022
Insert a new named workspace between two other named workspaces

Insert a new named workspace between two other named workspaces

null 2 Mar 13, 2022
Rust bindings for the WebView2 COM APIs

webview2-rs Rust bindings for the WebView2 COM APIs Crates in this repo The root of this repo defines a virtual workspace in Cargo.toml which includes

Bill Avery 24 Dec 15, 2022
A rust library for interacting with multiple Discord.com-compatible APIs and Gateways at the same time.

Chorus A rust library for interacting with (multiple) Spacebar-compatible APIs and Gateways (at the same time). Explore the docs » Report Bug · Reques

polyphony 4 Apr 30, 2023
A library for working with alpaca.markets APIs, including the Broker API.

alpaca-rs, an unofficial Rust SDK for the Alpaca API (WIP) Features Easy to use - Minimal setup and no boilerplate. Cross-platform - can run on most p

null 4 Dec 2, 2023
TinyUF2 flash unlocker for STM32F411 (easily adaptable to other STM32F4xx)

TinyUF2 flash unlocker for STM32F411 (easily adaptable to other STM32F4xx) This small program is meant to unlock the first 2 (=32kb) flash sectors tha

Stefan Kerkmann 1 Feb 1, 2022
This crate provides a convenient macro that allows you to generate type wrappers that promise to always uphold arbitrary invariants that you specified.

prae This crate provides a convenient macro that allows you to generate type wrappers that promise to always uphold arbitrary invariants that you spec

null 96 Dec 4, 2022
todo-or-die provides procedural macros that act as checked reminders.

todo-or-die provides procedural macros that act as checked reminders.

David Pedersen 552 Dec 24, 2022