CryptoPatrick / agents
Portrait of curfew, The Timekeeper #9 of 26

Agents/Concurrency & state

Deadline fork-join Beta v0.0.5

curfew

The Timekeeper

Runs work in parallel and keeps only what finishes before the deadline.

9Downloads, all time
9Last 90 days
1Releases since Oct 2026

Downloads per day

Last 90 days · 9 total · peak 8

Dossier

What curfew does.

Spawns tasks across threads, joins at an absolute deadline, and collects only the results that arrived in time.

From the crate: “A deadline-aware fork-join model where multiple tasks run in parallel, but only those that finish before a specified absolute clock time are joined and their results collected.”

Codename
The Timekeeper
Role
Deadline fork-join
Character
Strict about time and unsentimental about stragglers. When the clock strikes, it leaves without them.
Domain
Concurrency & state
Enlisted
8 October 2026
Last release
v0.0.5, 8 October 2026
Keywords
concurrency, deadline, fork-join, real-time

Use cases

Put it to work.

Getting started

Call it in.

Add it to a Rust project, then follow the example. The full API is on docs.rs.

Install

cargo add curfew

Example

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 || {
            // staggered work with different run times
            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),
        }
    }
}

More

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);
        }
    }
}

More

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);
        }
    }
}

Build with a prompt

Add the Rust crate `curfew` (version 0.0.5) to this project with `cargo add curfew`. I want to use it for this: Spawns tasks across threads, joins at an absolute deadline, and collects only the results that arrived in time. Before writing code, read its README at https://crates.io/crates/curfew and the API docs at https://docs.rs/curfew. Use only the functions, types and signatures that appear there; do not invent API names. Write a minimal working example and one test that runs it.

Paste this into Claude Code or another coding agent.

Specs

Version
0.0.5 (pre-0.1, API may change)
License
MIT
Edition
Rust 2021
Size
439 lines of code, 1 of comments
Package
12.3 KB compressed
Dependencies
crossbeam-channel, futures, log, tokio
Used by
0 crates on crates.io
Features
tokio
Docs
docs.rs, built
Repository
cryptopatrick/curfew · 0★ · 0 forks · 0 open issues · pushed 17 Oct 2025

Release history

VersionDateDownloads
0.0.58 Oct 20269

Documentation

The manual.

The crate's README as published on crates.io. The full API reference is on docs.rs.

Read the README

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 || {
            // staggered work with different run times
            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.

Resources

Same unit

Other agents.