Skip to content

[otel-beatreceiver-isolation] BeatReceiver constructor writes process-global config/path/timestamp state #49610

@github-actions

Description

@github-actions

Findings

1) Process-global config resolver is overwritten per receiver construction

Location

  • x-pack/libbeat/cmd/instance/beat.go:132-146

Evidence

  • Constructor rewrites resolver options via config.OverwriteConfigOpts(...):
    • x-pack/libbeat/cmd/instance/beat.go:133-136
    • x-pack/libbeat/cmd/instance/beat.go:140-145
  • OverwriteConfigOpts in elastic-agent-libs/config updates a package-global configOpts guarded by mutex (module file: .../elastic-agent-libs@v0.33.3/config/config.go, lines 44-77, 153-156).

What is wrong
Receiver-specific keystore/env resolver settings are written to global process state during NewBeatForReceiver.

Why it matters
In one OTel Collector process, concurrent or sequential construction of multiple receivers can make one receiver parse/unpack config with another receiver’s resolver settings (cross-instance config/secret resolution behavior).

Suggested fix
Make resolver options instance-scoped in receiver construction (pass options directly to local parse/unpack flow), instead of calling process-global OverwriteConfigOpts.

2) Metricbeat receiver construction uses and mutates global paths.Paths

Location

  • x-pack/libbeat/cmd/instance/beat.go:118-123
  • libbeat/cmd/instance/beat.go:1496-1511

Evidence

  • Metricbeat branch does:
    • instance.InitPaths(cfg) then b.Paths = paths.Paths (x-pack/libbeat/cmd/instance/beat.go:119-123)
  • InitPaths calls paths.InitPaths(...) (libbeat/cmd/instance/beat.go:1509-1510), which mutates singleton paths.Paths in elastic-agent-libs/paths (var Paths = New(), InitPaths, *paths = *cfg; module file .../elastic-agent-libs@v0.33.3/paths/paths.go, lines 71, 99, 106).
  • Filebeat receiver branch already avoids this by using p := paths.New(); p.InitPaths(...); b.Paths = p (x-pack/libbeat/cmd/instance/beat.go:113-117).

What is wrong
Receiver construction in the metricbeat path still binds to process-global paths state.

Why it matters
Multiple receivers in the same process can overwrite each other’s path.* values, causing incorrect data/log/meta path usage and potential racey behavior during concurrent construction.

Suggested fix
Mirror filebeat branch behavior for metricbeat receiver construction: allocate per-instance paths.New() and initialize that object, never assign b.Paths = paths.Paths.

3) Receiver construction mutates global timestamp formatter

Location

  • x-pack/libbeat/cmd/instance/beat.go:180-181
  • libbeat/common/datetime.go:105-122 and readers at 137, 173

Evidence

  • Constructor calls common.SetTimestampPrecision(b.Config.TimestampPrecision) (x-pack/libbeat/cmd/instance/beat.go:180-181).
  • SetTimestampPrecision comment explicitly states it changes format globally and mutates package vars tsFmt / timeFormatter (libbeat/common/datetime.go:105-108, 118-120).

What is wrong
Per-receiver config controls process-global timestamp formatting state.

Why it matters
One receiver’s precision setting can silently affect all receivers in-process; concurrent construction with runtime formatting reads can produce inconsistent formatting and race-risk in -race runs.

Suggested fix
Make timestamp precision instance-local for receiver pipelines/encoders; avoid changing package-global formatter in receiver constructor path.

Safe/Already Good

  • management.SetManagerFactory(...) path is mutex-protected (libbeat/management/management.go:114-118) and does not by itself encode receiver-specific state.
  • x-pack/libbeat/cmd/instance/beat.go:42-52 FQDN caching uses sync.OnceValues and is not per-receiver mutable config.

Suggested Actions

  • Refactor receiver constructor path to remove process-global resolver mutation.
  • Convert metricbeat receiver path handling to per-instance paths.Path objects.
  • Remove global timestamp precision mutation from receiver construction.
  • Add regression tests that construct two receivers concurrently/sequentially with different configs and run with go test -race ./x-pack/filebeat/fbreceiver/... ./x-pack/metricbeat/mbreceiver/....

What is this? | From workflow: Sweeper: OTel BeatReceiver Global State Isolation

Give us feedback! React with 🚀 if perfect, 👍 if helpful, 👎 if not.

  • expires on Mar 30, 2026, 6:26 PM UTC

Metadata

Metadata

Assignees

No one assigned

    Labels

    needs_teamIndicates that the issue/PR needs a Team:* label

    Type

    No type
    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions