diff options
| -rw-r--r-- | internal/ior.go | 24 |
1 files changed, 21 insertions, 3 deletions
diff --git a/internal/ior.go b/internal/ior.go index a10f1c6..6e8111a 100644 --- a/internal/ior.go +++ b/internal/ior.go @@ -268,6 +268,12 @@ func makeTUIEventLoopConfigurer(ctx context.Context, cfg flags.Config, rt *tuiRu // makeTUIEventLoopConfigurer, and starts the trace in a goroutine, signalling // the TUI once BPF probes are attached (via startedCh) or returning an error // if startup fails. +// +// A dedicated done channel is closed by a defer when the outer function +// returns for any reason (ctx cancellation, successful start, or startup +// error). The trace goroutine selects on both errCh and done when delivering +// its result, so it can always exit regardless of which exit arm the outer +// caller took. func tuiTraceStarterFromRunTrace( baseCfg flags.Config, startTrace func(context.Context, flags.Config, chan<- struct{}, func(*eventLoop)) error, @@ -288,14 +294,26 @@ func tuiTraceStarterFromRunTrace( configureEl := makeTUIEventLoopConfigurer(ctx, cfg, rt) startedCh := make(chan struct{}) - errCh := make(chan error, 1) + // errCh carries at most one result from the trace goroutine to the + // outer select below. done is closed on return so the goroutine can + // always exit even when the outer caller already left via startedCh or + // ctx.Done() and nobody is draining errCh. + errCh := make(chan error) + done := make(chan struct{}) + defer close(done) + go func() { err := startTrace(ctx, cfg, startedCh, configureEl) if bindings, ok := runtime.RuntimeBindingsFromContext(ctx); ok { bindings.SetLiveFilterSetter(nil) } - errCh <- err - close(errCh) + // Deliver the result only if the caller is still selecting. + // done is closed when the outer function returns, so the goroutine + // will always proceed through this select and never block. + select { + case errCh <- err: + case <-done: + } }() select { |
