Files
Gitea/modules/process/command.go
T
Royce Remer eea03676d3 fix(process): reap entire process group on cmd.Cancel (#39143)
Signed-off-by: Royce Remer <royceremer@gmail.com>
Co-authored-by: wxiaoguang <wxiaoguang@gmail.com>
2026-08-29 19:54:07 +00:00

55 lines
1.5 KiB
Go

// Copyright 2026 The Gitea Authors. All rights reserved.
// SPDX-License-Identifier: MIT
package process
import (
"bytes"
"context"
"os/exec"
)
type Cmd struct {
*exec.Cmd
onCancelUserFunc func() error
termGraceful bool
}
func (c *Cmd) WithOnCancelGracefully(userFunc func() error) *Cmd {
c.termGraceful, c.onCancelUserFunc = true, userFunc
return c
}
func (c *Cmd) WithOnCancelForceKill(userFunc func() error) *Cmd {
c.termGraceful, c.onCancelUserFunc = false, userFunc
return c
}
func (c *Cmd) WithDir(dir string) *Cmd {
c.Cmd.Dir = dir
return c
}
func (c *Cmd) OutputString() (string, string, error) {
stdout, stderr := &bytes.Buffer{}, &bytes.Buffer{}
c.Cmd.Stdout = stdout
c.Cmd.Stderr = stderr
err := c.Cmd.Run()
return stdout.String(), stderr.String(), err
}
// CommandContext returns a wrapped exec.Cmd which kills the process group when the context is canceled.
// By default, it uses graceful termination (SIGTERM) on Unix-like systems to make the subprocesses have chances
// to clean up (e.g. remove temporary files or lock files).
func CommandContext(ctx context.Context, name string, arg ...string) *Cmd {
c := &Cmd{Cmd: exec.CommandContext(ctx, name, arg...)} //nolint:forbidigo // wrap it
setSysProcAttribute(c.Cmd)
c.Cmd.Cancel = c.onCancel
// Unlike exec.CommandContext, we use graceful termination by default to avoid corrupting data or leaving lock files behind.
// If some processes don't respond to SIGTERM, can switch to WithOnCancelForceKill (SIGKILL) to force kill them.
c.termGraceful = true
return c
}