()
| 52 | } |
| 53 | |
| 54 | func (w *wrapper) Wait() (err error) { |
| 55 | defer metrics.ShellCommandRun.StartTimer(w.ctx).SuccessFinish(&err) |
| 56 | |
| 57 | cmd := w.cmd |
| 58 | |
| 59 | if w.ctx == nil || !w.ctxCancellation { |
| 60 | return cmd.Wait() |
| 61 | } |
| 62 | |
| 63 | done := make(chan error, 1) |
| 64 | |
| 65 | go func() { |
| 66 | err := cmd.Wait() |
| 67 | if atomic.LoadUint32(&w.killedByCancel) > 0 { |
| 68 | // Normally, the cmd would report it was "terminated". |
| 69 | // In reality, the context was canceled and we gracefully stopped it, report as such. |
| 70 | err = w.ctx.Err() |
| 71 | } |
| 72 | done <- err |
| 73 | }() |
| 74 | |
| 75 | select { |
| 76 | case err := <-done: |
| 77 | return err |
| 78 | case <-w.ctx.Done(): |
| 79 | log(w.ctx, nil).Info("command was canceled; attempting graceful termination") |
| 80 | |
| 81 | atomic.AddUint32(&w.killedByCancel, 1) |
| 82 | err := w.killIgnoreDead(syscall.SIGTERM) |
| 83 | if err != nil { |
| 84 | log(w.ctx, err).Warn("unable to sigterm process; attempting killing") |
| 85 | atomic.AddUint32(&w.killedByCancel, 1) |
| 86 | if err := w.killIgnoreDead(syscall.SIGKILL); err != nil { |
| 87 | return errors.Wrap(err, "unable to kill process") |
| 88 | } |
| 89 | |
| 90 | return <-done |
| 91 | } |
| 92 | |
| 93 | select { |
| 94 | case err := <-done: |
| 95 | return err |
| 96 | case <-time.After(w.gracefulTerminationOnCancelTimeout): |
| 97 | log(w.ctx, nil).Warn("command was canceled and did not terminate in time; killing") |
| 98 | atomic.AddUint32(&w.killedByCancel, 1) |
| 99 | if err := w.killIgnoreDead(syscall.SIGKILL); err != nil { |
| 100 | return errors.Wrap(err, "unable to kill process") |
| 101 | } |
| 102 | return <-done |
| 103 | } |
| 104 | } |
| 105 | } |
| 106 | |
| 107 | func (w *wrapper) killIgnoreDead(signal syscall.Signal) error { |
| 108 | err := w.Kill(signal) |
no test coverage detected