diff options
| author | Paul Buetow <paul@buetow.org> | 2026-05-13 19:38:09 +0300 |
|---|---|---|
| committer | Paul Buetow <paul@buetow.org> | 2026-05-13 19:38:09 +0300 |
| commit | 7b4f74ab11a2504d107372afebdfd77dec59ea42 (patch) | |
| tree | 5da7edf16a764442e3acee16bc68965b04c98727 | |
| parent | f4a814df4e39ff5547a88d4f5d37ae6fe159cc76 (diff) | |
fix: stop BPF ring-buffer polling goroutine on trace end to prevent leak
setupEventChannel now returns the *bpf.RingBuffer handle alongside the
event channel. Both callers (runTraceWithContext, runHeadlessParquet)
defer rb.Stop() so the libbpfgo polling goroutine and C ring_buffer
struct are released promptly when the trace context is cancelled,
rather than waiting for bpfModule.Close() to eventually call rb.Close().
rb.Stop() and rb.Close() are both idempotent, so the double-call from
the defer and from bpfModule.Close() is safe.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
| -rw-r--r-- | internal/ior.go | 8 | ||||
| -rw-r--r-- | internal/ior_bpfsetup.go | 13 | ||||
| -rw-r--r-- | internal/ior_parquet_sink.go | 8 |
3 files changed, 23 insertions, 6 deletions
diff --git a/internal/ior.go b/internal/ior.go index 9a60869..02a5b30 100644 --- a/internal/ior.go +++ b/internal/ior.go @@ -499,10 +499,16 @@ func runTraceWithContext(parentCtx context.Context, cfg flags.Config, started ch }() defer releaseBindings() - ch, err := setupEventChannel(bpfModule) + ch, rb, err := setupEventChannel(bpfModule) if err != nil { return err } + // Stop the ring-buffer polling goroutine before the module is closed. + // rb.Stop() signals the background goroutine, drains the channel, and + // waits for the goroutine to exit; bpfModule.Close() (deferred above) + // then calls rb.Close() which frees the C ring_buffer struct. Both are + // idempotent so double-calling is safe. + defer rb.Stop() ctx, cancel, stopSignals := setupTraceContext(parentCtx, cfg, logln) defer cancel() defer stopSignals() diff --git a/internal/ior_bpfsetup.go b/internal/ior_bpfsetup.go index 0d32d4c..61009c6 100644 --- a/internal/ior_bpfsetup.go +++ b/internal/ior_bpfsetup.go @@ -87,15 +87,20 @@ func setupBPFModule(parentCtx context.Context, cfg flags.Config) (*bpf.Module, * return bpfModule, mgr, releaseBindings, nil } -// setupEventChannel initialises the BPF ring-buffer and returns the event channel. -func setupEventChannel(bpfModule *bpf.Module) (chan []byte, error) { +// setupEventChannel initialises the BPF ring-buffer and returns both the event +// channel and the ring-buffer handle. The caller must call rb.Stop() when the +// trace ends (before bpfModule.Close()) to promptly halt the background polling +// goroutine and release the C ring_buffer struct. bpfModule.Close() also closes +// all ring buffers it owns, but only calling Stop() first ensures the goroutine +// exits without waiting for the module teardown path. +func setupEventChannel(bpfModule *bpf.Module) (chan []byte, *bpf.RingBuffer, error) { ch := make(chan []byte, appconfig.DefaultChannelBufferSize) rb, err := bpfModule.InitRingBuf("event_map", ch) if err != nil { - return nil, err + return nil, nil, err } rb.Poll(300) - return ch, nil + return ch, rb, nil } // --- compile-time interface satisfaction assertions --- diff --git a/internal/ior_parquet_sink.go b/internal/ior_parquet_sink.go index 3b87385..279c9be 100644 --- a/internal/ior_parquet_sink.go +++ b/internal/ior_parquet_sink.go @@ -111,10 +111,16 @@ func runHeadlessParquet(cfg flags.Config) error { }() defer releaseBindings() - ch, err := setupEventChannel(bpfModule) + ch, rb, err := setupEventChannel(bpfModule) if err != nil { return err } + // Stop the ring-buffer polling goroutine before the module is closed. + // rb.Stop() signals the background goroutine, drains the channel, and + // waits for the goroutine to exit; bpfModule.Close() (deferred above) + // then calls rb.Close() which frees the C ring_buffer struct. Both are + // idempotent so double-calling is safe. + defer rb.Stop() ctx, cancel, stopSignals := setupTraceContext(context.Background(), cfg, logln) defer cancel() defer stopSignals() |
