### Managing process groups with start, spawn, and adopt Source: https://zelanton.github.io/processkit/process-groups.html Demonstrates the three ways to add processes to a ProcessGroup: start() for managed commands, spawn() for raw tokio commands, and adopt() for existing external processes. ```rust #![allow(unused)] fn main() { use processkit::{Command, ProcessGroup}; let group = ProcessGroup::new()?; // 1. start(): the full Command experience (capture, streaming, timeouts) in a // SHARED group. The handle does not own the group — dropping the handle // kills that child, dropping the group kills everyone. let server = group.start(&Command::new("dev-server")).await?; // 2. spawn(): the raw escape hatch for a tokio::process::Command you already // have. You get the bare Child back; pipes and reaping are your problem. // spawn() takes the command BY VALUE (reuse would stack pre-exec hooks). let raw = tokio::process::Command::new("background-helper"); let child = group.spawn(raw)?; // 3. adopt(): contain a child that was spawned OUTSIDE the group. let external = tokio::process::Command::new("legacy-launcher").spawn()?; group.adopt(&external)?; } ``` -------------------------------- ### Run a command with ProcessKit Source: https://zelanton.github.io/processkit/index.html A basic example demonstrating how to use the Command struct to execute a command and await its result. ```rust use processkit::Command; #[tokio::main] async fn main() -> processkit::Result<()> { let version = Command::new("cargo").arg("--version").run().await?; println!("{version}"); Ok(()) } ``` -------------------------------- ### Install processkit-py Source: https://zelanton.github.io/processkit/python-wrapper.html Install the processkit-py package using pip. This command fetches and installs the latest version from PyPI. ```bash pip install processkit-py ``` -------------------------------- ### Restart delay example values Source: https://zelanton.github.io/processkit/supervision.html An example of how restart delays scale over time with a base of 200ms, a factor of 2.0, and a cap of 30s. ```text base=200ms, factor=2.0, cap=30s: restart #0 → ~200ms #1 → ~400ms #2 → ~800ms … #7 → ~25.6s #8+ → 30s (cap) ``` -------------------------------- ### Process output line-by-line with StreamExt Source: https://zelanton.github.io/processkit/index.html Use start() to get a RunningProcess and process stdout lines as they arrive using StreamExt. The finish() method collects the final outcome and stderr after the stream ends. ```rust use processkit::prelude::StreamExt; use processkit::{Command, Outcome, Finished}; #[tokio::main] async fn main() -> processkit::Result<()> { let mut run = Command::new("git") .args(["log", "--oneline", "-n", "50"]) .start() .await?; let mut lines = run.stdout_lines()?; while let Some(line) = lines.next().await { println!("commit: {line}"); } // After the stream ends, collect the outcome and whatever went to stderr // (drained in the background while you streamed stdout). The `Outcome` // distinguishes a clean exit, a signal kill, and a timeout. let Finished { outcome, stderr, .. } = run.finish().await?; if outcome != Outcome::Exited(0) { eprintln!("git ended {outcome:?}: {stderr}"); } Ok(()) } ``` -------------------------------- ### Configure Supervisor Storm Guard Source: https://zelanton.github.io/processkit/supervision.html Example of initializing a Supervisor with storm guard settings including storm_pause, failure_decay, and failure_threshold. ```rust #![allow(unused)] fn main() { use processkit::{Command, Supervisor}; use std::time::Duration; let outcome = Supervisor::new(Command::new("worker")) .storm_pause(Duration::from_secs(15)) // master switch — off by default .failure_decay(Duration::from_secs(30)) // score half-life (default 30s) .failure_threshold(5.0) // trip point (default 5.0) .run() .await?; println!("storm pauses taken: {}", outcome.storm_pauses); } ``` -------------------------------- ### Install ProcessKit via Cargo Source: https://zelanton.github.io/processkit/index.html Add the processkit crate to your project dependencies. ```bash cargo add processkit ``` -------------------------------- ### Manage process groups with ProcessGroup Source: https://zelanton.github.io/processkit/cookbook.html Demonstrates creating a process group, starting commands, and handling shutdown. The group acts as a unit of fate where a panic or early return reaps all members. ```rust #![allow(unused)] fn main() { use processkit::ProcessGroup; let group = ProcessGroup::new()?; let _db = group.start(&Command::new("dev-db")).await?; let _api = group.start(&Command::new("dev-api")).await?; // Either: graceful — SIGTERM, bounded wait, optional SIGKILL escalation… group.shutdown().await?; // …or just drop(group): hard kill-on-drop of everything, grandchildren included. } ``` -------------------------------- ### ProcessGroup::start Source: https://zelanton.github.io/processkit/print.html Starts a command within the process group. ```APIDOC ## ProcessGroup::start(command: &Command) ### Description Starts a new process as a member of the process group. ### Signature `pub async fn start(&self, command: &Command) -> Result` ``` -------------------------------- ### List process group members Source: https://zelanton.github.io/processkit/print.html Demonstrates how to start commands within a ProcessGroup and retrieve the list of active member PIDs. ```rust #![allow(unused)] fn main() { use processkit::{Command, ProcessGroup}; let group = ProcessGroup::new()?; let _a = group.start(&Command::new("worker-a")).await?; let _b = group.start(&Command::new("worker-b")).await?; let pids: Vec = group.members()?; println!("live members: {pids:?}"); } ``` -------------------------------- ### Send signals to a process group Source: https://zelanton.github.io/processkit/process-groups.html Demonstrates how to initialize a ProcessGroup, start a command, and send various signals to the group members. Requires the process-control feature. ```rust #![allow(unused)] fn main() { use processkit::{Command, ProcessGroup, Signal}; let group = ProcessGroup::new()?; let _server = group.start(&Command::new("my-server")).await?; group.signal(Signal::Hup)?; // "reload your configuration" group.signal(Signal::Usr1)?; // whatever the tool defines group.signal(Signal::Other(34))?; // raw signal number escape hatch } ``` -------------------------------- ### Wait for server readiness Source: https://zelanton.github.io/processkit/cookbook.html Starts a server process and uses a probe to wait for readiness before proceeding. Probes fail fast with Error::NotReady if they do not succeed within the specified duration. ```rust #![allow(unused)] fn main() { use std::time::Duration; let mut server = Command::new("my-server").args(["--port", "8080"]).start().await?; // Pick the probe that matches how the server announces readiness: server.wait_for_line(|l| l.contains("listening"), Duration::from_secs(10)).await?; // server.wait_for_port("127.0.0.1:8080".parse().unwrap(), Duration::from_secs(10)).await?; // server.wait_for(|| async { http_health().await }, Duration::from_secs(10)).await?; // …use the server; dropping `server` kills its whole tree. } ``` -------------------------------- ### ProcessGroup::start Source: https://zelanton.github.io/processkit/print.html Starts a command within the process group, providing full management features like capture, streaming, and timeouts. ```APIDOC ## start(command: &Command) ### Description Starts a new process within the shared group. The returned handle does not own the group; dropping the handle kills the child, while dropping the group kills all processes in the group. ### Parameters - **command** (&Command) - Required - The command to execute. ``` -------------------------------- ### Interact with a REPL using keep_stdin_open Source: https://zelanton.github.io/processkit/streaming.html Demonstrates using keep_stdin_open and take_stdin to send lines to the 'bc' calculator and read the results. The example shows interleaving writes and reads to prevent buffer deadlocks. ```rust use processkit::prelude::StreamExt; use processkit::{Command, Outcome, Finished}; // `ProcessStdin`'s writer methods return `std::io::Result`; `Box` // mixes them with the crate's `Result` (or `.map_err(processkit::Error::Io)?`). #[tokio::main] async fn main() -> Result<(), Box> { // `bc` evaluates each stdin line and prints the result. let mut run = Command::new("bc").keep_stdin_open().start().await?; let mut stdin = run.take_stdin().expect("stdin was kept open"); let mut answers = run.stdout_lines()?; stdin.write_line("2 + 2").await?; // writes "2 + 2\n", flushed println!("= {}", answers.next().await.unwrap()); stdin.write_line("6 * 7").await?; println!("= {}", answers.next().await.unwrap()); stdin.finish().await?; // send EOF — bc exits let Finished { outcome, .. } = run.finish().await?; assert_eq!(outcome, Outcome::Exited(0)); Ok(()) } ``` -------------------------------- ### Manage process lifecycle with Command::start() Source: https://zelanton.github.io/processkit/streaming.html Demonstrates initializing a long-running process, accessing metadata like PID and elapsed time, and consuming the process handle using wait(). ```rust #![allow(unused)] fn main() { use processkit::Command; let mut run = Command::new("dev-server").start().await?; run.pid(); // Option — None once the child is reaped run.elapsed(); // time since spawn // Consume the handle exactly one way: // output_string() / output_bytes() → capture everything (same as the one-shot verbs) // wait() → just the Outcome; output is discarded // finish() → after streaming stdout (below) // profile(every) → resource samples; output discarded, like wait() (stats feature) let outcome = run.wait().await?; // Outcome: Exited(code) / Signalled(sig) / TimedOut } ``` -------------------------------- ### Configure give_up_when for permanent failure detection Source: https://zelanton.github.io/processkit/supervision.html Use give_up_when to identify permanent failures such as missing binaries or specific exit codes that should not trigger a restart. This example demonstrates matching against GiveUpAttempt to distinguish between failed starts and crashed processes. ```rust #![allow(unused)] fn main() { use processkit::{Command, GiveUpAttempt, Supervisor}; let outcome = Supervisor::new(Command::new("maybe-typo'd-binary")) .give_up_when(|attempt| match attempt { // The child never even started — e.g. ENOENT for a mistyped name. GiveUpAttempt::Failed(err) => err.is_not_found(), // A completed crash your own domain knows is permanent, e.g. a // documented "config invalid, do not restart" exit code. GiveUpAttempt::Crashed(res) => res.code() == Some(78), _ => false, }) .run() .await?; println!("stopped: {:?}", outcome.stopped); } ``` -------------------------------- ### Configure and run a Command Source: https://zelanton.github.io/processkit/commands.html Demonstrates building a command with arguments and a working directory, then executing it asynchronously. ```rust #![allow(unused)] fn main() { use processkit::Command; let out = Command::new("git") .arg("log") // one at a time… .args(["--oneline", "-n", "10"]) // …or in bulk .current_dir("/path/to/repo") // run there .run() .await?; } ``` -------------------------------- ### Configure and run a Supervisor Source: https://zelanton.github.io/processkit/supervision.html Demonstrates how to initialize a Supervisor with a command, set restart policies, configure backoff and jitter, and define an exit condition. ```rust use processkit::{Command, RestartPolicy, Supervisor}; use std::time::Duration; #[tokio::main] async fn main() -> processkit::Result<()> { let outcome = Supervisor::new(Command::new("my-server").args(["--port", "8080"])) .restart(RestartPolicy::OnCrash) // default .max_restarts(5) // default: unlimited .backoff(Duration::from_millis(200), 2.0) // default: 200ms × 2.0 .max_backoff(Duration::from_secs(30)) // default: 30s cap .jitter(true) // default: on .stop_when(|res| res.code() == Some(0)) // optional exit condition .run() .await?; println!( "ended after {} restarts, reason: {:?}, last exit: {:?}", outcome.restarts, outcome.stopped, outcome.final_result.code(), ); Ok(()) } ``` -------------------------------- ### Configure a Supervisor with Restart Policies Source: https://zelanton.github.io/processkit/cookbook.html Demonstrates setting up a supervisor for a service with crash restart policies, backoff, and a storm pause to prevent crash-looping. ```rust #![allow(unused)] fn main() { use processkit::{RestartPolicy, Supervisor}; use std::time::Duration; let outcome = Supervisor::new(Command::new("my-service")) .restart(RestartPolicy::OnCrash) .max_restarts(5) .backoff(Duration::from_millis(200), 2.0) .storm_pause(Duration::from_secs(15)) // crash-loop guard (off by default) .run() .await?; println!( "stopped after {} restarts ({} storm pauses): {:?}", outcome.restarts, outcome.storm_pauses, outcome.stopped ); } ``` -------------------------------- ### Command::new Source: https://zelanton.github.io/processkit/commands.html Initializes a new command and configures environment variables. ```APIDOC ## Command::new(program: &str) ### Description Creates a new command builder for the specified program. ### Methods - **env(key, value)**: Sets an environment variable. - **env_remove(key)**: Unsets an inherited environment variable. - **inherit_env(keys)**: Clears environment and copies only the listed variables from the parent. - **env_clear()**: Starts the child with an empty environment. - **run()**: Executes the command. ``` -------------------------------- ### Configure process spawn-time controls with Command Source: https://zelanton.github.io/processkit/index.html Demonstrates using Command to inherit specific environment variables, drop privileges on Unix, manage window creation on Windows, and handle parent death signals. Note that privilege dropping methods like uid/gid and setsid are POSIX-only and will return Error::Unsupported on other platforms. ```rust use processkit::Command; #[tokio::main] async fn main() -> processkit::Result<()> { Command::new("worker") .inherit_env(["PATH", "HOME", "LANG"]) // allow-list on a cleared env .uid(1000).gid(1000) // Unix: drop privileges .setsid() // Unix: new session .run() .await?; Command::new("helper") .create_no_window() // Windows: no console window .run() .await?; Command::new("daemonish") .kill_on_parent_death() // die with a SIGKILLed parent .start() .await?; Ok(()) } ``` -------------------------------- ### Create and configure a ProcessGroup Source: https://zelanton.github.io/processkit/process-groups.html Demonstrates initializing a ProcessGroup with default settings or custom options, and querying the underlying kernel mechanism used for containment. ```rust #![allow(unused)] fn main() { use processkit::{ProcessGroup, ProcessGroupOptions}; use std::time::Duration; // Defaults: 2s graceful-shutdown grace, escalate to SIGKILL. let group = ProcessGroup::new()?; // Tuned: let group = ProcessGroup::with_options( ProcessGroupOptions::default() .shutdown_timeout(Duration::from_secs(10)) .escalate_to_kill(true), )?; // Which kernel mechanism is actually containing the tree? println!("{:?}", group.mechanism()); // JobObject | CgroupV2 | ProcessGroup } ``` -------------------------------- ### ProcessRunner Trait Definition Source: https://zelanton.github.io/processkit/testing.html The core trait defining the interface for subprocess execution. Only output_string is required, while output_bytes and start are defaulted. ```rust #[async_trait] pub trait ProcessRunner: Send + Sync { async fn output_string(&self, command: &Command) -> Result>; // Defaulted (route through `start`); override for byte/streaming support: async fn output_bytes(&self, command: &Command) -> Result>>; async fn start(&self, command: &Command) -> Result; } ``` -------------------------------- ### Command::start Source: https://zelanton.github.io/processkit/print.html Spawns a new child process and returns a RunningProcess handle. Dropping the handle kills the process tree. ```APIDOC ## Command::start ### Description Spawns the command as a child process. The returned handle owns the process group; dropping the handle will kill the entire process tree. ### Signature `async fn start(&mut self) -> Result` ``` -------------------------------- ### Create and run a command pipeline Source: https://zelanton.github.io/processkit/pipelines.html Demonstrates chaining git log, sort, and uniq commands using Command::pipe and executing the pipeline with run(). ```rust use processkit::Command; #[tokio::main] async fn main() -> processkit::Result<()> { // git log --format=%an | sort | uniq -c let authors = Command::new("git").args(["log", "--format=%an"]) .pipe(Command::new("sort")) .pipe(Command::new("uniq").arg("-c")) .run() // require every stage to succeed .await?; println!("{authors}"); Ok(()) } ``` -------------------------------- ### Use readiness probes to wait for a server Source: https://zelanton.github.io/processkit/streaming.html Demonstrates using wait_for_line, wait_for_port, and wait_for to verify a server is ready before continuing execution. ```rust #![allow(unused)] fn main() { use processkit::Command; use std::time::Duration; let mut run = Command::new("my-server").start().await?; // 1. A line on stdout (returns the matching line): let banner = run .wait_for_line(|l| l.contains("listening on"), Duration::from_secs(10)) .await?; // 2. A TCP port accepting connections: run.wait_for_port("127.0.0.1:8080".parse().unwrap(), Duration::from_secs(10)) .await?; // 3. Any async predicate (an HTTP /health endpoint, a file appearing, …): run.wait_for(|| async { health_check().await }, Duration::from_secs(10)) .await?; // ready — use the server… } ``` -------------------------------- ### Generic Runner Usage Source: https://zelanton.github.io/processkit/testing.html Example of production code that is generic over the ProcessRunner trait, allowing it to accept both real implementations and test doubles. ```rust #![allow(unused)] fn main() { use processkit::{Command, ProcessRunner, ProcessRunnerExt, Result}; // Production code: generic over the runner. async fn current_branch(runner: &impl ProcessRunner) -> Result { runner .run(&Command::new("git").args(["branch", "--show-current"])) .await } } ``` -------------------------------- ### Configure process sandboxing and hardening Source: https://zelanton.github.io/processkit/commands.html Demonstrates privilege dropping on Unix, window suppression on Windows, and parent-death signal handling for process hardening. ```rust #![allow(unused)] fn main() { use processkit::Command; // Unix: drop privileges (uid + gid + supplementary groups) and detach. Command::new("worker") .gid(1000) // applied before uid (a gid change needs privilege) .groups([1000]) // replace the inherited (often root's) supplementary groups .uid(1000) // dropped last .setsid() // new session: survives the controlling terminal .run().await?; // Windows: no console window flashing up from a GUI app. Command::new("helper").create_no_window().run().await?; // Hardening: take the direct child down even if THIS process is SIGKILLed // (Drop never runs). Windows has this for free; Linux arms PDEATHSIG. Command::new("worker").kill_on_parent_death().start().await?; } ``` -------------------------------- ### Configure Output Encoding Source: https://zelanton.github.io/processkit/commands.html Use the encoding method to specify a non-UTF-8 encoding for command output. This example demonstrates using SHIFT_JIS for a legacy tool. ```rust #![allow(unused)] fn main() { use processkit::Command; let out = Command::new("legacy-tool") .encoding(encoding_rs::SHIFT_JIS) // both streams… // .stdout_encoding(…) / .stderr_encoding(…) // …or each its own .output_string() .await?; } ``` -------------------------------- ### Command::new Source: https://zelanton.github.io/processkit/print.html Initializes a new command builder for an external process. ```APIDOC ## Command::new(program: &str) ### Description Creates a new command builder instance for the specified program. ### Parameters - **program** (str) - Required - The name or path of the executable to run. ``` -------------------------------- ### Command Execution and Result Handling Source: https://zelanton.github.io/processkit/commands.html Demonstrates how to execute a command using Command::new and inspect the resulting ProcessResult. ```APIDOC ## Command::new().output_string() ### Description Executes a shell command and returns a `ProcessResult` containing the output and exit status. ### Method Rust Method ### Signature `Command::new(program: &str).args(args: impl IntoIterator).output_string().await` ### ProcessResult Accessors - **code()** (Option) - The exit code of the process, or None if killed. - **signal()** (Option) - The signal number (Unix), or None. - **is_success()** (bool) - Returns true if the exit code is in the configured ok_codes (default {0}). - **timed_out()** (bool) - Returns true if the process deadline expired. - **stdout()** (&str) - The standard output of the process. - **stderr()** (&str) - The standard error of the process. - **ensure_success()** (Result) - Returns an error if the process failed, timed out, or was signalled. ``` -------------------------------- ### Configure resource limits for a process group Source: https://zelanton.github.io/processkit/print.html Shows how to initialize a ProcessGroup with memory, process count, and CPU quota limits using ProcessGroupOptions. Requires the 'limits' feature. ```rust #![allow(unused)] fn main() { use processkit::{Command, ProcessGroup, ProcessGroupOptions}; let group = ProcessGroup::with_options( ProcessGroupOptions::default() .max_memory(512 * 1024 * 1024) // bytes, whole tree .max_processes(64) // fork-bomb ceiling .cpu_quota(0.5), // half of one core )?; let _sandboxed = group.start(&Command::new("untrusted-tool")).await?; } ``` -------------------------------- ### Run a command and get its output Source: https://zelanton.github.io/processkit/cookbook.html Executes a git command to retrieve the current HEAD commit hash. The run() method requires a zero exit code and returns stdout with trailing whitespace trimmed. ```rust #![allow(unused)] fn main() { let head = Command::new("git").args(["rev-parse", "HEAD"]).run().await?; } ``` -------------------------------- ### Configure stdin with Stdin sources Source: https://zelanton.github.io/processkit/commands.html Shows how to use Stdin::from_iter_lines to provide input to a command and verify the output. ```rust #![allow(unused)] fn main() { use processkit::{Command, Stdin}; let sorted = Command::new("sort") .stdin(Stdin::from_iter_lines(["banana", "apple", "cherry"])) .run() .await?; assert_eq!(sorted, "apple\nbanana\ncherry"); } ``` -------------------------------- ### Stream child process output with stdout_lines() Source: https://zelanton.github.io/processkit/streaming.html Demonstrates using stdout_lines() to iterate over output lines as they are generated by a cargo build command. The example shows how to drain stderr in the background and handle the final process outcome. ```rust use processkit::prelude::StreamExt; use processkit::{Command, Outcome, Finished}; #[tokio::main] async fn main() -> processkit::Result<()> { let mut run = Command::new("cargo") .args(["build", "--release"]) .start() .await?; let mut lines = run.stdout_lines()?; while let Some(line) = lines.next().await { println!("build: {line}"); } // The stream ended (stdout closed). Collect the outcome and stderr — // stderr was drained in the background the whole time, so a noisy child // could never block on a full pipe. let Finished { outcome, stderr, .. } = run.finish().await?; if outcome != Outcome::Exited(0) { eprintln!("build failed ({outcome:?}): {stderr}"); } Ok(()) } ``` -------------------------------- ### Using ProcessGroup as a Context Manager Source: https://zelanton.github.io/processkit/python-wrapper.html Start and manage multiple commands within a shared containment object using ProcessGroup. Ensure deterministic cleanup by using it as a context manager. Inspect `group.mechanism` to understand the underlying resource management (job_object, cgroup_v2, or process_group). ```python from processkit import Command, ProcessGroup with ProcessGroup() as group: group.start(Command("dev-server")) group.start(Command("worker")) print(group.mechanism) # job_object | cgroup_v2 | process_group print(group.members()) ``` -------------------------------- ### Execute Processes with ProcessKit Source: https://zelanton.github.io/processkit/index.html Demonstrates capturing output, requiring success, feeding stdin, and managing process groups with ProcessGroup. ```rust use processkit::{Command, ProcessGroup, Stdin}; #[tokio::main] async fn main() -> processkit::Result<()> { // Capture output; a non-zero exit does not error on its own. let result = Command::new("git").args(["rev-parse", "HEAD"]).output_string().await?; println!("HEAD is {}", result.stdout().trim()); // Require success and get trimmed stdout directly. let version = Command::new("cargo").arg("--version").run().await?; println!("{version}"); // Feed stdin. let sorted = Command::new("sort") .stdin(Stdin::from_string("banana\napple\n")) .output_string() .await?; println!("{}", sorted.stdout()); // Share one kill-on-drop group across several children; dropping the group // reaps the whole tree. let group = ProcessGroup::new()?; let _server = group.start(&Command::new("some-server")).await?; // ... work ... group.shutdown().await?; // graceful SIGTERM → wait → SIGKILL (Unix); atomic on Windows Ok(()) } ``` -------------------------------- ### Initialize and configure MockRunner Source: https://zelanton.github.io/processkit/testing.html Demonstrates creating a new MockRunner instance and setting an expectation for output_string with a specific call count and return value. ```rust use processkit::testing::MockRunner; let mut mock = MockRunner::new(); mock.expect_output_string() .times(1) .returning(|_cmd| /* build a Result> */ …); ``` -------------------------------- ### Configure environment variables with Command Source: https://zelanton.github.io/processkit/commands.html Demonstrates setting, removing, and clearing environment variables for child processes, including allow-list inheritance. ```rust #![allow(unused)] fn main() { use processkit::Command; Command::new("worker") .env("RUST_LOG", "debug") // set one variable .env_remove("GIT_DIR") // unset one inherited variable .run().await?; // Allow-list mode: clear everything, copy only the named parent variables. Command::new("sandboxed-tool") .inherit_env(["PATH", "HOME", "LANG"]) .env("MODE", "ci") // explicit env/env_remove still apply on top .run().await?; // Scorched earth: the child starts with an empty environment. Command::new("hermetic-tool").env_clear().run().await?; } ``` -------------------------------- ### Feed Stdin from Strings or Async Sources Source: https://zelanton.github.io/processkit/cookbook.html Demonstrates how to provide input to a command using a string, a file reader, or a stream of lines. Note that one-shot sources will cause the command to fail if re-run. ```rust #![allow(unused)] fn main() { use processkit::Stdin; // A string you already have: let sorted = Command::new("sort") .stdin(Stdin::from_string("banana\napple\n")) .run() .await?; // …or any async source: a reader (file, socket) or a stream of lines. let from_file = Stdin::from_reader(tokio::fs::File::open("input.txt").await?); let from_chan = Stdin::from_lines(tokio_stream::iter(vec!["one".to_owned()])); } ``` -------------------------------- ### Profile process groups and single runs Source: https://zelanton.github.io/processkit/index.html Demonstrates using sample_stats to monitor a ProcessGroup and the profile method to capture a one-shot summary of a single command run. ```rust use processkit::prelude::StreamExt; use processkit::{Command, ProcessGroup}; use std::time::Duration; #[tokio::main] async fn main() -> processkit::Result<()> { // A CPU/RSS/process-count series for a whole group: let group = ProcessGroup::new()?; let _worker = group.start(&Command::new("worker")).await?; let mut samples = group.sample_stats(Duration::from_millis(250)); if let Some(s) = samples.next().await { println!("procs={} rss={:?}", s.active_process_count, s.peak_memory_bytes); } drop(samples); // …or a one-shot summary of a single run: let profile = Command::new("crunch") .start().await? .profile(Duration::from_millis(100)).await?; println!( "outcome={:?} took={:?} peak_rss={:?} avg_cpu_cores={:?}", profile.outcome, profile.duration, profile.peak_memory_bytes, profile.avg_cpu_cores(), ); Ok(()) } ``` -------------------------------- ### Execute wait_all and output_all in ProcessKit Source: https://zelanton.github.io/processkit/index.html Demonstrates joining a fixed set of processes with wait_all and running a batch of commands with a concurrency cap using output_all. ```rust use processkit::{Command, JobRunner, wait_all, output_all}; #[tokio::main] async fn main() -> processkit::Result<()> { let group = processkit::ProcessGroup::new()?; let mut a = group.start(&Command::new("worker-a")).await?; let mut b = group.start(&Command::new("worker-b")).await?; let outcomes = wait_all(&mut [&mut a, &mut b]).await?; // both, in input order // 200 conversions, but never more than 8 processes alive at once. let cmds = (0..200).map(|i| Command::new("convert").arg(format!("{i}.png"))); let results = output_all(cmds, 8, &JobRunner).await; let failed = results.iter().filter(|r| !matches!(r, Ok(o) if o.is_success())).count(); println!("{:?}; {failed} conversions failed", outcomes); Ok(()) } ```