Dispose subprocess descendants after leader exit
This commit is contained in:
@@ -4,11 +4,15 @@ package subprocess
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"os"
|
||||
"os/exec"
|
||||
"syscall"
|
||||
"time"
|
||||
)
|
||||
|
||||
const processGroupPollInterval = 10 * time.Millisecond
|
||||
|
||||
type unixProcessTree struct {
|
||||
processGroupID int
|
||||
}
|
||||
@@ -34,8 +38,26 @@ func (tree *unixProcessTree) TerminateForcefully() error {
|
||||
return tree.signal(syscall.SIGKILL)
|
||||
}
|
||||
|
||||
func (tree *unixProcessTree) Close() error {
|
||||
return nil
|
||||
func (tree *unixProcessTree) Dispose() error {
|
||||
hasMembers, err := tree.hasMembers()
|
||||
if err != nil || !hasMembers {
|
||||
return err
|
||||
}
|
||||
|
||||
cleanupErr := tree.TerminateGracefully()
|
||||
empty, waitErr := tree.waitUntilEmpty(gracefulTerminationWait)
|
||||
cleanupErr = joinErrors(cleanupErr, waitErr)
|
||||
if empty {
|
||||
return cleanupErr
|
||||
}
|
||||
|
||||
cleanupErr = joinErrors(cleanupErr, tree.TerminateForcefully())
|
||||
empty, waitErr = tree.waitUntilEmpty(forcefulTerminationWait)
|
||||
cleanupErr = joinErrors(cleanupErr, waitErr)
|
||||
if !empty {
|
||||
cleanupErr = joinErrors(cleanupErr, fmt.Errorf("owned subprocess group did not exit within %s after forceful termination", forcefulTerminationWait))
|
||||
}
|
||||
return cleanupErr
|
||||
}
|
||||
|
||||
func (tree *unixProcessTree) signal(signal syscall.Signal) error {
|
||||
@@ -48,3 +70,35 @@ func (tree *unixProcessTree) signal(signal syscall.Signal) error {
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
func (tree *unixProcessTree) hasMembers() (bool, error) {
|
||||
if tree.processGroupID <= 0 {
|
||||
return false, nil
|
||||
}
|
||||
err := syscall.Kill(-tree.processGroupID, 0)
|
||||
if err == nil || errors.Is(err, syscall.EPERM) {
|
||||
return true, nil
|
||||
}
|
||||
if errors.Is(err, syscall.ESRCH) || errors.Is(err, os.ErrProcessDone) {
|
||||
return false, nil
|
||||
}
|
||||
return false, fmt.Errorf("inspect owned subprocess group: %w", err)
|
||||
}
|
||||
|
||||
func (tree *unixProcessTree) waitUntilEmpty(timeout time.Duration) (bool, error) {
|
||||
deadline := time.Now().Add(timeout)
|
||||
for {
|
||||
hasMembers, err := tree.hasMembers()
|
||||
if err != nil || !hasMembers {
|
||||
return !hasMembers, err
|
||||
}
|
||||
remaining := time.Until(deadline)
|
||||
if remaining <= 0 {
|
||||
return false, nil
|
||||
}
|
||||
if remaining > processGroupPollInterval {
|
||||
remaining = processGroupPollInterval
|
||||
}
|
||||
time.Sleep(remaining)
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user