166 lines
4.5 KiB
Go
166 lines
4.5 KiB
Go
package utility
|
|
|
|
import (
|
|
"context"
|
|
"os"
|
|
"os/exec"
|
|
"time"
|
|
|
|
"go.uber.org/zap"
|
|
)
|
|
|
|
func configureACPCommand(cmd *exec.Cmd, log *zap.Logger) {
|
|
log = acpCommandLogger(log)
|
|
setACPCommandProcAttr(cmd)
|
|
cmd.Cancel = func() error {
|
|
if cmd.Process == nil {
|
|
return os.ErrProcessDone
|
|
}
|
|
log.Debug("ACP command process group SIGTERM requested",
|
|
zap.Int("pgid", cmd.Process.Pid),
|
|
zap.String("reason", "context_cancel"))
|
|
if err := terminateACPProcessGroup(cmd.Process.Pid); err != nil {
|
|
if acpProcessGroupMissing(err) {
|
|
return os.ErrProcessDone
|
|
}
|
|
log.Debug("ACP command process group SIGTERM failed",
|
|
zap.Int("pgid", cmd.Process.Pid),
|
|
zap.String("reason", "context_cancel"),
|
|
zap.Error(err))
|
|
return err
|
|
}
|
|
return nil
|
|
}
|
|
}
|
|
|
|
func cleanupACPCommand(ctx context.Context, cmd *exec.Cmd, lifecycle acpCommandLifecycleHandle, log *zap.Logger) {
|
|
log = acpCommandLogger(log)
|
|
defer releaseACPCommandLifecycle(lifecycle)
|
|
if cmd == nil {
|
|
log.Debug("ACP command cleanup skipped", zap.String("reason", "nil_command"))
|
|
return
|
|
}
|
|
if cmd.Process == nil {
|
|
log.Debug("ACP command cleanup skipped", zap.String("reason", "nil_process"))
|
|
return
|
|
}
|
|
pid := cmd.Process.Pid
|
|
cleanupCtx, cancel := context.WithTimeout(
|
|
context.WithoutCancel(ctx),
|
|
2*acpCommandTerminateGrace+2*acpCommandForceKillGrace,
|
|
)
|
|
defer cancel()
|
|
log.Debug("ACP command cleanup requested",
|
|
zap.Int("pid", pid),
|
|
zap.String("path", cmd.Path),
|
|
zap.Strings("args", cmd.Args))
|
|
log.Debug("ACP command process group SIGTERM requested",
|
|
zap.Int("pgid", pid),
|
|
zap.String("reason", "cleanup"))
|
|
if err := terminateACPProcessGroup(pid); err != nil && !acpProcessGroupMissing(err) {
|
|
log.Debug("ACP command process group SIGTERM failed",
|
|
zap.Int("pgid", pid),
|
|
zap.String("reason", "cleanup"),
|
|
zap.Error(err))
|
|
}
|
|
|
|
waitCh := make(chan error, 1)
|
|
go func() {
|
|
waitCh <- cmd.Wait()
|
|
}()
|
|
|
|
if waitForACPCommand(cleanupCtx, waitCh, acpCommandTerminateGrace) {
|
|
log.Debug("ACP command exited after SIGTERM", zap.Int("pid", pid))
|
|
reapACPProcessGroup(cleanupCtx, pid, log)
|
|
return
|
|
}
|
|
|
|
log.Debug("ACP command process group SIGKILL requested",
|
|
zap.Int("pgid", pid),
|
|
zap.String("reason", "cleanup_timeout"))
|
|
if err := killACPProcessGroup(pid); err != nil && !acpProcessGroupMissing(err) {
|
|
log.Debug("ACP command process group SIGKILL failed; killing process",
|
|
zap.Int("pgid", pid),
|
|
zap.Int("pid", pid),
|
|
zap.Error(err))
|
|
log.Debug("ACP command process SIGKILL requested",
|
|
zap.Int("pid", pid),
|
|
zap.String("reason", "process_group_kill_failed"))
|
|
_ = cmd.Process.Kill()
|
|
}
|
|
_ = waitForACPCommand(cleanupCtx, waitCh, acpCommandForceKillGrace)
|
|
reapACPProcessGroup(cleanupCtx, pid, log)
|
|
}
|
|
|
|
func waitForACPCommand(ctx context.Context, waitCh <-chan error, timeout time.Duration) bool {
|
|
timer := time.NewTimer(timeout)
|
|
defer timer.Stop()
|
|
select {
|
|
case <-waitCh:
|
|
return true
|
|
case <-ctx.Done():
|
|
return false
|
|
case <-timer.C:
|
|
return false
|
|
}
|
|
}
|
|
|
|
func reapACPProcessGroup(ctx context.Context, pid int, log *zap.Logger) {
|
|
log = acpCommandLogger(log)
|
|
if !acpProcessGroupAlive(pid) {
|
|
return
|
|
}
|
|
log.Debug("ACP command process group still alive; SIGTERM requested",
|
|
zap.Int("pgid", pid),
|
|
zap.String("reason", "reap_descendants"))
|
|
if err := terminateACPProcessGroup(pid); err != nil && !acpProcessGroupMissing(err) {
|
|
log.Debug("ACP command process group SIGTERM failed",
|
|
zap.Int("pgid", pid),
|
|
zap.String("reason", "reap_descendants"),
|
|
zap.Error(err))
|
|
}
|
|
waitForACPProcessGroupExit(ctx, pid, acpCommandTerminateGrace)
|
|
if !acpProcessGroupAlive(pid) {
|
|
return
|
|
}
|
|
log.Debug("ACP command process group SIGKILL requested",
|
|
zap.Int("pgid", pid),
|
|
zap.String("reason", "reap_descendants_timeout"))
|
|
if err := killACPProcessGroup(pid); err != nil && !acpProcessGroupMissing(err) {
|
|
log.Debug("ACP command process group SIGKILL failed",
|
|
zap.Int("pgid", pid),
|
|
zap.String("reason", "reap_descendants_timeout"),
|
|
zap.Error(err))
|
|
}
|
|
waitForACPProcessGroupExit(ctx, pid, acpCommandForceKillGrace)
|
|
}
|
|
|
|
func acpCommandLogger(log *zap.Logger) *zap.Logger {
|
|
if log == nil {
|
|
return zap.NewNop()
|
|
}
|
|
return log
|
|
}
|
|
|
|
func waitForACPProcessGroupExit(ctx context.Context, pid int, timeout time.Duration) bool {
|
|
if !acpProcessGroupAlive(pid) {
|
|
return true
|
|
}
|
|
timer := time.NewTimer(timeout)
|
|
defer timer.Stop()
|
|
ticker := time.NewTicker(acpCommandPollInterval)
|
|
defer ticker.Stop()
|
|
for {
|
|
select {
|
|
case <-ctx.Done():
|
|
return false
|
|
case <-timer.C:
|
|
return false
|
|
case <-ticker.C:
|
|
if !acpProcessGroupAlive(pid) {
|
|
return true
|
|
}
|
|
}
|
|
}
|
|
}
|