curfew 0.0.5

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.
Documentation
use crate::outcome::{PanicInfo, TaskId, TaskOutcome, TaskResult};
use crossbeam_channel::{bounded, Receiver, RecvTimeoutError};
use std::thread;
use std::thread::JoinHandle;
use std::time::Instant;

const SINGLE_TASK_ID: TaskId = TaskId::MAX;

/// Attempts to join a thread before a deadline, returning late results via [`LateTask`].
pub fn join_with_deadline<T: Send + 'static>(
    handle: JoinHandle<T>,
    deadline: Instant,
) -> JoinOutcome<T> {
    let (sender, receiver) = bounded(1);
    thread::spawn(move || {
        let finished_at;
        let outcome = match handle.join() {
            Ok(value) => {
                finished_at = Instant::now();
                TaskOutcome::Completed(value)
            }
            Err(payload) => {
                finished_at = Instant::now();
                TaskOutcome::Panicked(PanicInfo::from_payload(payload))
            }
        };

        let _ = sender.send(TaskResult {
            task_id: SINGLE_TASK_ID,
            started_at: None,
            finished_at,
            outcome,
        });
    });

    match receiver.recv_deadline(deadline) {
        Ok(result) => JoinOutcome::Completed(result),
        Err(RecvTimeoutError::Timeout) => {
            JoinOutcome::TimedOut(LateTask { receiver })
        }
        Err(RecvTimeoutError::Disconnected) => {
            // The worker thread panicked before sending or the sender was dropped; treat as completion.
            match receiver.recv() {
                Ok(result) => JoinOutcome::Completed(result),
                Err(_) => JoinOutcome::TimedOut(LateTask { receiver }),
            }
        }
    }
}

/// Result wrapper returned by [`join_with_deadline`].
#[derive(Debug)]
pub enum JoinOutcome<T> {
    /// The task completed before the deadline.
    Completed(TaskResult<T>),
    /// The task was still running when the deadline expired.
    TimedOut(LateTask<T>),
}

/// Handle that allows recovering the result of a task that exceeded the deadline.
#[derive(Debug)]
pub struct LateTask<T> {
    receiver: Receiver<TaskResult<T>>,
}

impl<T> LateTask<T> {
    /// Blocks until the background thread finishes and returns its result.
    pub fn wait(self) -> TaskResult<T> {
        self.receiver
            .recv()
            .expect("background worker dropped before sending result")
    }
}