runner_unix.go 1.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051
  1. //go:build unix
  2. package runner
  3. import (
  4. "os"
  5. "os/exec"
  6. "os/signal"
  7. "syscall"
  8. )
  9. // startSignalForwarding installs handlers for SIGINT, SIGTERM, and SIGHUP and
  10. // forwards them to proc. The returned stop function removes the handlers and
  11. // drains the channel; callers should defer-call it.
  12. //
  13. // We deliberately do NOT exit on signal receipt — the child gets the signal
  14. // and owns its shutdown lifecycle; we just relay and keep waiting for it.
  15. func startSignalForwarding(proc *os.Process) func() {
  16. ch := make(chan os.Signal, 4)
  17. signal.Notify(ch, syscall.SIGINT, syscall.SIGTERM, syscall.SIGHUP)
  18. done := make(chan struct{})
  19. go func() {
  20. for {
  21. select {
  22. case sig := <-ch:
  23. _ = proc.Signal(sig)
  24. case <-done:
  25. return
  26. }
  27. }
  28. }()
  29. return func() {
  30. signal.Stop(ch)
  31. close(done)
  32. }
  33. }
  34. // extractSignal pulls the signal from an *exec.ExitError on Unix.
  35. // If the error wasn't a signal termination, ok is false.
  36. func extractSignal(exitErr *exec.ExitError) (syscall.Signal, bool) {
  37. ws, ok := exitErr.Sys().(syscall.WaitStatus)
  38. if !ok {
  39. return 0, false
  40. }
  41. if !ws.Signaled() {
  42. return 0, false
  43. }
  44. return ws.Signal(), true
  45. }