Skip to content

brandonkramer/proctree

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

22 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

proctree

Cross-platform Go helpers for running shell and exec commands in an isolated process group or tree, streaming stdout/stderr, killing the full child tree on context cancellation or timeout, and inspecting process state.

Install

From pkg.go.dev:

go get github.com/brandonkramer/proctree

Quick start

Shell command

ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second)
defer cancel()

var buf bytes.Buffer
spec := &proctree.Spec{Shell: "make test"}
opts := &proctree.Options{
    OnStdout: func(line string) { fmt.Println("out:", line) },
    Stdout:   &buf,
}
res, err := proctree.Run(ctx, spec, opts)
if err != nil {
    // context.Canceled, context.DeadlineExceeded, or non-zero exit
}
fmt.Println("exit", res.ExitCode, "pid", res.PID, "started", res.StartedAt)

Exec (argv) — preferred when possible

res, err := proctree.Run(ctx, &proctree.Spec{
    Path: "/usr/bin/git",
    Args: []string{"status", "--short"},
    Dir:  "/path/to/repo",
    Env:  []string{"GIT_TERMINAL_PROMPT=0"},
}, &proctree.Options{
    Stdin: strings.NewReader(""),
})

Recovery reconcile

res := proctree.ReconcilePID(pid, &proctree.Ownership{
    Spec:      *spec,
    StartedAt: startedAt,
})
switch res.Outcome {
case proctree.ReconcileKilled:
    // verified and terminated
case proctree.ReconcileUnverified:
    // alive but not ours — left running (fail closed)
case proctree.ReconcileNotRunning:
    // already gone
}

Tee stdout/stderr to logs + callbacks

opts := proctree.TeeOptions(stdoutFile, stderrFile,
    func(line string) { emit("stdout", line) },
    func(line string) { emit("stderr", line) },
)
proctree.Run(ctx, spec, opts)

Ownership verification (PID reuse safe)

own := &proctree.Ownership{
    Spec:      *spec,
    StartedAt: res.StartedAt,
}
if proctree.VerifyOwnership(pid, own) {
    _ = proctree.KillTreeByPID(pid)
}

Custom matcher for processes that do not match Spec shape:

own := &proctree.Ownership{
    StartedAt: res.StartedAt,
    Match: func(parts []string) bool {
        return len(parts) > 0 && strings.Contains(parts[0], "my-worker")
    },
}
proctree.VerifyOwnershipOpts(pid, own, &proctree.VerifyOptions{
    SkipProcessGroup: true,
})

VerifyOwnership checks cmdline match, Unix process-group leader (when applicable), and optional create-time window. Fails closed when uncertain.

Introspection and recovery

info, err := proctree.Inspect(pid)
// info.Cmdline, info.CreateTime, info.MemoryRSS, info.Status, info.OpenFiles (linux)

kids, _ := proctree.Children(pid)
tree, _ := proctree.InspectTree(pid) // pid + descendants

Low-level helpers

cmd := proctree.NewCommand(&proctree.Spec{Shell: "sleep 300"})
cmd.Start()
proctree.KillTree(cmd)

proctree.KillTreeByPID(pid)
proctree.Alive(pid)
proctree.VerifyOwned(pid, spec)

Platform behavior

Platform Process isolation Tree kill Alive Inspect sources Ownership verify
Linux Setpgid SIGKILL to -pid /proc stat (skip zombies) /proc cmdline + pgid + create time
macOS Setpgid SIGKILL to -pid and pid sysctl stat (skip zombies) sysctl (KinfoProc, procargs2) cmdline + pgid + create time
Windows process group + Job Object TerminateJobObject (fallback: descendant walk) OpenProcess Toolhelp + NT APIs cmdline + create time

Windows notes

  • Run assigns each child to a kill-on-close Job Object when the OS allows it.
  • Inspect uses NtQueryInformationProcess, GetProcessTimes, and GetProcessMemoryInfo (no wmic).

Unix zombie processes

Alive consults /proc on Linux and sysctl process state on macOS so zombies count as not running. Use VerifyOwnership or ReconcilePID before killing stale pids.

API surface

Execution

  • Run(ctx, spec, opts) — context-first execution with streaming, optional stdin/writer sinks, tree kill on cancel/timeout
  • NewCommand(spec) — build a configured *exec.Cmd without starting
  • KillTree(cmd) / KillTreeByPID(pid) — terminate process trees
  • Alive(pid) — liveness probe

Recovery

  • ReconcilePID(pid, own) / ReconcilePIDOpts(pid, own, opts) — verify-and-kill for daemon recovery
  • WaitNotAlive(pid, timeout) — poll until process exits

Streaming helpers

  • TeeLine(w, fn) / TeeOptions(stdoutW, stderrW, onStdout, onStderr) — mirror lines to writers and callbacks

Verification

  • VerifyOwned(pid, spec) — cmdline + platform checks
  • VerifyOwnership(pid, own) — adds create-time window and optional CmdlineMatcher
  • VerifyOwnershipOpts(pid, own, opts) — tune timing, matcher, and SkipProcessGroup

Introspection

  • Inspect(pid) — point-in-time ProcessInfo snapshot
  • InspectTree(root) — snapshots for root + descendants
  • Children(pid) / Descendants(root) — process tree discovery
  • Cmdline(pid) / CreateTime(pid) — convenience accessors

Development

Lefthook and golangci-lint are pinned in go.mod as tools (dev-only). Install git hooks once per clone:

make install-hooks

Hooks and make lint use go tool binaries from go.mod. Pre-commit lints staged .go files; pre-push runs ./scripts/check.sh. Skip hooks with LEFTHOOK=0 git push.

make check
make test
make lint

Releases

Tagged semver releases are published to pkg.go.dev. See GitHub releases for notes.

License

MIT

About

Cross-platform Go helpers for running shell and exec commands in an isolated process group or tree, streaming stdout/stderr, killing the full child tree on context cancellation or timeout, and inspecting process state.

Topics

Resources

License

Stars

0 stars

Watchers

0 watching

Forks

Packages

 
 
 

Contributors

Languages