haz_exec/lib.rs
1//! Async task scheduler and process executor for `haz`.
2//!
3//! The crate currently exposes the following public modules:
4//!
5//! - [`process`]: the [`process::ProcessSpawner`] /
6//! [`process::Process`] trait pair, the
7//! [`std_impl::StdProcessSpawner`] production backend over
8//! [`tokio::process::Command`], and the value types every
9//! implementation produces. A scriptable mock implementation lives
10//! alongside but is gated to test builds only and is not part of
11//! the crate's public surface.
12//! - [`cache_key`]: the [`cache_key::build_cache_key`] derivation,
13//! gluing the workspace state, the validated dependency graph,
14//! the host-env snapshot, and the per-predecessor stream hashes
15//! into a [`haz_cache::CacheKey`].
16//! - [`run_task`]: the single-task lifecycle ([`run_task::run_task`],
17//! [`run_task::cache_lookup_phase`], [`run_task::restore_from_hit`],
18//! [`run_task::run_fresh`]) and the [`run_task::RunObserver`]
19//! port. The [`run_task::RunOutcome`] sum type carries
20//! [`run_task::CompletedRecord`] (a task that reached an
21//! `EXEC-009` classification through the lookup-then-spawn
22//! pipeline), [`run_task::SkipRecord`] (a task the cascade
23//! marked do-not-schedule per `EXEC-010` / `EXEC-011`), and
24//! [`run_task::CancelledRecord`] (a task the cancellation
25//! flow caught per `EXEC-012..015`, in three structural
26//! shapes: signalled in flight, drained from the ready set,
27//! or cascade-cancelled from an upstream cancellation).
28//! - [`run_graph`]: the workspace-wide [`run_graph::run_graph`]
29//! scheduler. It walks a validated [`haz_dag::graph::TaskGraph`]
30//! in canonical order, admits ready tasks subject to the
31//! global and per-tag concurrency caps (`EXEC-004` /
32//! `EXEC-005`), evaluates mutex compatibility post-lookup
33//! against a live hold set (`EXEC-006` condition 3 /
34//! `EXEC-007` / `MUTEX-001..007`), threads predecessor stream
35//! hashes into every downstream [`run_task::run_task`] call,
36//! and cascade-skips hard descendants of a failed task while
37//! letting unrelated subgraphs continue (`EXEC-010`). Per
38//! `EXEC-011` the scheduler emits a
39//! [`run_task::RunOutcome::Skipped`] for every cascade-skipped
40//! descendant and fires
41//! [`run_task::RunObserver::on_task_skipped`] at cascade time.
42//!
43//! Cancellation (`EXEC-009` cancelled state,
44//! `EXEC-012..015`) is wired end-to-end: the spawn-step future
45//! observes [`run_task::RunContext::cancel`] and runs the
46//! per-future SIGTERM / grace / SIGKILL dance against the child's
47//! process group on Unix; the scheduler polls the same token,
48//! stops admitting new tasks, drains [`run_task::RunOutcome`]s
49//! into the `RunCancelled` shape for every task still in the
50//! ready set, and reclassifies late-arriving lookup-step results
51//! as `RunCancelled`. Cancellation cascades along hard edges via
52//! the same `complete_failed` machinery as the failure cascade,
53//! emitting [`run_task::CancelledRecord::UpstreamCancelled`]
54//! per descendant.
55//!
56//! Output presentation (`EXEC-016` / `EXEC-017`) is delivered as
57//! two distinct [`run_task::RunObserver`] implementations under
58//! the [`output`] module:
59//! [`output::LiveOutputObserver`] tag-prefixes each emitted line
60//! with `[project:task] ` and writes lines atomically under a
61//! single per-observer mutex, so multiple in-flight tasks
62//! interleave at line granularity only;
63//! [`output::BufferedOutputObserver`] accumulates each task's
64//! bytes and writes them as two contiguous blocks (`stdout` then
65//! `stderr`) on task completion. Cache-hit emission travels the
66//! same observer methods as a fresh run, satisfying `EXEC-017`
67//! structurally.
68//!
69//! Runtime DAG validation (`EXEC-019`, `EXEC-020`) is wired:
70//! [`run_task::CompletedRecord`] carries each succeeded task's
71//! [`run_task::CompletedRecord::materialised_outputs`] (sourced
72//! from the output-resolution pass for fresh runs and
73//! [`haz_cache::Manifest::outputs`] for cache hits); the
74//! [`run_graph::run_graph`] scheduler maintains a per-run claim
75//! map for `EXEC-020` and a kind-erased augmented edge set for
76//! `EXEC-019`. Detected violations are appended to
77//! [`run_graph::RunGraphOutcome::invariant_violations`] as
78//! [`run_graph::RuntimeInvariantViolation`] entries
79//! ([`run_graph::RuntimeInvariantViolation::OutputOverlap`] and
80//! [`run_graph::RuntimeInvariantViolation::RuntimeCycle`]
81//! variants). `EXEC-019` additionally trips an
82//! internal child token (a child of [`run_task::RunContext::cancel`])
83//! so in-flight non-cycle tasks receive SIGTERM via the
84//! `EXEC-014` grace-and-escalate flow; the user-supplied parent
85//! token stays
86//! uncancelled. Pending cycle members in the ready set surface
87//! as [`run_task::RunOutcome::Skipped`] with
88//! [`run_task::SkipCause::RuntimeCycle`]. `EXEC-020` only stops
89//! admission and lets in-flight tasks complete naturally per the
90//! spec's silence on cancellation for output overlaps.
91//!
92//! Exit-code mapping (`EXEC-021`) is delivered by the
93//! [`exit_code`] module: [`exit_code::exit_code_for`] takes a
94//! finished [`run_graph::RunGraphOutcome`] plus an optional
95//! [`exit_code::CancellationSignal`] (recorded by the binary's
96//! OS signal handler) and returns the numeric exit status the
97//! `haz run` process MUST report. The helper computes the
98//! number; the binary that consumes haz-exec performs the
99//! actual `process::exit` call (per the workspace-wide "pure
100//! library" decision: signal handlers and stdio writes live at
101//! the binary boundary, not inside any haz-exec module).
102
103#![deny(missing_docs)]
104
105pub mod cache_key;
106pub mod exit_code;
107pub(crate) mod hold_set;
108#[cfg(any(test, feature = "test-util"))]
109pub mod mock_impl;
110pub mod output;
111pub(crate) mod pattern_walk;
112pub mod presenter;
113pub mod process;
114pub mod run_graph;
115pub mod run_task;
116pub mod std_impl;