4
0

timeout_context.go 1.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172
  1. package executor
  2. import (
  3. "context"
  4. "os"
  5. "sync"
  6. "time"
  7. )
  8. // timeoutContext is a custom context that kills the process group when cancelled due to timeout.
  9. type timeoutContext struct {
  10. context.Context
  11. cancel context.CancelFunc
  12. logEntry *InternalLogEntry
  13. process *os.Process
  14. executor *Executor
  15. processMu sync.Mutex
  16. }
  17. // newTimeoutContext creates a context that will kill the process group when the timeout expires.
  18. // logEntry is the same InternalLogEntry as the running execution so Kill(logEntry) can use Attributes and Process.
  19. // Pass nil logEntry for subprocesses (e.g. shellAfterCompleted); then setProcess stores the process and it is killed on timeout.
  20. func newTimeoutContext(parent context.Context, timeout time.Duration, executor *Executor, logEntry *InternalLogEntry) (*timeoutContext, context.CancelFunc) {
  21. ctx, cancel := context.WithTimeout(parent, timeout)
  22. tc := &timeoutContext{
  23. Context: ctx,
  24. cancel: cancel,
  25. logEntry: logEntry,
  26. executor: executor,
  27. }
  28. go func() {
  29. <-ctx.Done()
  30. if ctx.Err() != context.DeadlineExceeded {
  31. return
  32. }
  33. tc.processMu.Lock()
  34. entry := tc.logEntry
  35. process := tc.process
  36. tc.processMu.Unlock()
  37. if entry != nil {
  38. _ = executor.Kill(entry)
  39. } else if process != nil {
  40. _ = executor.Kill(&InternalLogEntry{Process: process})
  41. }
  42. }()
  43. return tc, cancel
  44. }
  45. func (tc *timeoutContext) setProcess(process *os.Process) {
  46. tc.processMu.Lock()
  47. if tc.logEntry != nil {
  48. tc.logEntry.Process = process
  49. } else {
  50. tc.process = process
  51. }
  52. tc.processMu.Unlock()
  53. tc.killProcessIfAlreadyTimedOut(process)
  54. }
  55. func (tc *timeoutContext) killProcessIfAlreadyTimedOut(process *os.Process) {
  56. if tc.Context.Err() != context.DeadlineExceeded || process == nil {
  57. return
  58. }
  59. if tc.logEntry != nil {
  60. _ = tc.executor.Kill(tc.logEntry)
  61. return
  62. }
  63. _ = tc.executor.Kill(&InternalLogEntry{Process: process})
  64. }