DrainErrGroup waits for g.Wait() to return, but gives up after `timeout`. Why this exists: keploy's record/replay teardown runs in a deferred function that is also the path SIGINT/SIGTERM takes (the signal cancels the root context, which triggers the defer). The teardown calls g.Wait() to drain the
(logger *zap.Logger, name string, g *errgroup.Group, timeout time.Duration)
| 25 | // nil; the leaked goroutine is reaped when the process exits. In the normal |
| 26 | // case the goroutines drain in well under the timeout, so this is a no-op. |
| 27 | func DrainErrGroup(logger *zap.Logger, name string, g *errgroup.Group, timeout time.Duration) error { |
| 28 | done := make(chan error, 1) |
| 29 | go func() { done <- g.Wait() }() |
| 30 | select { |
| 31 | case err := <-done: |
| 32 | return err |
| 33 | case <-time.After(timeout): |
| 34 | logger.Error("teardown drain timed out after cancellation; forcing shutdown so stop/SIGINT isn't swallowed — a goroutine is ignoring context cancellation", |
| 35 | zap.String("group", name), |
| 36 | zap.Duration("timeout", timeout)) |
| 37 | return nil |
| 38 | } |
| 39 | } |