Crates.io | noworkers |
lib.rs | noworkers |
version | 0.1.0 |
created_at | 2025-07-01 13:46:59.27657+00 |
updated_at | 2025-08-08 20:45:52.277624+00 |
description | A small asyncronous worker pool manages thread pool limiting, cancellation and error propogation, inspired by golangs errgroup (requires tokio) |
homepage | |
repository | https://git.kjuulh.io/kjuulh/noworkers |
max_upload_size | |
id | 1733237 |
size | 86,838 |
A small, ergonomic Rust crate for spawning and supervising groups of asynchronous “workers” on Tokio.
Manage concurrent tasks with optional limits, cancellation, and first-error propagation.
Inpired by golang (errgroups)
The library is still new, and as such the API is subject to change, I don't expect changes to the add and wait functions, but the rest may change. I might also move to custom error types, and or removing the tokio_utils entirely to slim down the package. It shouldn't affect the user too much however.
The crate is in production, and has seen extensive use
with_limit(usize)
.tokio_util::sync::CancellationToken
]—either external (with_cancel
) or task-driven (with_cancel_task
)..wait()
awaits all workers, cancels any in-flight tasks, and returns the first error (if any).Add to your Cargo.toml
:
[dependencies]
noworkers = "0.1"
Then in your code:
use noworkers::Workers;
use noworkers::Workers;
#[tokio::main]
async fn main() -> anyhow::Result<()> {
// Create a worker group with up to 5 concurrent tasks
let mut workers = Workers::new();
// Limit amount of concurrent workers
workers.with_limit(5);
// Adds cancellation signal
workers.with_cancel_task(async move {
// send cancellation to tasks after 60 seconds
tokio::time::sleep(std::time::Duration::from_secs(60)).await
});
// Spawn 10 async jobs
for i in 0..10 {
// Work is done immediatley, so this will wait in two batches of 1 seconds each (because of limit)
workers.add(move |cancel_token| async move {
// optional tokio::select, if you use cancellation for your tasks, if not just do your work
tokio::select! {
// Do work, in this case just sleep
_ = tokio::time::sleep(tokio::time::Duration::from_secs(1)) => {
println!("Job {i} done");
Ok(())
}
// If we receive cancel we close
_ = cancel_token.cancelled() => {
println!("Job {i} cancelled");
Ok(())
}
}
}).await?;
}
// Wait for all to finish or for the first error
workers.wait().await?;
Ok(())
}
Workers::new() -> Workers
Create a fresh worker group.
with_limit(limit: usize) -> &mut Self
Bound in-flight tasks to limit
, back-pressuring .add()
calls when full.
with_cancel(token: &CancellationToken) -> &mut Self
Tie this group’s lifetime to an external token.
with_cancel_task(fut: impl Future<Output=()>) -> &mut Self
Spawn a task that, when it completes, cancels the group.
add<F, Fut>(&self, f: F) -> anyhow::Result<()>
Spawn a new worker. f
is a closure taking a child CancellationToken
and returning Future<Output=anyhow::Result<()>>
.
wait(self) -> anyhow::Result<()>
Await all workers. Returns the first error (if any), after cancelling in-flight workers.
Err(_)
wins: its error is sent on a oneshot and all others are cancelled.Dual-licensed under MIT or Apache-2.0. See LICENSE-MIT and LICENSE-APACHE for details.
Simply create an issue here or pr https://github.com/kjuulh/noworkers.git, development happens publicly at: https://git.kjuulh.io/kjuulh/noworkers.