Help us shape the future of Go! Take the 2026 Go Developer Survey

procgroup

package
v0.37.1 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Oct 21, 2026 License: MIT Imports: 6 Imported by: 0

Documentation

Overview

Package procgroup stops a child process — and, on Unix, every process that child started — when the context it was started under is done.

The defect

exec.CommandContext stops only the DIRECT child. A child that forks helpers (pkgdev's fetchers, make's compilers, a git transport) hands each of them the pipe that cmd.Stdout is copied from, and Wait does not return while any descriptor for that pipe is open. A cancelled run therefore lasted as long as its longest-lived descendant: a download, a compile, or forever for a stalled push. Measured on overlay manifest: 30 s to come back from a cancel delivered at 300 ms, against a grandchild that slept 30 s.

The modes

Configure an unstarted command created by exec.CommandContext with exactly one of them, then start it:

  • Group: the child leads a new process group. On cancel, SIGTERM goes to the whole group; whatever is still running GracePeriod later gets SIGKILL. For children that never read the terminal.
  • Foreground: the child stays in the caller's process group, which is the terminal's foreground group, so a password or passphrase prompt keeps working. On cancel, SIGTERM goes to the direct child only; if it is still running GracePeriod later, os/exec SIGKILLs it.
  • KillGroupNow: overlay manifest's original behaviour — its own group, SIGKILL to the whole group at once, no grace.

Each mode replaces cmd.Cancel. Group and Foreground also set cmd.WaitDelay to GracePeriod, so Wait returns within GracePeriod of the cancel even while a process that escaped the signals (one that called setsid) still holds the output pipe: os/exec then stops waiting for the pipe. Pass Wait's (or Run's) error through Result.

What group mode costs

A child in its own group no longer receives the Ctrl+C typed at the terminal. It is stopped only through the caller's context, so every command that starts a group-mode child must cancel that context on SIGINT and SIGTERM. A child that reads the terminal from a background group is stopped by SIGTTIN, so a group-mode child needs a standard input that is not the terminal.

Outside Unix

There is no process group to signal. Every mode only sets cmd.WaitDelay, which stops the WAIT for a descendant but leaves the descendant running.

Index

Constants

View Source
const GracePeriod = 5 * time.Second

GracePeriod is how long a cancelled child and its descendants get between SIGTERM and SIGKILL, and how long Wait keeps draining their output after the cancel. A caller that bounds a child by a deadline therefore returns within that deadline plus GracePeriod.

Variables

This section is empty.

Functions

func Foreground

func Foreground(cmd *exec.Cmd)

Foreground configures the unstarted cmd for a child that must stay in the caller's process group — the terminal's foreground group — because it may prompt there: sudo's password, git's ssh passphrase or https credentials. A child moved to a background group would be stopped by SIGTTIN at its first read, and the operator could no longer signal it from the terminal.

When the context is done, cmd.Cancel sends SIGTERM to the direct child only; sudo relays SIGTERM to the command it runs, though never SIGKILL. If the child is still running GracePeriod later, os/exec SIGKILLs it: cmd.WaitDelay is GracePeriod, and once it expires os/exec calls Process.Kill, which reaches the direct child — here, exactly the process to stop.

A child that has already exited is not an error (os.ErrProcessDone). Any other refusal — EPERM from a child now running as another user — is returned wrapped, naming the pid.

func Group

func Group(cmd *exec.Cmd)

Group configures the unstarted cmd so that, when its context is done, the child and every process it started are stopped:

  1. at Start, the child becomes the leader of a new process group;
  2. cmd.Cancel sends SIGTERM to the whole group at once;
  3. GracePeriod later, whatever is left of the group gets SIGKILL;
  4. cmd.WaitDelay is GracePeriod, so Wait returns by then even if a process that left the group (setsid) still holds the output pipe.

Step 3 is a timer armed by Cancel, not left to WaitDelay, because WaitDelay's Process.Kill reaches the group LEADER only, leaving a SIGTERM-ignoring descendant orphaned and running. A group already gone is not an error (os.ErrProcessDone); any other refusal is returned wrapped, naming the group.

Only Setpgid (and a zero Pgid, so the child LEADS the group and -pid names it) is written into cmd.SysProcAttr; every other field the caller set there is kept. The package documentation says what leaving the terminal's foreground group costs.

func KillGroupNow

func KillGroupNow(cmd *exec.Cmd)

KillGroupNow makes a cancelled run's child die together with everything it spawned, so cmd.Wait comes back when the work stops rather than when the last orphan finishes. It is how overlay manifest stops pkgdev; new spawners use Group. The child leads a new process group, and on cancel cmd.Cancel sends SIGKILL to the whole group at once — no SIGTERM, no grace, no WaitDelay. A group already gone is os.ErrProcessDone; any other refusal is returned unwrapped, as kill(2) gave it, as overlay manifest always reported.

Why the group and not WaitDelay: pkgdev's fetchers inherit the stdout pipe, so a cancelled run lasted as long as a grandchild (30.002s against a 30s sleeper, cancel at 300ms) and could not render its partial report. WaitDelay returns by ABANDONING those fetchers onto a distdir about to be deleted, and its timer also fires after a NORMAL exit, turning a lingering helper into a reported failure. Killing the group ends the work: 301ms.

The cost: ctrl+c no longer reaches pkgdev directly but through signal.NotifyContext in `overlay manifest`; a setsid() descendant escapes (it would hang an uncancelled run anyway); and SIGKILL stays because it is what exec.CommandContext already sent. Group is the mode that asks first.

func Result

func Result(cmd *exec.Cmd, err error) error

Result returns the error a caller should act on after Wait (or Run) of a command configured by this package: err itself, except that exec.ErrWaitDelay after a successful exit is success.

WaitDelay's timer also starts when Wait sees the child exit NORMALLY. A child that exits 0 while a helper it left behind still holds the output pipe makes Wait return exec.ErrWaitDelay once the delay expires: an error for a command whose work finished. os/exec returns ErrWaitDelay only after a successful exit status, and Result checks that status as well, so a non-zero exit, a signal or a cancelled context always stays a failure.

Types

This section is empty.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL