curfew

curfew is a deadline-aware fork-join helper for Rust. It lets you spin up work across
multiple threads, set an absolute deadline (std::time::Instant), and collect only the
results that arrive on time. Late workers are skipped without attempting to cancel them,
so real-time sections of your application can keep moving.
Installation
Add the crate to your Cargo manifest:
[dependencies]
curfew = "0.0.5"
Enable the optional tokio feature to work with async tasks powered by Tokio.
Usage
Joining a group of tasks before a deadline
use curfew::{DeadlineJoiner, TaskOutcome};
use std::thread;
use std::time::{Duration, Instant};
fn main() {
let deadline = Instant::now() + Duration::from_millis(200);
let mut joiner = DeadlineJoiner::new(deadline);
for job in 0..4 {
joiner.spawn(move || {
thread::sleep(Duration::from_millis(40 * job));
job * 10
});
}
let summary = joiner.join_ready();
println!("completed before deadline: {}", summary.completed_count());
println!("missed deadline: {}", summary.timed_out_count());
for task in summary.completed {
match task.outcome {
TaskOutcome::Completed(value) => println!("task {} => {value}", task.task_id),
TaskOutcome::Panicked(info) => println!("task {} panicked: {}", task.task_id, info),
}
}
}
Joining an individual handle with a deadline
use curfew::{join_with_deadline, JoinOutcome, TaskOutcome};
use std::thread;
use std::time::{Duration, Instant};
fn main() {
let handle = thread::spawn(|| {
thread::sleep(Duration::from_millis(150));
99
});
match join_with_deadline(handle, Instant::now() + Duration::from_millis(80)) {
JoinOutcome::Completed(result) => match result.outcome {
TaskOutcome::Completed(value) => println!("finished on time: {value}"),
TaskOutcome::Panicked(info) => println!("task panicked: {info}"),
},
JoinOutcome::TimedOut(late) => {
println!("missed deadline; waiting asynchronously for the late result");
let result = late.wait();
println!("late completion: {:?}", result.outcome);
}
}
}
Async tasks with Tokio (tokio feature)
use curfew::{
join_with_deadline_async, DeadlineJoinerAsync, JoinOutcomeAsync, TaskOutcome,
};
use std::time::{Duration, Instant};
#[tokio::main(flavor = "current_thread")]
async fn main() {
let deadline = Instant::now() + Duration::from_millis(120);
let mut joiner = DeadlineJoinerAsync::new(deadline);
for value in 0..3u32 {
joiner.spawn(async move {
tokio::time::sleep(Duration::from_millis(u64::from(30 * (value + 1))))
.await;
value
});
}
let summary = joiner.join_ready().await;
println!("async tasks finished on time: {}", summary.completed_count());
let future = async {
tokio::time::sleep(Duration::from_millis(200)).await;
99
};
match join_with_deadline_async(future, Instant::now() + Duration::from_millis(60)).await {
JoinOutcomeAsync::Completed(result) => match result.outcome {
TaskOutcome::Completed(value) => println!("completed on time: {value}"),
TaskOutcome::Panicked(info) => println!("task panicked: {info}"),
},
JoinOutcomeAsync::TimedOut(late) => {
println!("future missed the deadline; waiting for late completion");
let result = late.wait().await;
println!("late outcome: {:?}", result.outcome);
}
}
}
API Highlights
DeadlineJoiner spawns tasks, tracks their start/finish instants, and only returns
results that beat the deadline.
join_with_deadline wraps a single JoinHandle, returning a LateTask so callers can
optionally await the late result later.
- Enable the
tokio feature for async siblings DeadlineJoinerAsync and
join_with_deadline_async with the same semantics.
- Rich metadata via
TaskResult, TaskOutcome, and TimedOutTask for diagnostics and
logging.
Limitations
- Tasks are never forcefully terminated. Anything that misses the deadline continues
running in the background until it completes naturally.
- Deadlines rely on the monotonic
Instant clock. System clock adjustments do not
influence scheduling, but very long sleeps may still drift.
Examples
Run the bundled examples with cargo run --example basic or see examples/ for more
scenarios (enable --features tokio for async_example).
Development
See _dev/PRD.md for the full product specification and
CONTRIBUTING.md for contribution guidelines.
License
This project is licensed under MIT. See LICENSE for details.