RunningProcess Type
A live handle to a started process: stream its output, feed its stdin, wait for it, or collect it to completion. Disposing it reaps the whole process tree (kill-on-drop).
Instance members
| Instance member |
Description
|
|
Cumulative CPU time (user + kernel) of the child right now, if the platform reports it and the process is still alive.
|
Full Usage:
this.DroppedStreamLineCount
Returns: int
|
Stream items dropped so far by a bounded streaming policy's `StreamFullMode.DropOldest`/ `DropNewest` (always `0` unless `Command.StreamBuffer` is configured with one of those modes). For line/event streams this counts dropped lines/events; for `StdoutChunksAsync`/ `StderrChunksAsync` it counts dropped chunks. It is the streaming analogue of a buffered verb's `ProcessResult.Truncated`.
|
|
Wall-clock time since the process started.
|
|
After streaming stdout, wait for exit and return the captured stderr. Reaps the tree. Safe to call without streaming first: stdout is then drained to keep the child moving and **discarded as it arrives**, retaining nothing — `Finished` carries the outcome and stderr, never stdout. Asking for that stdout afterwards is refused, not answered with an empty stream: a later `StdoutLinesAsync`/`StdoutJsonLinesAsync` throws `InvalidOperationException` and a later `WaitForLineAsync` returns `ProcessError.Unsupported`, the same already-consumed answer they give after `WaitAsync`/`ProfileAsync`. Capture stdout with `OutputStringAsync`/`OutputBytesAsync`, or take `StdoutLinesAsync`/`StdoutChunksAsync` BEFORE finishing, if you need it. A stream that WAS handed out keeps the existing hand-off semantics unchanged: everything the child wrote stays queued for its enumerator, dropped or bounded only by the `StreamBuffer` policy the caller opted into. After `StderrChunksAsync()` this is still the terminal hand-off, with `Finished.Stderr` empty by construction: that session hands the stderr BYTES to the caller instead of capturing them, so there is no text capture to return (see `StderrChunksAsync`).
|
|
`ForwardParentSignals` using the default 2-second graceful-stop window.
|
|
Forward parent termination requests into this run's graceful tree-stop path. POSIX registers `SIGINT` and `SIGTERM`; Windows handles Ctrl+C and Ctrl+Break through `Console.CancelKeyPress`. The first signal starts one `StopAsync(gracePeriod)` and suppresses the parent's default immediate termination while the tree stops; repeated signals never start duplicate teardown. The returned caller-owned scope removes the handlers when disposed. It is also removed automatically when the child exits. Registering the scope starts only the handle's shared exit observation and does not claim stdout/stderr, so capture and streaming verbs remain available. On Windows the forwarded request uses the ordinary `StopAsync` contract (best-effort `WM_CLOSE`, then Job termination after the grace window), not a promise that a console child receives the original Ctrl event.
|
Full Usage:
this.Kill
|
Signal the process tree to die without waiting (fire-and-forget, like `Process.Kill()`); the tree is fully reaped when the handle is disposed. For a blocking kill, dispose the handle. Delivering the kill also starts this handle's bounded post-kill reap window: a tree that cannot be reaped afterwards (a child wedged in uninterruptible sleep defers even SIGKILL) resolves this handle's exit wait to an honest `Outcome.Unobserved` once the window elapses, instead of leaving a caller that killed and then awaited — notably a CANCELLED run, whose token registration calls exactly this — blocked forever. The native wait is not abandoned: the `PostKillReap` ledger owns it as the single eventual reaper. |
|
Run to completion, capturing stdout as raw bytes (no line splitting) and stderr as text. The configured `OutputBuffer` policy's **byte** controls apply to this raw stdout capture: `MaxBytes = Some cap` enforces the cap per `Overflow` — `Error` returns `ProcessError.OutputTooLarge` once the cumulative stdout exceeds the cap (the pipe is still drained), `DropOldest` keeps the last `cap` bytes, `DropNewest` keeps the first `cap` bytes, both setting `ProcessResult.Truncated` when anything was dropped. `MaxBytes = None` (the default) keeps the raw capture **unbounded** — there is no byte ceiling to enforce. `MaxLines` never applies to a raw byte stream (it has no line structure) and is ignored on stdout here; it still governs the line-pumped **stderr** capture. `Truncated` reflects truncation of stdout OR stderr, and `OutputTooLarge` fires if either stream trips its fail-loud ceiling. This is a deliberate, documented divergence from the Rust `ProcessKit-rs` reference, whose `output_bytes` bounds raw bytes only by `Timeout`, not by the buffer policy: a caller who set `MaxBytes`/`FailLoud` to bound memory would still get an unbounded stdout buffer otherwise. A configured `Command.CapturePolicy` shapes the line-pumped **stderr** capture here and leaves the raw stdout bytes untouched: that seam transforms decoded lines, and this stdout capture has no line structure to hand it. Use the text verb when a policy must shape stdout.
|
|
Stream merged stdout+stderr line events as they arrive, each tagged with its origin (`OutputEvent.Stdout`/`OutputEvent.Stderr`). Under `Command.MergeStderr` the child has no separate stderr stream (it is folded into stdout at the OS level), so every event is an `OutputEvent.Stdout` — the stderr lines are already interleaved, in order, within the stdout byte stream.
|
|
Run to completion, capturing stdout as decoded text. A non-zero exit is data; the tree is reaped when the call returns.
|
Full Usage:
this.PeakMemoryBytes
Returns: int64 option
|
Peak resident memory of the child in bytes, if reported (some platforms, e.g. macOS, may not) and the process is still alive.
|
Full Usage:
this.Pid
Returns: int option
|
The pid, when known.
|
|
`ProfileAsync` sampling every 100 ms.
|
|
Run to completion while periodically sampling the child's CPU/memory and, where available, its private containment tree's I/O every `interval`, then return a `RunProfile`. Drains and discards output (like `WaitAsync`) and reaps the tree. A run in a shared group reports no I/O counters, because the group's aggregate would include sibling runs. A non-positive `interval` (`<= TimeSpan.Zero`) is rejected with `ArgumentOutOfRangeException` — a sampling cadence must be a positive duration. Validated up front, before the pipes are claimed, so an invalid call neither consumes this one-shot handle nor starts a tight loop.
|
Full Usage:
this.ResizeAsync
Parameters:
int
rows : int
Returns: Task<Result<unit, ProcessError>>
|
Resize the child's controlling pseudo-terminal to `cols` columns x `rows` rows (a `Command.Pty` run only). Windows applies it with `ResizePseudoConsole`; POSIX applies `ioctl(TIOCSWINSZ)` on the pty master and then delivers `SIGWINCH` to the child so a running TUI re-queries its geometry (D6). Honest, never a silent no-op: on a **non-PTY** run this returns `Error(ProcessError.Unsupported)`, and a native resize failure returns `Error(ProcessError.Io ...)` — a garbled/partial resize is never reported as success. `cols` and `rows` must each be at least 1 and at most `Int16.MaxValue` (a terminal `COORD`/`winsize` is a `SHORT`), rejected with `ArgumentOutOfRangeException` at the boundary, matching the `Command.Pty` builder's geometry validation. A **pure**, non-consuming verb: it neither consumes the output pipes nor touches the exit-wait/reap path, so it never trips the "already consumed by another verb" gate and can run alongside a capturing/streaming/`WaitAsync` verb that has claimed the handle. It is honest about lifecycle, though: once the run has been **torn down** — a terminal verb has concluded and reaped it, or the handle has been disposed — the pty master fd / pseudoconsole handle behind the resize is closed, and its number is reusable by another run, so a resize then returns `Error(ProcessError.Unsupported ...)` rather than risk `ioctl`/`SIGWINCH`/`ResizePseudoConsole` landing on an unrelated run through a recycled fd/pid/handle. Resize a run while it is live.
|
|
Deliver `signal` to this run's own contained process tree without consuming or reaping the handle. Delivery is lifecycle-gated: after teardown it returns a typed `Unsupported` error and never targets a recycled pid. On Windows only the documented Job/CTRL+BREAK/WM_CLOSE mappings are available; unsupported signals fail honestly.
|
|
When the process was started.
|
Full Usage:
this.StderrBytesSeen
Returns: int64
|
The stderr twin of `StdoutBytesSeen`. A run with **one merged stream** — `Command.MergeStderr()` or a `Command.Pty()` run, where the child has a single terminal device — has no separate parent-side stderr stream at all, so every byte it produced is counted in `StdoutBytesSeen` and this counter stays `0` (the same boundary `StderrChunksAsync` and the stderr readiness waits report as unsupported). `0` likewise for stderr redirected to a file or set to `Null`/`Inherit`.
|
|
Stream **stderr** as raw byte chunks — the exact counterpart of `StdoutChunksAsync`, for diagnostics that text is the wrong abstraction for (a binary progress protocol, a high-volume log a caller wants to relay or hash byte-for-byte). Each item is a non-empty `ReadOnlyMemory<byte>` containing exactly one underlying read, including NUL bytes, invalid UTF-8, and arbitrary read boundaries; the returned memory owns its backing array and remains valid after the next item is produced. Same `Command.StreamBuffer` backpressure/drop/ fail-loud policy, same `StderrTee` raw tee, same teardown behaviour as the stdout chunk stream, and the same one-shot contract: a second call — or any other consuming verb — is refused with the already-consumed `InvalidOperationException`. Call `FinishAsync()` afterwards for the process outcome. **This run's stdout is drained and discarded.** `Finished` carries the outcome and the captured stderr, which this verb has just handed to the caller as bytes, so there is nothing for a terminal verb to return stdout through — and retaining it would pin a whole run's output in memory for nobody (T-357). stdout is still read, framed, teed (`StdoutTee`), handed to `OnStdoutLine` and counted into `StdoutLineCount`, so the child never blocks on a full pipe; it is simply never retained, and asking for it afterwards (`StdoutLinesAsync`/`StdoutChunksAsync`/ `OutputStringAsync`/...) is refused rather than answered with an empty stream. Capture stdout with `Command.StdoutTee`/`StdoutToFile` if you need both. **No separate stderr, no fake stream.** A run whose stderr does not reach the parent as its own pipe cannot have a byte-exact stderr stream at all: `Command.MergeStderr` folds stderr into stdout at the OS level, a `Command.Pty` run gives the child one terminal device, and `StderrToFile`/`StdioMode.Inherit`/`StdioMode.Null` leave no parent-side stream. Each of those raises `ProcessException` carrying `ProcessError.Unsupported` (naming which one it was) instead of returning an empty enumerable that would read as "the child wrote nothing to stderr" — under a merge, use `StdoutChunksAsync()`, where those bytes really are. The refusal happens BEFORE the pipes are claimed, so the handle is left untouched and every other verb remains available.
|
Full Usage:
this.StderrLineCount
Returns: int
|
Total stderr lines pumped so far.
|
Full Usage:
this.StdoutBytesSeen
Returns: int64
|
Cumulative **raw** stdout bytes this handle has read from the child — exactly what `Stream.ReadAsync` returned on the parent's side, counted **before** decoding, line framing, or any buffering/streaming policy could drop or refuse them. So it counts the bytes of a line a bounded `StreamBuffer` policy dropped, of output an `OutputBuffer` cap refused as oversized, and of a run whose output is discarded (`WaitAsync`) just the same: it measures what came off the child, not what was kept. Monotonic, cheap to read at any time — mid-stream, and afterwards: it keeps its final total once the pumps end, including after `FinishAsync`/`DisposeAsync`. **A stream the parent never reads answers `0`** — honestly, without blocking: a `StdioMode` `Null`/`Inherit` stdout, one redirected straight to a file (`Command.StdoutToFile`), or a stream this command did not configure. Not the converse: a piped stream reads `0` too until bytes actually come off it, so `0` alone does not mean the stream is unread. It is the byte-level counterpart of `StdoutLineCount`, and a different quantity from the byte totals a capture reports (`ProcessResult`'s truncation totals count what was RETAINED, post-decode, in the currency of the cap that bounds it).
|
|
Stream stdout as raw byte chunks. Each item is a non-empty `ReadOnlyMemory
|
Full Usage:
this.StdoutJsonLinesAsync
Parameters:
JsonTypeInfo<'T>
Returns: IAsyncEnumerable<'T>
Type parameters: 'T |
Like the overload above, but deserializes each line via a source-generated `JsonTypeInfo<'T>` instead of reflection — no `RequiresUnreferencedCode`/ `RequiresDynamicCode`, so this overload is trim-/NativeAOT-safe. Pass `MyJsonContext.Default.MyType` from a `[<JsonSerializable>]`-annotated `JsonSerializerContext`. Same empty-line-skip / `ProcessError.Parse` contract as the reflection overload above.
|
Full Usage:
this.StdoutJsonLinesAsync
Parameters:
JsonSerializerOptions
Returns: IAsyncEnumerable<'T>
Type parameters: 'T |
Stream stdout as NDJSON / JSON Lines: each non-empty line is deserialized into a `'T` via `System.Text.Json` (`options` omitted uses the BCL defaults) as it arrives. A thin wrapper over `StdoutLinesAsync()` — it shares the very same exclusive-consumption gate (`StartStdoutStreaming`) and the same already-consumed enumerator guard, `LineTerminator`, and `StreamBuffer` policy, so calling this instead of `StdoutLinesAsync()` (or vice versa, or twice) on one handle follows the same already-consumed contract every other streaming verb already has; nothing extra needs configuring here. An empty line (after that line-terminator policy is applied) is skipped silently, never deserialized — a common NDJSON producer quirk (a trailing blank line, a keep-alive newline). A non-empty line that fails to deserialize ends the enumeration with `ProcessException(ProcessError.Parse(...))`, exactly like every other JSON verb's `ProcessError.Parse` (`OutputJsonAsync`/`ParseAsync`) — never a raw, undocumented exception escaping the `IAsyncEnumerable`. Call `FinishAsync()` afterwards for stderr + outcome, same as after `StdoutLinesAsync()`. **Trimming / AOT:** deserializes via reflection-based `System.Text.Json` (`JsonSerializer.Deserialize(string, Type, JsonSerializerOptions)`), so it is not trim-/AOT-safe — pass a `JsonTypeInfo<'T>` via the other overload, or avoid this verb, in a trimmed/NativeAOT app.
|
Full Usage:
this.StdoutLineCount
Returns: int
|
Total stdout lines pumped so far (counts dropped lines too).
|
|
Stream stdout line by line as it arrives. Call `FinishAsync` afterwards for stderr + outcome. Hands out its ONE enumerator exactly once per handle — a second call (directly, or via `StdoutJsonLinesAsync`, which itself calls this) throws `InvalidOperationException`, same as any other already-consumed verb; `FinishAsync`/`WaitForLineAsync` remain free to rejoin the same session afterwards (they do not produce a second enumerator). Take the stream BEFORE finishing: a `FinishAsync` that ran with no enumerator handed out discards stdout as it arrives, so this throws the same already-consumed `InvalidOperationException` afterwards rather than returning a stream that could only be empty.
|
|
|
|
Gracefully stop the process tree, then reap it: send the command's configured `StopSignal`, wait up to `gracePeriod` for it to exit on its own, then hard-kill whatever is still alive — the same graceful-kill machinery `Command.TimeoutGrace` and `ProcessGroup.ShutdownAsync` drive. Returns the honest `Outcome` of how the child *actually* concluded (a clean `Exited` if it obeyed the signal, otherwise a `Signalled`/`Exited` from the escalated kill); a non-zero or killed exit is data, never a raised error. Unlike the fire-and-forget `Kill()`, this awaits the stop and tears the tree down before returning, so it is a terminal verb like `WaitAsync`. A negative `gracePeriod` is rejected with `ArgumentOutOfRangeException`; `TimeSpan.Zero` skips the grace window and escalates immediately. **Bounded, always.** The whole call — the grace window and the reap that follows the escalated hard kill — is bounded by `gracePeriod` plus a post-kill reap budget. A tree that cannot be reaped even after the hard kill lands (a child wedged in uninterruptible sleep defers SIGKILL until its I/O unblocks) therefore ends this call with an honest `Outcome.Unobserved` carrying that detail, never a fabricated exit and never an unbounded block. The wait is not dropped: the single remaining right to reap the tree passes to a background reaper, so nothing starts a second waiter and the eventual conclusion is still observed exactly once. This drains the child's stdout/stderr while it shuts down (a child blocked writing to a full pipe would otherwise ignore the soft signal until it could flush). If a streaming or capturing verb already owns the pipes, `StopAsync` reuses that session's wait rather than starting a second reader on them, so it is safe to call after `StdoutLinesAsync`/`OutputEventsAsync` or concurrently with an in-flight `FinishAsync`/`WaitAsync`. Idempotent and race-safe with `Kill`, `Dispose`, and a repeat `StopAsync`: the tree is reaped exactly once. **Platform / shared-group degradation (no new silent downgrade).** A soft signal needs a mechanism that has one. On **Windows** there is no per-tree graceful signal, but a windowed child (Electron/GUI) is sent a best-effort `WM_CLOSE` at the start of the grace window and can close itself within it; a child with no window (or one that vetoes the close) is hard-killed by the atomic Job terminate when the grace elapses — exactly as `Command.TimeoutGrace` and `ProcessGroup.ShutdownAsync` behave there (a console child can additionally get a best-effort CTRL+BREAK via `Command.WindowsCtrlSignals()` + `ProcessGroup.Signal`). On a **shared** group (a handle from `ProcessGroup.StartAsync`, where the group — not the handle — owns the tree) there is no per-child graceful signal either, so this immediately hard-kills just this child (like `Kill()`), matching the documented `TimeoutGrace` fallback for a shared group. A handle from the default runner (`Command.StartAsync()` / `IProcessRunner.SpawnAsync`) owns a private group and gets the full configured-soft-signal → grace → SIGKILL path on Unix.
|
|
Take the parent side of the POSIX full-duplex channel connected to `targetFd` in the child. Returns `Some` only for a descriptor configured with `Command.ExtraFd`, and only once.
|
|
Take the interactive stdin handle — `Some` only when the command kept stdin open (`Command.KeepStdinOpen`), and only once. `None` in every other case: stdin was not kept open (no `KeepStdinOpen`, or an `InheritStdin` child), the writer was already taken (an earlier `TakeStdin`, or the `PtySession`/`ContentLengthSession` that took it for its own send verbs), or a verb that ran this handle to completion found the writer untaken and ended the child's input itself (see `FinishUnclaimedStdin` — the same once-only claim, made from the other side). So take the writer BEFORE `OutputStringAsync`/`OutputBytesAsync`/`WaitAsync`/`ProfileAsync`, a first-consumer `WaitAnyAsync`/`WaitAllAsync`/`StopAsync`, or a runner-level verb that hands out no handle at all: such a verb claims as it starts, and a claim lost to it is not recoverable — the child's end of input is already on its way. A writer this call hands out stays the caller's: no verb closes a handle it gave away, and completion waits for that caller's own `FinishAsync`. With **no** source the writer is available immediately; with a `Command.Stdin(source)` it is available once the background feeder has finished draining that source (this call blocks until then), so the caller never writes to the pipe while the feeder still is. That wait is deadlock-safe even on a single-threaded `SynchronizationContext` (a WPF/WinForms UI thread, classic ASP.NET): the source feeder runs detached on the thread pool (see `Pump.feedStdin`'s `backgroundTask`), so it always makes progress while this thread is blocked here and is never waiting to post a continuation back to it.
|
|
|
Full Usage:
this.WaitForAsync
Parameters:
Func<Task<bool>>
timeout : TimeSpan
?cancellationToken : CancellationToken
Returns: Task<Result<unit, ProcessError>>
|
Poll `probe` until it returns true, or fail with `NotReady` once the shared `timeout` deadline elapses (or `Cancelled` if `cancellationToken` fires first). The deadline is honored even if `probe` never completes — or blocks synchronously without ever returning a task: the invocation is isolated on the thread pool and raced against the shared deadline, and the caller's token takes priority over a concurrent success. The API cannot force a caller-owned `probe` to stop, so an abandoned invocation keeps running in the background, but its late outcome is safely observed (a late fault never becomes an unobserved task exception). See `ReadinessProbe.waitForCoreUsing` for the full contract, including the ratified scheduler-bounded window at the deadline. If the child exits before `probe` returns true, `probe` is invoked exactly once more — bounded by what is left of `timeout` and by a brief internal grace, so readiness published immediately before the child terminated is reported as `Ok` instead of being lost — and this then returns `NotReady` rather than polling out the full `timeout`. Callers therefore must expect one extra `probe` invocation after the child exits; a cancelled token or an already-spent deadline suppresses it. That is the same early-exit contract `WaitForHttpAsync`/`WaitForPortAsync`/`WaitForPathAsync` honour, and it runs via the one reap-once exit wait the rest of the handle shares (so a later `WaitAsync`/`ProfileAsync` still reports the real exit). Background-drains (and discards) the child's piped stdout/stderr for the duration of the poll, exactly like `WaitForPortAsync` — see its doc for what that does and doesn't compose with afterward.
|
Full Usage:
this.WaitForHttpAsync
Parameters:
Uri
client : HttpClient
isSatisfactory : Func<HttpResponseMessage, bool>
timeout : TimeSpan
?cancellationToken : CancellationToken
Returns: Task<Result<unit, ProcessError>>
|
Like the predicate overload, but sends requests through the caller-owned `client`. ProcessKit neither mutates nor disposes the client.
|
Full Usage:
this.WaitForHttpAsync
Parameters:
Uri
isSatisfactory : Func<HttpResponseMessage, bool>
timeout : TimeSpan
?cancellationToken : CancellationToken
Returns: Task<Result<unit, ProcessError>>
|
Like `WaitForHttpAsync(uri, timeout, cancellationToken)`, but uses `isSatisfactory` to inspect each response. A false result is retried; an exception from caller-supplied validation propagates. `uri` must be absolute.
|
Full Usage:
this.WaitForHttpAsync
Parameters:
Uri
client : HttpClient
acceptableStatusCodes : int seq
timeout : TimeSpan
?cancellationToken : CancellationToken
Returns: Task<Result<unit, ProcessError>>
|
Like the status-code overload, but sends requests through the caller-owned `client`. ProcessKit neither mutates nor disposes the client.
|
Full Usage:
this.WaitForHttpAsync
Parameters:
Uri
acceptableStatusCodes : int seq
timeout : TimeSpan
?cancellationToken : CancellationToken
Returns: Task<Result<unit, ProcessError>>
|
Like `WaitForHttpAsync(uri, timeout, cancellationToken)`, but treats only status codes from `acceptableStatusCodes` as ready. The sequence is materialized once before polling, so every retry applies the same criteria. The sequence must contain at least one status code.
|
Full Usage:
this.WaitForHttpAsync
Parameters:
Uri
client : HttpClient
timeout : TimeSpan
?cancellationToken : CancellationToken
Returns: Task<Result<unit, ProcessError>>
|
Like `WaitForHttpAsync(uri, timeout, cancellationToken)`, but sends requests through the caller-owned `client`. ProcessKit neither mutates nor disposes the client.
|
Full Usage:
this.WaitForHttpAsync
Parameters:
Uri
timeout : TimeSpan
?cancellationToken : CancellationToken
Returns: Task<Result<unit, ProcessError>>
|
Poll `uri` with HTTP GET until a response passes the default 2xx check, or fail with `NotReady` once `timeout` expires (or `Cancelled` if `cancellationToken` fires first). Connection failures, DNS failures, and request cancellations caused by the shared deadline are retried every 50ms. If the child exits before a satisfactory response arrives, exactly one more request is sent — bounded by what is left of `timeout` and by a brief internal grace — and this returns `NotReady` unless that last response is satisfactory, exactly as `WaitForPortAsync` describes. While polling, the child's piped stdout/stderr are background-drained and discarded exactly like `WaitForPortAsync`, so startup output cannot block a chatty child before it becomes ready. `uri` must be absolute; a relative URI throws `ArgumentException` before polling begins.
|
Full Usage:
this.WaitForLineAsync
Parameters:
Func<string, bool>
timeout : TimeSpan
?cancellationToken : CancellationToken
Returns: Task<Result<string, ProcessError>>
|
Wait until a stdout line satisfies `predicate`, or fail with `NotReady` after `timeout` (or `Cancelled` if `cancellationToken` fires first). Consumed lines are not re-delivered; a later `StdoutLinesAsync`/`FinishAsync` sees the rest. Once a `FinishAsync` that took no stream has discarded stdout, this returns `ProcessError.Unsupported` (already consumed) rather than a `NotReady` that would read as "the line never arrived" for a stream nobody can be given.
|
Full Usage:
this.WaitForNamedPipeAsync
Parameters:
string
timeout : TimeSpan
?cancellationToken : CancellationToken
Returns: Task<Result<unit, ProcessError>>
|
Wait until the Windows named pipe `pipeName` accepts a client connection, or fail with `NotReady` once the shared `timeout` deadline elapses (or `Cancelled` if `cancellationToken` fires first). Behaves like `WaitForSocketAsync` (same deadline mechanics, same early-exit-on-child-death contract, same background stdout/stderr draining), but dials a Windows named pipe through `CreateFileW` instead of a socket — see `ReadinessProbe.waitForNamedPipe`/`waitForCoreUsing` for the full deadline contract. `pipeName` may be a bare name (`"my-service"`, resolved under the local `\\.\pipe\` namespace) or an already-fully-qualified path (`\\.\pipe\my-service`, or a remote `\\server\pipe\my-service`). `ERROR_PIPE_BUSY` counts as ready: it proves a server created the pipe even though every instance is currently serving another client, which genuinely differs from the pipe not existing at all — the latter keeps polling until `pipeName`'s server creates it or the deadline elapses. Each poll is a real, if momentary, client connection: a successful `CreateFileW` is indistinguishable from any other client's open, so the server sees an accepted connection that immediately disconnects again once the poll closes the handle (this is what the test `WaitForNamedPipe connects to a listening named pipe` observes from the server side, and it matches the source Rust crate's `named_pipe_is_ready`, which opens and drops a client the same way). For a single-instance server this can briefly leave the pipe genuinely busy for a real client racing the same poll, until the probe closes its handle and the server re-posts its listen; a multi-instance server is unaffected. This is never a connection the caller keeps open or that the caller can observe directly — only its side effect on the server's own accept state. This probe is available only on Windows: requires the host to support Windows named pipes (`RuntimeInformation.IsOSPlatform OSPlatform.Windows`); on any other platform this returns `Error(ProcessError.Unsupported ...)` immediately, before ever attempting to open a pipe — never a silent downgrade or an inevitable hang, symmetric with `WaitForSocketAsync`'s `AF_UNIX` gate.
|
Full Usage:
this.WaitForPathAsync
Parameters:
string
timeout : TimeSpan
?cancellationToken : CancellationToken
Returns: Task<Result<unit, ProcessError>>
|
Wait until `path` exists on disk — as a file, a directory, or anything else `File.Exists`/`Directory.Exists` can observe — or fail with `NotReady` once the shared `timeout` deadline elapses (or `Cancelled` if `cancellationToken` fires first). This is the portable readiness signal for a pidfile, a sentinel/lock file, or a Unix-socket pathname a daemon creates before a caller should attempt a richer connection probe (`WaitForSocketAsync`). A relative `path` resolves against this run's own `Command.CurrentDir` (the CHILD's working directory) when one was set — the same rule `Command.PreferLocal` already applies to its own relative entries — and otherwise resolves against the calling process's current directory, the same as an unresolved `File.Exists` call. Pass an absolute `path` for a sentinel the child writes when no `CurrentDir` is configured, so the probe does not depend on where this process happens to be running from. EXISTENCE ONLY: a file and a directory both count as ready the instant `stat` can see them — this does not require the path to be a regular file, does not wait for its writer to finish, and makes no size/stability promise, so a sentinel a daemon `touch`es before it is done writing elsewhere is reported ready immediately. A caller that needs "fully written", not merely "created" (e.g. a config file whose writer must finish first), should probe that stronger condition itself with `WaitForAsync`. A filesystem lookup failure (a denied directory, a transient I/O error, a race with a concurrent rename) is treated the same as "does not exist yet" and retried until the deadline — the same "any failure just means not ready yet" rule `WaitForPortAsync`/`WaitForSocketAsync` apply to a refused connect. An existence check has no platform precondition (unlike `WaitForSocketAsync`'s `AF_UNIX` gate), so this never returns `ProcessError.Unsupported`. If the child exits before `path` appears, existence is checked exactly once more — bounded by what is left of `timeout` and by a brief internal grace, so a sentinel created immediately before the child terminated is reported as `Ok` instead of being lost — and this then returns `NotReady` rather than polling out the full `timeout`; a cancelled token or an already-spent deadline still wins over that last check. That is the same early-exit contract `WaitForPortAsync`/ `WaitForHttpAsync`/`WaitForSocketAsync`/`WaitForAsync` honour, and it runs via the one reap-once exit wait the rest of the handle shares (so a later `WaitAsync`/`ProfileAsync` still reports the real exit). Background-drains (and discards) the child's piped stdout/stderr for the duration of the poll, exactly like `WaitForPortAsync` — see its doc for what that does and doesn't compose with afterward.
|
Full Usage:
this.WaitForPortAsync
Parameters:
IPEndPoint
timeout : TimeSpan
?cancellationToken : CancellationToken
Returns: Task<Result<unit, ProcessError>>
|
Wait until a TCP connection to `endpoint` succeeds, or fail with `NotReady` once the shared `timeout` deadline elapses (or `Cancelled` if `cancellationToken` fires first). Every connect attempt and polling backoff shares that one deadline, so a slow or non-cooperative connect can never overrun a short `timeout` — see `ReadinessProbe.waitForCoreUsing` for the full contract, including the ratified scheduler-bounded window at the deadline. If the child exits before the port opens, the endpoint is dialled exactly once more — bounded by what is left of `timeout` and by a brief internal grace, so a port opened immediately before the child terminated is reported as `Ok` instead of being lost — and this then returns `NotReady` rather than polling out the full `timeout`; a cancelled token or an already-spent deadline still wins over that last dial. That is the same early-exit contract `WaitForHttpAsync`/`WaitForSocketAsync`/`WaitForPathAsync`/ `WaitForAsync` honour, and it runs via the one reap-once exit wait the rest of the handle shares (so a later `WaitAsync`/`ProfileAsync` still reports the real exit). Background-drains (and discards) the child's piped stdout/stderr for the duration of the poll — like `WaitForLineAsync`, so a child that writes more than one OS pipe buffer of startup output (~64 KiB on Linux) before becoming ready can't block in `write()` and spuriously time out this probe — but unlike `WaitForLineAsync`, the drained bytes are discarded rather than handed back, and draining stops once the probe concludes rather than continuing as an established streaming session. A capture verb (`OutputStringAsync`/`OutputBytesAsync`/`StdoutLinesAsync`/ `OutputEventsAsync`) called AFTER this probe therefore only sees what the child wrote after the probe concluded, not the full run — the same "doesn't compose with a subsequent fresh capture" limitation `WaitForLineAsync` already documents, now uniform across every other readiness probe. If a buffered/streaming verb already claimed the pipes before this call, that verb's own pump is already draining them and this probe leaves them alone (no second reader).
|
Full Usage:
this.WaitForSocketAsync
Parameters:
string
timeout : TimeSpan
?cancellationToken : CancellationToken
Returns: Task<Result<unit, ProcessError>>
|
Wait until a connection to the Unix domain socket at `path` succeeds, or fail with `NotReady` once the shared `timeout` deadline elapses (or `Cancelled` if `cancellationToken` fires first). Behaves exactly like `WaitForPortAsync` (same deadline mechanics, same early-exit-on-child-death contract, same background stdout/stderr draining), but dials `AddressFamily.Unix` instead of TCP — see `ReadinessProbe.waitForSocket`/`waitForCoreUsing` for the full contract. Requires the host to support `AF_UNIX` sockets (Windows 10 1809+, any current Linux/macOS via .NET's own requirement); on a host without that support this returns `Error(ProcessError.Unsupported ...)` immediately, before ever attempting to dial — never a silent downgrade or an inevitable hang. A path that cannot fit the platform's Unix-socket address fails immediately with `ArgumentOutOfRangeException` rather than being retried as if no listener were present.
|
Full Usage:
this.WaitForStderrLineAsync
Parameters:
Func<string, bool>
timeout : TimeSpan
?cancellationToken : CancellationToken
Returns: Task<Result<string, ProcessError>>
|
Wait until a **stderr** line satisfies `predicate`, or fail with `NotReady` after `timeout` (or `Cancelled` if `cancellationToken` fires first) — `WaitForLineAsync`'s exact contract, pointed at the diagnostic stream, for the many tools that publish their readiness marker there rather than on stdout. Returns the line that matched. Lines are framed with `Command.StderrLineTerminator` and decoded with `Command.StderrEncoding`, so this sees precisely what `Command.OnStderrLine`, `StderrTee` and `Finished.Stderr` see — never stdout's framing applied to the wrong stream. **It observes stderr; it does not take it.** The line it matched still reaches the captured stderr `FinishAsync` returns, exactly once, together with every line before and after it: this wait consumes from its own readiness view of the stream, not from the capture. What it does consume is that readiness view — the line it matched, and the lines it skipped past on the way, are not offered to a LATER stderr readiness wait, the same way `WaitForLineAsync` consumes the stdout lines it reads. **Composes with the stdout verbs**, because it joins the very same streaming session they use: `WaitForLineAsync`, `StdoutLinesAsync`/`StdoutJsonLinesAsync` and a closing `FinishAsync` all remain available before and after it, and stdout keeps being drained (and queued for a stream a caller may still take) throughout the wait. A verb that owns the pipes outright — the buffered captures, `OutputEventsAsync`, either byte-chunk stream, an interactive session — makes this return `ProcessError.Unsupported` (already consumed), as does a terminal `FinishAsync` that has already discarded the session's stdout. Stderr lines framed BEFORE the first wait on a handle whose session another verb had already started are not retained for it (nothing was watching yet); between successive waits they are, bounded as described below. If the child exits (or stderr simply reaches EOF) before the predicate is satisfied, this returns `NotReady` promptly rather than waiting out the full `timeout` — and a genuine stderr pump failure (a throwing `OnStderrLine`/`StderrTee` handler, a decode or I/O error) surfaces as that failure instead of a misleading readiness timeout, exactly as it does through `FinishAsync`. A run with no parent-side stderr stream — `Command.MergeStderr`, a `Command.Pty` run, `StderrToFile`, `StdioMode.Inherit`/`Null` — returns `ProcessError.Unsupported` naming which one it was, rather than a `NotReady` that would read as "the marker never came" for a stream that never existed. Under a merge the marker is in stdout: wait for it with `WaitForLineAsync`. `predicate` runs on the pump's own thread as each line is framed, so keep it cheap and non-blocking (the same rule `Command.OnStderrLine` follows); if it throws, this wait fails with that exception and the run itself is unaffected.
|
Full Usage:
this.WaitForStderrTailAsync
Parameters:
Func<string, bool>
timeout : TimeSpan
?cancellationToken : CancellationToken
Returns: Task<Result<string, ProcessError>>
|
Wait until the **unterminated tail** of stderr satisfies `predicate` — everything the child has written since the last line terminator, matched as it grows, without waiting for a terminator that may never come. This is the readiness signal for a newline-free prompt (`Password: `, `Continue? [y/N] `, a progress marker) that `WaitForStderrLineAsync` by construction cannot see: a line pump holds such text in its assembly buffer until a terminator (or EOF) arrives. Returns the tail text that matched. Everything `WaitForStderrLineAsync` documents about framing/encoding, composition with the stdout verbs, the already-consumed refusals, child-exit `NotReady`, pump-fault propagation, the `Unsupported` refusal on a run with no separate stderr, and the predicate running on the pump's thread applies here identically. The one difference is WHAT the predicate is shown: the accumulated tail after each read (`"Pass"`, then `"Password: "` when the rest arrives), plus — for content that had already been framed before this wait armed — those whole lines. A tail that does end up terminated is offered complete, immediately before the pump frames it, so a marker that turns out to have a newline after all still matches here. **A matched tail is not a line, and is delivered nowhere twice.** The text this returns is still in flight in the pump: it reaches `Command.OnStderrLine`, `StderrTee` and the captured `Finished.Stderr` exactly once, later, as part of the line it is eventually framed into (or as the final unterminated line at EOF). No other verb sees it as an extra line, and none loses it. Consuming it here only means the next partial wait starts from what arrives after it. **Bounded retention.** What this wait needs — the accumulated tail, plus the lines framed between one wait and the next — is capped at `OutputBufferPolicy.MaxBytes` when this run set one (`Command.OutputBuffer`), else at 64 KiB. At the cap the tail is force-flushed after being offered one last time, exactly as `MaxBytes` force-flushes an unterminated line in the capture paths, and the retained lines drop oldest-first — so a child that floods stderr with no line terminator at all cannot grow this without bound. A marker larger than that cap is the one thing this cannot match; raise `MaxBytes` if you have one.
|
Static members
| Static member |
Description
|
Full Usage:
RunningProcess.WaitAllAsync(processes)
Parameters:
RunningProcess[]
Returns: Task<Outcome[]>
|
Wait for all of `processes` to exit; returns their outcomes in order. Does not reap them. `processes` must be non-null, non-empty, and free of null elements — each is a programmer error, not a process outcome, so it throws (`ArgumentNullException` for a null array, `ArgumentException` for an empty array or a null element) rather than reporting through a `Result`. Symmetric with `WaitAnyAsync` on all three axes: error channel, empty input, and null handling. If a pump backing one of the `ExitTask`s faults, that exception propagates unchanged from `Task.WhenAll` — also not wrapped in a `Result`.
|
Full Usage:
RunningProcess.WaitAnyAsync(processes)
Parameters:
RunningProcess[]
Returns: Task<WaitAnyResult>
|
Wait for the first of `processes` to exit; returns its index and outcome. Does not reap any of them — dispose them yourself. Safe to call on a handle a buffered verb (`OutputStringAsync`/ `OutputBytesAsync`/`WaitAsync`/`ProfileAsync`) already started: it reuses that verb's own wait (see `ExitTask`) rather than racing a second reader on the same pipes. `processes` must be non-null, non-empty, and free of null elements — each is a programmer error, not a process outcome, so it throws (`ArgumentNullException` for a null array, `ArgumentException` for an empty array or a null element) rather than reporting through a `Result`. Symmetric with `WaitAllAsync` on all three axes: error channel, empty input, and null handling. If a pump backing one of the raced `ExitTask`s faults, that exception propagates unchanged from the awaited task — also not wrapped in a `Result`.
|
ProcessKit API Reference