Skip to main content

run_bounded

Function run_bounded 

Source
pub fn run_bounded(spec: &CommandSpec) -> Result<CommandOutput>
Expand description

Executes a command with cleared environment, bounded stdin, bounded output, and kill/reap.

The wall-clock deadline set by CommandSpec::timeout is an unconditional maximum bound on the whole bounded execution. It is evaluated on every poll whether or not the direct child has already exited, so this call always returns. The deadline starts once the spawn has returned, so a blocking spawn is not covered by it.

Stdin is null unless the caller attached a payload with CommandSpec::stdin_data. An attached payload is bounded by MAX_STDIN_BYTES, inclusively: a larger payload is rejected with Error::InvalidInput before any child is spawned, so no process is created for it. A payload within the bound is written by a dedicated writer thread, which closes the pipe when it ends, so the child observes EOF once the payload has been delivered. The writer thread is started only after the output readers are attached, so a child that fills stdout or stderr before draining its stdin cannot deadlock both sides.

The deadline bounds the stdin writer exactly as it bounds the output readers: when the deadline is reached, the direct child is killed, which breaks the child’s stdin pipe for a child that is still running and whose stdin pipe no descendant inherited, and a writer that is still blocked in write is abandoned explicitly, exactly like a reader blocked on a pipe a surviving descendant holds. Killing the direct child does not break the pipe for a descendant that inherited it: that descendant keeps the write end open, so the writer stays blocked until the descendant exits or closes the pipe, and the writer is reclaimed only then. A bounded execution is not reported as complete until the writer has delivered its verdict, so a write failure is never lost behind a “the child exited” observation.

§The deadline and a stdin write failure

The deadline has priority over the writer’s verdict. The writer stamps its verdict with the instant at which it observed the write’s completion, so whether a write failure happened before or after the deadline is a fact about the write itself and never a fact about when this call happened to poll for the verdict:

  • a write failure stamped before the deadline is an Err: the payload was not delivered, so the child is killed and reaped and the call fails closed rather than reporting a partial success;
  • a write failure stamped at or after the deadline does not turn the deadline outcome into an Err: the deadline already decided that outcome, so the call returns Ok with timed_out set;
  • a writer that had not delivered its verdict when the deadline was reached is abandoned, exactly like a reader blocked on a pipe a surviving descendant holds. Its verdict is never observed, and the deadline outcome stands.

A payload that was not delivered is therefore never reported as a success: the outcome is either an Err or an Ok whose timed_out flag is set.

A descendant of the direct child can inherit the stdout or stderr pipe and hold it open after the direct child has exited. Core terminates only the direct child it spawned, so such a descendant is never killed: the call returns at the deadline, the result reports timed_out together with the direct child’s exit code, and the stream the descendant still holds contributes no bytes, because they were never captured.

The only work that may extend past the deadline is the kill and reap of a direct child that is still running. That allowance is bounded by the child’s own termination; it is not a second, larger deadline.

A reader thread that delivered its result is joined, which is bounded because a reader only has to return after its send. A reader that was still blocked in read() when the bound was reached cannot be joined without waiting past the deadline, and core cannot terminate a descendant it does not own: it has no process-group or job-object dependency and unsafe is denied. Such a reader is abandoned explicitly, and it ends when the pipe reaches EOF, that is, when the holding descendant exits or closes the pipe. The same rule applies to the stdin writer.

The caller observes four distinguishable outcomes:

  • the child ran to completion: Ok, with the exit code, and timed_out and output_limited both false;
  • the deadline was reached: Ok, with timed_out set; the direct child is killed only if it was still running. This outcome also covers a stdin payload that was never delivered, so an undelivered payload is never reported as a success;
  • a stream exceeded max_output_bytes: Ok, with output_limited set and that stream’s captured bytes discarded;
  • the command could not be spawned or polled, or the stdin payload could not be written: Err.

§Error classification

The operation string of Error::Io is a stable classification surface for this function: a downstream adapter may branch on it, so these three strings are part of the published contract.

  • "spawning bounded candidate" — the child could not be spawned. Nothing was created, so the caller should treat it as an environment problem and may retry under its own policy.
  • "polling bounded candidate" — the child’s status could not be polled. The child may still be running, so the caller must not treat the outcome as a completed execution.
  • "writing bounded stdin" — the stdin payload could not be written, which includes the broken pipe a child produces when it exits without reading its stdin. The child is killed and reaped before the error is returned, so the payload was not delivered and the caller must not treat the execution as complete.

When a failed stdin write and an output overflow are live at the same time, the write failure wins: it is an Err, and the caller must not be told that the execution merely overflowed.

§Errors

Returns Error::InvalidInput when the attached stdin payload is larger than MAX_STDIN_BYTES. Nothing is spawned in that case.

Returns Error::Io when the command cannot be spawned, when polling the child fails, or when the stdin payload cannot be written. The operation string distinguishes the three cases; see Error classification.

Returns Error::CandidateExecution when the spawned child’s stdout, stderr, or stdin pipe is missing.

A command that ran to completion, that reached the deadline, or that exceeded its output bound is not an error: those outcomes are reported through CommandOutput::timed_out, CommandOutput::output_limited, and CommandOutput::exit_code on an Ok result. A reader error is surfaced as output_limited, never as an Err.