Files
zero/internal/specialist/exec_proc_unix.go
KRATOS 9e800b4ffc fix five concurrency/leak bugs (swarm, specialist, oauth, mcp, cron) (#265)
Surfaced by a full-tree audit; each ships a regression test where the
behavior is deterministically testable.

- swarm: deliver a handoff's mailbox note BEFORE registering the new task,
  so a mailbox failure can't leave a phantom pending task in the coordinator.
- specialist: put a foreground sub-agent child in its own process group and
  group-kill on cancel/timeout (+ WaitDelay backstop), so a build/server it
  forked dies with it instead of orphaning.
- oauth: serialize on-demand token refresh per key (keyed mutex) and re-load
  inside the lock, so parallel callers don't each spend the single-use
  refresh token (all but one would otherwise fail at the token endpoint).
- mcp: treat a "source already gone" rename during legacy-token migration as
  success, so a concurrent migrator doesn't wrongly drop an OAuth MCP server.
- cron: claim a due job atomically (advance NextRunAt under the per-job lock)
  before running it, so two schedulers can't double-fire the same job.
2026-06-20 10:39:55 +05:30

41 lines
1.3 KiB
Go

//go:build !windows
package specialist
import (
"errors"
"os/exec"
"syscall"
"time"
)
// specialistWaitDelay bounds how long Wait blocks for the child's stdout pipe to
// drain after the process exits or its context is cancelled, so a leaked
// grandchild holding the pipe cannot hang the parent past cancel/timeout. Var
// (not const) so tests can shorten it.
var specialistWaitDelay = 2 * time.Second
// hardenSpecialistChild makes a foreground specialist child killable as a single
// unit. Setpgid puts the child into its own process group, so on cancel/timeout
// we signal the whole group (negative pid) — any build/server/bash the sub-agent
// forked dies with it instead of being orphaned. WaitDelay is the backstop if a
// grandchild still holds the stdout pipe after the group is killed. Must be
// called before command.Start (M6).
func hardenSpecialistChild(command *exec.Cmd) {
if command.SysProcAttr == nil {
command.SysProcAttr = &syscall.SysProcAttr{}
}
command.SysProcAttr.Setpgid = true
command.WaitDelay = specialistWaitDelay
command.Cancel = func() error {
if command.Process == nil {
return nil
}
// Negative pid targets the whole process group led by the child.
if err := syscall.Kill(-command.Process.Pid, syscall.SIGKILL); err != nil && !errors.Is(err, syscall.ESRCH) {
return err
}
return nil
}
}