2026-08-10 18:44:10 +00:00
package subprocess
import (
"context"
"errors"
"fmt"
"os/exec"
"time"
)
const (
gracefulTerminationWait = 2 * time . Second
forcefulTerminationWait = 2 * time . Second
)
// ownedProcessTree owns every process started by a command invocation.
// Implementations must tolerate a leader that has already exited.
type ownedProcessTree interface {
Start ( * exec . Cmd ) error
TerminateGracefully ( ) error
TerminateForcefully ( ) error
Close ( ) error
}
2026-08-10 18:55:49 +00:00
func waitForOwnedCommand ( ctx context . Context , tree ownedProcessTree , waitCh <- chan error , captureLimits <- chan * captureLimitError ) ( waitErr , ctxErr error , captureLimit * captureLimitError , cleanupErr error ) {
2026-08-10 18:44:10 +00:00
select {
case waitErr = <- waitCh :
2026-08-10 18:55:49 +00:00
return waitErr , nil , nil , nil
2026-08-10 18:44:10 +00:00
case <- ctx . Done ( ) :
ctxErr = ctx . Err ( )
2026-08-10 18:55:49 +00:00
case captureLimit = <- captureLimits :
2026-08-10 18:44:10 +00:00
}
cleanupErr = tree . TerminateGracefully ( )
gracefulTimer := time . NewTimer ( gracefulTerminationWait )
defer gracefulTimer . Stop ( )
select {
case waitErr = <- waitCh :
// The leader may exit before descendants finish graceful shutdown.
cleanupErr = joinErrors ( cleanupErr , tree . TerminateForcefully ( ) )
2026-08-10 18:55:49 +00:00
return waitErr , ctxErr , captureLimit , cleanupErr
2026-08-10 18:44:10 +00:00
case <- gracefulTimer . C :
}
cleanupErr = joinErrors ( cleanupErr , tree . TerminateForcefully ( ) )
forcefulTimer := time . NewTimer ( forcefulTerminationWait )
defer forcefulTimer . Stop ( )
select {
case waitErr = <- waitCh :
2026-08-10 18:55:49 +00:00
return waitErr , ctxErr , captureLimit , cleanupErr
2026-08-10 18:44:10 +00:00
case <- forcefulTimer . C :
2026-08-10 18:55:49 +00:00
return nil , ctxErr , captureLimit , joinErrors ( cleanupErr , fmt . Errorf ( "owned subprocess did not reap within %s after forceful termination" , forcefulTerminationWait ) )
2026-08-10 18:44:10 +00:00
}
}
func joinErrors ( errs ... error ) error {
filtered := make ( [ ] error , 0 , len ( errs ) )
for _ , err := range errs {
if err != nil {
filtered = append ( filtered , err )
}
}
return errors . Join ( filtered ... )
}