91 lines
2.3 KiB
Go
91 lines
2.3 KiB
Go
package subprocess
|
|
|
|
import (
|
|
"context"
|
|
"os/exec"
|
|
"time"
|
|
)
|
|
|
|
type executionReason uint8
|
|
|
|
const (
|
|
executionNotStarted executionReason = iota
|
|
executionNaturalExit
|
|
executionCanceled
|
|
executionDeadline
|
|
executionAborted
|
|
)
|
|
|
|
type executionOutcome struct {
|
|
reason executionReason
|
|
startedAt time.Time
|
|
completedAt time.Time
|
|
exitCode int
|
|
startErr error
|
|
waitErr error
|
|
contextErr error
|
|
abortErr error
|
|
cleanupErr error
|
|
}
|
|
|
|
func executeOwnedCommand(ctx context.Context, cmd *exec.Cmd, abort <-chan error) (outcome executionOutcome) {
|
|
outcome = executionOutcome{startedAt: time.Now().UTC(), exitCode: -1}
|
|
defer func() { outcome.completedAt = time.Now().UTC() }()
|
|
if outcome.contextErr = ctx.Err(); outcome.contextErr != nil {
|
|
outcome.reason = executionNotStarted
|
|
return outcome
|
|
}
|
|
tree, err := newOwnedProcessTree()
|
|
if err != nil {
|
|
outcome.reason, outcome.startErr = executionNotStarted, err
|
|
return outcome
|
|
}
|
|
defer func() { outcome.cleanupErr = joinErrors(outcome.cleanupErr, tree.Dispose()) }()
|
|
cmd.WaitDelay = forcefulTerminationWait
|
|
if outcome.startErr = tree.Start(cmd); outcome.startErr != nil {
|
|
outcome.reason = executionNotStarted
|
|
return outcome
|
|
}
|
|
waitCh := make(chan error, 1)
|
|
go func() { waitCh <- cmd.Wait() }()
|
|
select {
|
|
case outcome.waitErr = <-waitCh:
|
|
outcome.reason = executionNaturalExit
|
|
case <-ctx.Done():
|
|
outcome.contextErr = ctx.Err()
|
|
if outcome.contextErr == context.DeadlineExceeded {
|
|
outcome.reason = executionDeadline
|
|
} else {
|
|
outcome.reason = executionCanceled
|
|
}
|
|
outcome.cleanupErr = terminateAndReap(tree, waitCh)
|
|
case outcome.abortErr = <-abort:
|
|
outcome.reason = executionAborted
|
|
outcome.cleanupErr = terminateAndReap(tree, waitCh)
|
|
}
|
|
if cmd.ProcessState != nil {
|
|
outcome.exitCode = cmd.ProcessState.ExitCode()
|
|
}
|
|
return outcome
|
|
}
|
|
|
|
func terminateAndReap(tree ownedProcessTree, waitCh <-chan error) error {
|
|
cleanupErr := tree.TerminateGracefully()
|
|
timer := time.NewTimer(gracefulTerminationWait)
|
|
defer timer.Stop()
|
|
select {
|
|
case <-waitCh:
|
|
return joinErrors(cleanupErr, tree.TerminateForcefully())
|
|
case <-timer.C:
|
|
}
|
|
cleanupErr = joinErrors(cleanupErr, tree.TerminateForcefully())
|
|
forcefulTimer := time.NewTimer(forcefulTerminationWait)
|
|
defer forcefulTimer.Stop()
|
|
select {
|
|
case <-waitCh:
|
|
return cleanupErr
|
|
case <-forcefulTimer.C:
|
|
return joinErrors(cleanupErr, context.DeadlineExceeded)
|
|
}
|
|
}
|