57 lines
1.6 KiB
Go
57 lines
1.6 KiB
Go
package compose
|
|
|
|
import (
|
|
"errors"
|
|
"os"
|
|
"time"
|
|
)
|
|
|
|
// processLifetime owns a started Docker CLI process until it has been reaped and every process
|
|
// it may have started has been released. Platform implementations must make close idempotent.
|
|
type processLifetime interface {
|
|
terminate(done <-chan error) error
|
|
close() error
|
|
}
|
|
|
|
var startProcessForRunner = startProcess
|
|
|
|
var terminateProcessForRunner = func(process processLifetime, done <-chan error) error {
|
|
return process.terminate(done)
|
|
}
|
|
|
|
// terminateProcessTree terminates the owned tree before consulting the direct child's reaping
|
|
// result. A direct child can be reaped while descendants survive, so a ready done channel is never
|
|
// evidence that tree termination can be skipped. The direct kill is only a fallback when tree
|
|
// ownership could not terminate the tree; an already-reaped direct child is benign in that case.
|
|
func terminateProcessTree(
|
|
done <-chan error,
|
|
terminateTree func() error,
|
|
terminateDirect func() error,
|
|
bound time.Duration,
|
|
) error {
|
|
treeErr := terminateTree()
|
|
var directErr error
|
|
if treeErr != nil {
|
|
directErr = terminateDirect()
|
|
if errors.Is(directErr, os.ErrProcessDone) {
|
|
directErr = nil
|
|
}
|
|
}
|
|
reapErr := waitForProcessReap(done, bound)
|
|
if treeErr == nil && directErr == nil && reapErr == nil {
|
|
return nil
|
|
}
|
|
return errors.Join(ErrProcessReap, treeErr, directErr, reapErr)
|
|
}
|
|
|
|
func waitForProcessReap(done <-chan error, bound time.Duration) error {
|
|
timer := time.NewTimer(bound)
|
|
defer timer.Stop()
|
|
select {
|
|
case <-done:
|
|
return nil
|
|
case <-timer.C:
|
|
return ErrProcessReap
|
|
}
|
|
}
|