Skip to content

fix(ebpf): synchronize perfUsageCollector.Collect with Manager.Start - #54952

Draft
pgimalac wants to merge 1 commit into
mainfrom
pgimalac/fix-perf-usage-collector-race
Draft

fix(ebpf): synchronize perfUsageCollector.Collect with Manager.Start#54952
pgimalac wants to merge 1 commit into
mainfrom
pgimalac/fix-perf-usage-collector-race

Conversation

@pgimalac

Copy link
Copy Markdown
Member

What does this PR do?

Fixes a data race on PerfMap/RingBuffer telemetry fields: perfUsageCollector.Collect reads BufferSize() (which reads bufferSize without a lock) during Prometheus Gather ticks, while ebpf.Manager.Start writes those same fields during eBPF program startup. Changes the collector mutex from sync.Mutex to sync.RWMutex so Collect takes RLock (reader), and exports LockForWrite/UnlockForWrite so Manager.Start can take the write lock around m.Manager.Start().

Motivation

Fix a race, found via a race-detector-enabled build in staging (see #54333 for context).

Describe how you validated your changes

Added TestLockForWriteBlocksCollect and TestCollectConcurrentReaders which validate the lock mechanism: LockForWrite blocks Collect until UnlockForWrite is called, and multiple Collect calls can run concurrently under RLock. Tests pass under -race (dda inv test --targets=./pkg/ebpf/telemetry/... --race). The actual race requires eBPF support to trigger (Manager.Start writes to PerfMap/RingBuffer internal fields), so the test validates the synchronization mechanism rather than the full race.

perfUsageCollector.Collect reads PerfMap/RingBuffer telemetry fields
(e.g. BufferSize) during Prometheus Gather ticks, while ebpf.Manager.Start
writes those same fields during eBPF program startup. The collector's
mutex protected its own data structures but not the shared PerfMap/
RingBuffer objects.

Change the collector mutex from sync.Mutex to sync.RWMutex so Collect
takes RLock (reader), and export LockForWrite/UnlockForWrite so
Manager.Start can take the write lock around m.Manager.Start(). This
prevents Collect from reading telemetry fields while Start is writing them.

Found via a race-detector-enabled build in staging (see #54333).
@pgimalac pgimalac added qa/done QA done before merge and regressions are covered by tests changelog/no-changelog No changelog entry needed labels Aug 17, 2026
@github-actions

Copy link
Copy Markdown
Contributor

@codex review

@dd-octo-sts dd-octo-sts Bot added internal Identify a non-fork PR team/ebpf-platform labels Aug 17, 2026
@github-actions github-actions Bot added the short review PR is simple enough to be reviewed quickly label Aug 17, 2026

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 6ac15534c1

ℹ️ About Codex in GitHub

Codex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".

Comment thread pkg/ebpf/manager.go
Comment on lines +193 to +194
ebpftelemetry.LockForWrite()
defer ebpftelemetry.UnlockForWrite()

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Protect managers that bypass this wrapper

When CWS internal telemetry is enabled, pkg/security/probe/eventstream/{reorderer/perfmap,ringbuffer/ringbuffer}.go registers buffers with this collector, but pkg/security/probe/probe_ebpf.go:494 starts its raw *ebpf-manager.Manager directly. That path therefore never acquires this new lock and can still race with a /telemetry collection while Start initializes the registered buffer fields; the synchronization needs to cover these direct manager starts as well, or be centralized below the wrapper.

Useful? React with 👍 / 👎.

Comment on lines +109 to +110
p.mtx.RLock()
defer p.mtx.RUnlock()

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Keep concurrent Collect calls mutually exclusive

When two /telemetry requests gather concurrently, this read lock lets both collections invoke the registered channel-length callbacks and mutate the shared metric vectors at once. The callbacks installed in pkg/ebpf/perf/event.go:371-372 and 408-409 use Swap(0), so one collection can consume the value while the other overwrites the gauge with zero before either snapshot is emitted, making channel-length telemetry nondeterministic. Keep collection serialized and use a separate lock or mechanism to synchronize it with manager startup.

Useful? React with 👍 / 👎.

Comment on lines +64 to +65
case <-time.After(100 * time.Millisecond):
t.Fatal("Collect did not complete after write lock was released")

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Replace the wall-clock lock assertion with synchronization

Under a sufficiently loaded CI worker, the collector goroutine can take longer than 100 ms to be scheduled after the lock is released, causing this test to fail even though the locking implementation is correct. The earlier 20 ms wait can likewise pass without proving that the goroutine reached RLock; use a readiness barrier to establish that the collection attempt has started and a deterministic completion signal instead of fixed wall-clock deadlines.

Useful? React with 👍 / 👎.

@datadog-datadog-prod-us1-2

datadog-datadog-prod-us1-2 Bot commented Aug 17, 2026

Copy link
Copy Markdown
Contributor

🎯 Code Coverage (details)
Patch Coverage: 100.00%
Overall Coverage: 52.45% (+0.07%)

This comment will be updated automatically if new data arrives.
🔗 Commit SHA: 6ac1553 | Docs | Datadog PR Page | Give us feedback!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

changelog/no-changelog No changelog entry needed component/system-probe internal Identify a non-fork PR qa/done QA done before merge and regressions are covered by tests short review PR is simple enough to be reviewed quickly team/ebpf-platform

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant