qubit-tokio-executor 0.7.0

Tokio-backed executor and executor service implementations for Qubit Rust libraries
Documentation
# Qubit Tokio Executor

[![Rust CI](https://github.com/qubit-ltd/rs-tokio-executor/actions/workflows/ci.yml/badge.svg)](https://github.com/qubit-ltd/rs-tokio-executor/actions/workflows/ci.yml)
[![Coverage](https://img.shields.io/endpoint?url=https://qubit-ltd.github.io/rs-tokio-executor/coverage-badge.json)](https://qubit-ltd.github.io/rs-tokio-executor/coverage/)
[![Crates.io](https://img.shields.io/crates/v/qubit-tokio-executor.svg?color=blue)](https://crates.io/crates/qubit-tokio-executor)
[![Rust](https://img.shields.io/badge/rust-1.94+-blue.svg?logo=rust)](https://www.rust-lang.org)
[![License](https://img.shields.io/badge/license-Apache%202.0-blue.svg)](LICENSE)
[![中文文档](https://img.shields.io/badge/文档-中文版-blue.svg)](README.zh_CN.md)

Tokio-backed executor services for Rust.

## Overview

Qubit Tokio Executor adapts the Qubit executor abstractions to Tokio. It provides
executor-service semantics for blocking functions submitted through
`tokio::task::spawn_blocking`, and a separate service for async futures submitted
through `tokio::spawn`.

The crate does not create or own a Tokio runtime. Calls that spawn Tokio work
must be made from inside an existing Tokio runtime configured by the
application.

## Features

- `TokioExecutor` for strategy-level Tokio blocking execution.
- `TokioExecutorService` for managed blocking work backed by `spawn_blocking`.
- `TokioBlockingExecutorService` alias for naming the Tokio blocking domain explicitly.
- `TokioIoExecutorService` for async `Future` work backed by `tokio::spawn`.
- `TokioBlockingTaskHandle` for tracked blocking tasks with pre-start cancellation.
- `TokioTaskHandle` for async IO tasks with Tokio abort-based cancellation.
- Shared `ExecutorService`, `SubmissionError`, `StopReport`, and `CancelResult` re-exports for convenient imports.

## Runtime Requirement

This crate assumes a Tokio runtime already exists. In applications, enable the
Tokio runtime features you need in `Cargo.toml`:

```toml
[dependencies]
qubit-tokio-executor = "0.7"
tokio = { version = "1.52", features = ["macros", "rt-multi-thread", "time"] }
```

If a method internally uses `tokio::spawn` or `tokio::task::spawn_blocking`, it
must be called while a Tokio runtime is entered. Calling it without a runtime is
rejected with `SubmissionError::WorkerSpawnFailed`.

## Blocking vs IO Tasks

Use `TokioExecutorService` or `TokioBlockingExecutorService` for synchronous
functions that may block an OS thread. These tasks run through Tokio's blocking
pool and should not be used for async IO futures.

Use `TokioIoExecutorService` for non-blocking futures. These tasks run on
Tokio's async scheduler and should not perform long blocking operations inside
the future body.

## Shutdown and Cancellation

A successful `submit` or `spawn` means only that the service accepted the task.
Blocking callable submissions report results through the shared `TaskHandle`;
tracked blocking submissions return `TokioBlockingTaskHandle`, which combines
the shared tracked-task state with Tokio's abort handle for queued blocking
tasks. Async IO submissions use `TokioTaskHandle` because they wrap Tokio
`JoinHandle`s directly.

`shutdown` rejects new tasks and lets accepted tasks finish. `stop`
rejects new tasks and requests cancellation or abort for tracked Tokio tasks.
Async IO task cancellation sends a best-effort Tokio abort request;
`CancelResult::Cancelled` means the request was sent, and the final outcome is
the result produced by awaiting the returned `TokioTaskHandle`. Blocking tasks
submitted through Tokio can be cancelled only before their blocking closure
starts. Queued tracked blocking tasks are removed from service accounting
immediately after successful cancellation; already running blocking code cannot
be forcibly stopped by Rust, and service termination waits for that code to
return.

For `TokioExecutorService`, `StopReport.cancelled` counts blocking tasks that
were actually cancelled while still queued. Running blocking tasks are reported
through `StopReport.running` and are not counted as cancelled, even though
`stop` requests abort for their Tokio handles. For `TokioIoExecutorService`,
`StopReport.cancelled` counts active async tasks for which a Tokio abort request
was sent.

`TokioExecutor` returns the standard `TrackedTask`. Cancelling that handle can
prevent the user callable from running if it wins before the task starts, but it
does not remove the already submitted Tokio `spawn_blocking` wrapper from
Tokio's blocking queue. Use tracked submissions through `TokioExecutorService`
when queued Tokio blocking work must be aborted directly.

`TokioExecutorService` exposes both blocking `wait_termination` and async
`await_termination` service-level waiting. `TokioIoExecutorService` intentionally
does not expose service-level async waiting; await the task handles returned by
`spawn` when the caller needs to observe async task completion.

## Quick Start

### Tokio blocking work

```rust
use std::io;

use qubit_tokio_executor::{ExecutorService, TokioExecutorService};

#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
    let service = TokioExecutorService::new();
    let handle = service.submit_callable(|| Ok::<usize, io::Error>(40 + 2))?;
    assert_eq!(handle.await?, 42);
    service.shutdown();
    service.await_termination().await;

    Ok(())
}
```

### Async IO futures

```rust
use std::io;

use qubit_tokio_executor::TokioIoExecutorService;

#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
    let service = TokioIoExecutorService::new();
    let handle = service.spawn(async { Ok::<usize, io::Error>(6 * 7) })?;
    assert_eq!(handle.await?, 42);
    service.shutdown();
    assert!(service.is_terminated());

    Ok(())
}
```

## Choosing an Executor

Use `qubit-tokio-executor` when your application is already Tokio-based and you
need execution services that integrate with Tokio scheduling. Use
`qubit-thread-pool` for runtime-independent OS-thread execution, and use
`qubit-rayon-executor` for CPU-bound Rayon work.

For application-level wiring across blocking, CPU-bound, Tokio blocking, and
async IO domains, use `qubit-execution-services`.

## Testing

A minimal local run:

```bash
cargo test
cargo clippy --all-targets --all-features -- -D warnings
```

To mirror what continuous integration enforces, run the repository scripts from
the project root: `./align-ci.sh` brings local tooling and configuration in line
with CI, then `./ci-check.sh` runs the same checks the pipeline uses. For test
coverage, use `./coverage.sh` to generate or open reports.

## Contributing

Issues and pull requests are welcome.

- Open an issue for bug reports, design questions, or larger feature proposals when it helps align on direction.
- Keep pull requests scoped to one behavior change, fix, or documentation update when practical.
- Before submitting, run `./align-ci.sh` and then `./ci-check.sh` so your branch matches CI rules and passes the same checks as the pipeline.
- Add or update tests when you change runtime behavior, and update this README or public rustdoc when user-visible API behavior changes.
- If you change runtime, shutdown, or cancellation behavior, cover both blocking and async IO services when applicable.

By contributing, you agree to license your contributions under the [Apache License, Version 2.0](LICENSE), the same license as this project.

## License

Copyright (c) 2026. Haixing Hu.

This project is licensed under the [Apache License, Version 2.0](LICENSE). See the `LICENSE` file in the repository for the full text.

## Author

**Haixing Hu** — Qubit Co. Ltd.

| | |
| --- | --- |
| **Repository** | [github.com/qubit-ltd/rs-tokio-executor]https://github.com/qubit-ltd/rs-tokio-executor |
| **Documentation** | [docs.rs/qubit-tokio-executor]https://docs.rs/qubit-tokio-executor |
| **Crate** | [crates.io/crates/qubit-tokio-executor]https://crates.io/crates/qubit-tokio-executor |