fix(auth): close cancellation and workspace races

This commit is contained in:
2026-08-17 19:32:03 +02:00
parent d12b629420
commit 0a2c667231
10 changed files with 822 additions and 221 deletions
@@ -1,26 +1,50 @@
package compose
import "time"
import (
"errors"
"os"
"time"
)
// terminateWindowsProcess reports a reap failure only when the process has not exited within the
// final bound. taskkill and Process.Kill can both race with a child that has already been reaped.
func terminateWindowsProcess(
// 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 {
if processReaped(done) {
return nil
treeErr := terminateTree()
var directErr error
if treeErr != nil {
directErr = terminateDirect()
if errors.Is(directErr, os.ErrProcessDone) {
directErr = nil
}
}
_ = terminateTree()
if processReaped(done) {
return nil
}
_ = terminateDirect()
if processReaped(done) {
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 {
@@ -30,12 +54,3 @@ func terminateWindowsProcess(
return ErrProcessReap
}
}
func processReaped(done <-chan error) bool {
select {
case <-done:
return true
default:
return false
}
}