Quirl0.1 RC
Contributing

Manual PTY job-control check

Canonical Quirl project documentation synced from crates/quirl-process/MANUAL_JOB_CONTROL.md.

Failure model and invariants

The native executor treats pipeline construction as a transaction and terminal ownership as a lease. These are the failure cases that every implementation and real-PTY check must preserve:

  • Partial spawn: every child and descriptor is owned immediately. Failure after any spawn kills the process group, kills each direct child as a fallback, reaps every direct child, closes pending pipe ends, and returns the original construction error.
  • Leader exit before group formation: the parent verifies each child's process-group membership after spawn. It never continues with an assumed group, and cleanup addresses both the group and each direct child so a fast leader cannot strand later stages.
  • Terminal handoff failure: children remain inside the construction guard until handoff succeeds. A failed handoff terminates and reaps them. Once handoff succeeds, an RAII lease restores Quirl's foreground group and saved termios on success, stop, cancellation, wait failure, and unwinding.
  • Stopped child: observing one stopped pipeline member stops every live member before the job is committed. A stopped job retains its children and bounded drain tasks until fg, bg, cancellation, completion, or executor destruction owns the next transition.
  • Cancellation and deadline: each bounded polling turn observes the request state. Cancellation or expiry terminates the whole contained process tree, reaps direct children, drains or closes capture streams, and returns the cancellation error rather than a cleanup error.
  • Drains: capture readers continuously drain child output while retaining only the configured byte budget. Here-string writers remain owned by the job while stopped and are joined after completion or termination.
  • Cleanup failure: cleanup is best-effort across every owned resource; one failed group operation does not skip direct-child cleanup. When an operating error already exists, cleanup cannot replace it. A standalone explicit lifecycle operation reports its own cleanup failure with actionable context.
  • Retained jobs: a NativeExecutor retains at most 1,024 job records. At capacity it refreshes state and removes completed records before accepting a new job; 1,024 still-live records fail early with ResourceLimit.
  • Job IDs: zero is never issued. Allocation wraps from u32::MAX to one and scans the bounded retained table, so an ID cannot collide with a visible job.
  • Redirects: redirect targets are opened in source order and the last input redirect supplies standard input. Earlier opens still take effect or fail, matching shell descriptor-order semantics.
  • Reference dialects: Bash and Zsh compatibility runners are explicitly noninteractive. Their standard input is closed; they remain process-group or Job-Object contained, continuously drain bounded captures, and observe cancellation. Scripts that need interactive reads must be run directly in a terminal outside this compatibility boundary.

Every wait above is either request-bounded or advances in bounded polling turns. Foreground interactive commands may intentionally run until they exit, stop, or receive a terminal signal; they do not retain output in memory.

The canonical Unix PTY harness automates foreground-group ownership, native Ctrl-Z/Ctrl-C, jobs/bg/fg, fast-leader and construction-failure races, stopped-job termios preservation, prompt restoration, and the explicit noninteractive dialect-island policy:

cargo build -p quirl-cli
cargo xtask rich-pty

The following remains a useful release smoke check in a maintainer's terminal after the automated harness passes.

cargo run -p quirl-cli

At the Quirl prompt:

  1. Run sleep 30, press Ctrl-Z, then run jobs. The job must be stopped and the prompt must accept input normally.
  2. Run bg %1, then jobs. The same job id must be running.
  3. Run fg %1, then press Ctrl-C. Quirl must regain the terminal and the next prompt must be usable without an extra keypress.
  4. Run sh -c 'printf out; printf err >&2' | cat. Both streams must complete; Quirl must not hang.
  5. Run printf hidden > /tmp/quirl-process-manual | cat, then cat /tmp/quirl-process-manual. The pipeline prints nothing and the file contains hidden.

Repeat steps 1–3 on both Linux and macOS before calling the Preview job-control gate complete.

On this page