Expand description
processkit — async child-process management for Rust + tokio: whole-tree
kill-on-drop (no orphaned subprocesses), run-and-capture, streaming,
shell-free pipelines, timeouts & cancellation, and supervision.
Two layers:
ProcessGroup— a kill-on-drop container for a process tree. Every child spawned into the group, and everything those children spawn, dies with the group, so an exiting or panicking owner doesn’t leak subprocesses. Containment is a Windows Job Object, a Linux cgroup v2 (with a POSIX process-group fallback), or a POSIX process group on macOS/BSD — observable viaMechanism. Two caveats theProcessGroup/Mechanismdocs spell out: the guarantee rides onDroprunning (apanic = "abort"process, or aSIGKILL/power-loss of the owner, skips it — the OS-owned Job Object / cgroup still reaps on handle close, the POSIX process-group fallback does not), and on the process-group mechanism a child that callssetsidescapes containment. The whole tree can be signalled (ProcessGroup::signal, seeSignal), paused/resumed (ProcessGroup::suspend/ProcessGroup::resume), and inspected (ProcessGroup::members);wait_anyraces several running processes and reports the first to exit.- runner — async run-and-capture built on the group. Describe a run with
Command, then drive it to completion (Command::output_string,Command::run, …) orstartit for streaming and interactive I/O. TheProcessRunnertrait runs commands to completion and is the mock seam (seeScriptedRunner). ASupervisorkeeps a command alive — restarting it per policy with backoff — whereCommand::retrymerely replays one run to success. Readiness probes (RunningProcess::wait_for_line/wait_for_port/wait_for) wait until a started child is actually ready instead of sleeping. APipeline(Command::pipe) chains commands stdout→stdin without a shell — each stage spawns into its own kill-on-dropProcessGroupsub-group, with chain-wide teardown fanning the kill across every sub-group, pipefail outcome.Command::cancel_onties a run to aCancellationToken: cancelling it kills the tree and every consuming path resolves toError::Cancelled. Spawn-time sandboxing knobs:Command::inherit_env(env allow-list),Command::uid/Command::gid(Unix privilege drop),Command::setsid,Command::create_no_window,Command::priority(CPU-scheduling priority, both platforms),Command::umask(Unix file-creation mask).
Async throughout (tokio). Errors are the structured Error; a non-zero
exit is reported in ProcessResult, not raised, until you call
ProcessResult::ensure_success.
Stability. Since 1.0, processkit follows Semantic Versioning: the
public API is stable, and any breaking change lands only in a new major
version, so 2.x upgrades are backward-compatible (the last breaking release
was 2.1.0). (The lone exception is the mock feature’s mockall-generated
expect_* surface — see below.)
Beyond this page, the repository ships a narrative guide set — a task-oriented cookbook (“I want to …” → snippet), a deep guide per capability, and every per-platform caveat collected in one place.
Run vocabulary — one verb, one meaning, at every layer (Command,
ProcessRunner/ProcessRunnerExt, CliClient):
run— require an accepted exit (0by default, widened byCommand::ok_codes) and return stdout as aString, trailing whitespace trimmed (trim_end: the final newline is noise, but leading whitespace can be significant).run_unit— the same, discarding the output.output_string/output_bytes— return the fullProcessResult(stdout as text / raw bytes); a non-zero exit is not an error here. (output_string, not a bareoutput, sincestd::process::Command::outputyields bytes — the explicit name avoids that footgun and is spelled the same on every layer.)exit_code— the exit code, with a missing code surfaced as an error. (On aProcessResult,codeis the plainOption<i32>accessor —Nonefor a timeout/signal kill, never a-1sentinel.)probe— run a predicate and read its exit code as abool:0→true,1→false, anything else is an error (git diff --quiet, …).parse/try_parse— run to a clean success and feed the captured stdout to a closure:parsefor an infallible closure,try_parsefor one returningResult(the JSON-deserialization shape).Send-contract exception:Command::parse/CliClient::parserequireF: Send(andT: Send), so the returned future isSendand movable intotokio::spawn;Pipeline::parsedeliberately does not requireSend— its closure runs inline on the awaiting task rather than across atokio::spawnboundary, so it accepts strictly more closures, but the resulting future isSendonly whenF/Thappen to be. If you need to move aPipeline::parse/Pipeline::try_parsecall intotokio::spawn, make sure your closure and its output areSendyourself; the compiler won’t require it for you the way it does forCommand/CliClient.
§Features
Every flag is additive and gates visibility only — the kill-on-drop tree guarantee is unconditional in every configuration.
stats— resource measurement:ProcessGroupStats,ProcessGroup::stats(plus thesample_statstime-series sampler), the per-processRunningProcess::cpu_time/peak_memory_bytesdiagnostics, and theRunningProcess::profilerun summary. Opt-in for its specialized purpose (on Windows it calls the systemProcessStatus/PSAPI API — a link to an OS library, not an added crate dependency); enable withfeatures = ["stats"], orlimits, which implies it. (The features that do pull an extra crate aremock→mockall,tracing→tracing, andrecord→serde/serde_json.)process-control(default) — tree control beyond contain+kill:SignalandProcessGroup::{signal, suspend, resume, members, members_info, adopt}, plus the enrichedMemberInfomember snapshot.limits— whole-tree resource caps:ResourceLimits, themax_memory/max_processes/cpu_quotabuilders onProcessGroupOptions, andError::ResourceLimit. Impliesstats.mock— themockall-generatedtesting::MockRunnerfor consumers’ tests. Itsexpect_*surface is generated bymockalland is exempt from this crate’s semver guarantees — it tracks themockallversion (an implementation detail) rather than a frozen API. The first-class doubles (ScriptedRunner/RecordingRunner) are the stable, recommended seam; reach formockonly if you specifically want expectation-style mocking.tracing—tracingevents on theprocesskittarget: spawn and exit (program/pid/mechanism), timeout and cancellation firing, group terminate/shutdown, retry attempts, supervisor restarts and storm pauses, and teardown anomalies (stdin-writer failures, pump overruns). Never logs argv or environment values.record— record/replay cassettes over theProcessRunnerseam:RecordReplayRunnerrecords realInvocation → ProcessResultpairs to a JSON fixture once, then replays them hermetically — no subprocess in CI. Pulls inserde+serde_json.
§Other languages
Not on Rust? processkit-py is a
Python wrapper (PyO3 bindings) over this crate’s core, with an asyncio-facing
API. This crate remains the single source of truth for the containment/runner
logic underneath.
Modules§
- prelude
- Re-exports of small vocabulary types from the crate’s
0.xdependencies, kept out of the crate root souse processkit::*doesn’t pull them in (and so a future0.xmajor bump of either dependency stays contained to this module rather than the whole crate surface). - testing
- Test doubles for the
ProcessRunnerseam: aScriptedRunnerthat serves canned replies, aRecordingRunnerthat asserts on invocations, theInvocationit captures, aDryRunRunnerthat renders and echoes commands without spawning them, and (behind features) record/replay cassettes and amockallmock.
Macros§
- cli_
client - Scaffold a typed CLI-wrapper struct around a
CliClient.
Structs§
- Cancellation
Token - Re-exported so callers can
use processkit::CancellationToken;without a directtokio-utildependency. SeeCommand::cancel_on. A token which can be used to signal a cancellation request to one or more tasks. - CliClient
- Owns a CLI tool’s program name,
ProcessRunner, and default timeout, and builds + runsCommands against them. - Command
- A description of a child process to launch: program, arguments, working directory, environment, stdin source, and an optional timeout.
- Finished
- The outcome of a run driven via
stdout_linesoroutput_events: how the run ended plus the captured standard error. Returned byRunningProcess::finish. - JobRunner
- The default runner: every run gets a fresh, private
ProcessGroupowned by the run, so its tree is torn down when the run finishes (or its handle drops). - Member
Info process-control - An enriched snapshot of one member of a
ProcessGroup— its pid plus best-effort metadata (parent pid, image name, start time). - Output
Buffer Policy - Caps how many captured/streamed output lines are retained in memory.
- Output
Events - A merged
Streamof both stdout and stderr lines (seeRunningProcess::output_events). - Output
Line - One decoded line carried by an
OutputEvent. - Pipeline
- A chain of
Commands connected stdout→stdin — built withCommand::pipe, extended withpipe, driven with the same verb vocabulary as a singleCommand:output_string/output_bytesfor capture,run/run_unit/checkedfor success-checked runs,exit_code/probefor the code, andparse/try_parsefor typed output — each operating on the pipefail outcome. Bound the whole chain withtimeout/cancel_on. - Process
Group - A container that ties the lifetime of a child-process tree to its own.
- Process
Group Options - Tuning for a
ProcessGroup— graceful-shutdown timing and (with thelimitsfeature) resource limits. - Process
Group Stats stats - A snapshot of a process group’s resource usage.
- Process
Result - The captured result of running a process to completion.
- Process
Stdin - An interactive writer to a child’s standard input.
- Resource
Limits limits - Resource limits enforced on a process group as a whole.
- Retry
Policy - How a retryable run is re-attempted: a bounded number of retries with exponential backoff, a per-delay cap, and optional full jitter.
- RunProfile
stats - Resource summary of one finished run — produced by
RunningProcess::profile. - Running
Process - A handle to a process spawned by a runner.
- Stats
Sampler stats - A periodic
ProcessGroupStatsseries — created byProcessGroup::sample_stats. - Stdin
- What to feed a child process on standard input.
- Stdout
Lines - A
Streamof the child’s standard-output lines (seeRunningProcess::stdout_lines). - Supervision
Outcome - What a finished supervision reports — the last run plus the keeper’s telemetry.
- Supervisor
- Keeps a
Commandalive: runs it, classifies every exit against theRestartPolicyand thestop_whenpredicate, and restarts it after an exponential-backoff delay until supervision ends.
Enums§
- Error
- Errors produced when launching or running a child process.
- Give
UpAttempt - What the
give_up_whenclassifier inspects: a crashed run that produced aProcessResult, or a spawn/IO failure that prevented the child from ever starting (e.g.ENOENTfor a mistyped program name) and so never produced one. - Limit
Kind limits - Which
ResourceLimitsfield anError::ResourceLimitfailure is about —Memoryformax_memory,Processesformax_processes,Cpuforcpu_quota. - Limit
Reason limits - Why a requested resource limit could not be applied — the classification an
Error::ResourceLimitfailure carries so a caller (e.g. theprocesskit-pybinding) can branch on the kind of failure without parsing the Englishdetailtext. - Line
Terminator - How the output pump decides where one captured/streamed line ends.
- Mechanism
- The containment mechanism actually in effect for a process group.
- Outcome
- How a run ended — the explicit form of the
code()/timed_out()pair. - Output
Event - An event produced by a child process: a decoded line from stdout or stderr.
- Overflow
Mode - What to drop when a bounded output buffer is full.
- Parent
Death Cleanup - The reach of the parent-death hardening
(
Command::kill_on_parent_death) when the owning process dies abruptly — aSIGKILLof the owner or a crash, whereDropnever runs to tear the containment group down. - Priority
- A portable CPU-scheduling priority for a child process (see
Command::priority), mapped onto the native primitive at spawn time: Unixsetpriority/nice(applied through the samepre_execseam asuid/gid), Windows priority class (OR’d intocreation_flags, the same seam ascreate_no_window). - Restart
Policy - When the supervisor restarts an exited child. See each variant; in every
case
stop_whenandmax_restartscan end supervision first. - Signal
process-control - A signal to broadcast to every process in a
ProcessGroupviasignal. - Stdio
Mode - How a child process’s standard output or error stream is connected.
- Stop
Reason - Why supervision ended.
Traits§
- Into
Command - What a
CliClientverb accepts: either an argument list — built into aCommandfor the client’s program with its defaults (timeout, env, cancellation) applied — or a ready-madeCommand, run as-is. - Process
Runner - Runs a
Command— to a captured result (output_string/output_bytes) or a live handle (start). - Process
Runner Ext - Convenience methods available on every
ProcessRunner(including&dyn ProcessRunner), layered overoutput_string.
Functions§
- output_
all - Run every command in
commands, keeping at mostconcurrencyof them live at once, and collect all their results in input order. - output_
all_ bytes - The raw-bytes companion to
output_all: captures each command’s stdout asVec<u8>instead of decoded text. All other semantics are identical — seeoutput_all. - output_
string - Run
programwithargsinside a private job and capture the result without erroring on a non-zero exit — for commands whose exit code is meaningful. - run
- Run
programwithargsinside a private job and return trimmed stdout, or anErroron a non-zero exit / spawn failure / timeout. A thin shim overCommand; use the builder for a working directory, env, stdin, timeout, or the full verb vocabulary. - wait_
all - Wait for all of several running processes to exit, returning their
Outcomes in the same order asprocesses. The processes are only borrowed and stay usable afterwards (the exit status tokio caches remains re-readable). - wait_
any - Wait for whichever of several running processes exits first, returning
its index in
processesand itsOutcome(matchingRunningProcess::wait). - which
- Resolve
programto a concrete executable path without launching it — a spawn-free preflight for a doctor / early-diagnosis check (“isgitinstalled?”) that must have no side effects. A thin shim overCommand::new(program).resolve_program(); use the builder form when you needprefer_localdirectories or a relocatedPATHhonored (the client-levelCliClient::resolve_programdoes the same for a wrapped tool).
Type Aliases§
- Result
- Crate result alias.