fix: enforce durable Windows launch release barrier

This commit is contained in:
2026-09-06 13:44:50 +00:00
parent 486894557d
commit 3f15377ed5
6 changed files with 82 additions and 24 deletions
@@ -95,13 +95,16 @@ type execProcess struct {
started time.Time
waitFn func() (int32, bool, error)
killFn func(uint32) error
releaseFn func() error
snapshotFn func() (supervisor.ResourceSnapshot, error)
mu sync.Mutex
finished bool
status supervisor.ExitStatus
waitErr error
closeIn sync.Once
mu sync.Mutex
finished bool
status supervisor.ExitStatus
waitErr error
closeIn sync.Once
release sync.Once
releaseErr error
}
type outputResult struct {
@@ -113,6 +116,21 @@ func (process *execProcess) IssueUUID() domain.UUID { return process.issue }
func (process *execProcess) Identity() supervisor.EffectiveIdentity { return process.identity }
// Release is the second half of the durable launch barrier. The portable
// adapter has no suspended native handle, so its release is intentionally a
// no-op; the Windows adapter supplies a ResumeThread closure.
func (process *execProcess) Release(_ context.Context) error {
if process == nil {
return ErrProcessNotReady
}
process.release.Do(func() {
if process.releaseFn != nil {
process.releaseErr = process.releaseFn()
}
})
return process.releaseErr
}
func (process *execProcess) ReadOutput(ctx context.Context) (supervisor.OutputChunk, error) {
if process == nil || process.outputs == nil {
return supervisor.OutputChunk{}, ErrProcessNotReady
@@ -453,15 +471,15 @@ func (manager *execSupervisor) startCommand(ctx context.Context, spec supervisor
}
return code, false, err
}
return manager.registerProcess(spec.IssueUUID, identity, command, stdin, stdout, stderr, started, waitFn, func(uint32) error { return command.Process.Kill() }, cleanup), nil
return manager.registerProcess(spec.IssueUUID, identity, command, stdin, stdout, stderr, started, waitFn, func(uint32) error { return command.Process.Kill() }, nil, cleanup), nil
}
func (manager *execSupervisor) registerProcess(issue domain.UUID, identity supervisor.EffectiveIdentity, command *exec.Cmd, stdin io.WriteCloser, stdout, stderr io.ReadCloser, started time.Time, waitFn func() (int32, bool, error), killFn func(uint32) error, cleanup func()) *execProcess {
func (manager *execSupervisor) registerProcess(issue domain.UUID, identity supervisor.EffectiveIdentity, command *exec.Cmd, stdin io.WriteCloser, stdout, stderr io.ReadCloser, started time.Time, waitFn func() (int32, bool, error), killFn func(uint32) error, releaseFn func() error, cleanup func()) *execProcess {
var pid uint32
if command != nil && command.Process != nil && command.Process.Pid > 0 {
pid = uint32(command.Process.Pid)
}
process := &execProcess{issue: issue, identity: identity, cmd: command, pid: pid, stdin: stdin, stdout: stdout, stderr: stderr, outputs: make(chan outputResult, 32), done: make(chan struct{}), started: started, waitFn: waitFn, killFn: killFn}
process := &execProcess{issue: issue, identity: identity, cmd: command, pid: pid, stdin: stdin, stdout: stdout, stderr: stderr, outputs: make(chan outputResult, 32), done: make(chan struct{}), started: started, waitFn: waitFn, killFn: killFn, releaseFn: releaseFn}
manager.mu.Lock()
manager.active[issue] = process
manager.mu.Unlock()