elastic / elastic/beats

[bug-hunter] Non-memory OTel receiver close releases shared default slabqueue pool

Open
#51,148 1 comment 0 reactions 0 assignees View on GitHub
needs_team
Dominant language
Go
Stars
12.7k
Forks
5k
Avg merge
2d 2h
Merged PRs (30d)
364

Description

## Impact
When a Beat receiver using a non-memory queue (`queue.disk` path) is closed, it decrements/releases the shared default slabqueue pool (`intakeQueueID == ""`) even though it never acquired that pool. If another receiver is actively using the default mem-backed pool, this can tear down shared queue state unexpectedly and break publishing behavior for the still-running pipeline.

## Reproduction Steps
1. Create `libbeat/publisher/pipeline/output_otel_bug_repro_test.go` with the test below.
2. Run:

```bash
go test -v ./libbeat/publisher/pipeline -run '^TestRepro_NonMemCloseReleasesDefaultPool$'
```

## Expected vs Actual
**Expected:** Closing a non-memory controller should not release the shared default slabqueue pool used by a mem-backed controller.

**Actual:** The pool registration for `""` is removed; test fails:

```text
=== RUN TestRepro_NonMemCloseReleasesDefaultPool
output_otel_bug_repro_test.go:61:
Error: Should be true
Test: TestRepro_NonMemCloseReleasesDefaultPool
Messages: closing a non-memory controller must not release the shared default slabqueue pool
--- FAIL: TestRepro_NonMemCloseReleasesDefaultPool (0.00s)
FAIL
FAIL github.com/elastic/beats/v7/libbeat/publisher/pipeline 0.007s
FAIL
```

## Failing Test
```go
(go/redacted):build !nooteloutput

package pipeline

import (
"testing"

"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"

"github.com/elastic/beats/v7/libbeat/publisher"
"github.com/elastic/beats/v7/libbeat/publisher/queue/memqueue"
)

func TestRepro_NonMemCloseReleasesDefaultPool(t *testing.T) {
type fakeNonMemqueueSettings struct{ Path string }

memController, err := newOTelOutputController(
beatInfoForTest(t),
monitorsForTest(),
nilObserver,
"",
nil,
memqueue.Settings{Events: 4},
)
require.NoError(t, err, "memory-backed controller creation should succeed")
defer memController.waitClose(cancelledContext(), false)

nonMemController, err := newOTelOutputController(
beatInfoForTest(t),
monitorsForTest(),
nilObserver,
"",
memqueue.FactoryForSettings[publisher.Event](memqueue.Settings{Events: 4}),
fakeNonMemqueueSettings{Path: "/tmp/dq"},
)
require.NoError(t, err, "non-memory queue controller creation should succeed")

require.NoError(t, nonMemController.waitClose(cancelledContext(), false), "closing non-memory controller should succeed")

allOTelPools.Lock()
_, stillRegistered := allOTelPools.lookup[""]
allOTelPools.Unlock()
assert.True(t, stillRegistered, "closing a non-memory controller must not release the shared default slabqueue pool")
}
```

## Evidence
- `libbeat/publisher/pipeline/output_otel.go:112-118` acquires shared pool only on `memqueue.Settings` path.
- `libbeat/publisher/pipeline/output_otel.go:119-131` non-memory path bypasses pool acquisition.
- `libbeat/publisher/pipeline/output_otel.go:253` unconditionally calls `releaseOTelPool(c.intakeQueueID)` during close.
- For non-memory controllers with empty intake ID, this releases key `""` despite no acquire.
- Recent introducing commit touching this path: `37398496f5` (`Implement a per-pipeline queue over a shared backing array. (slabqueue) (#51047)`).
- Duplicate checks run in repo issues/PR search for relevant keywords found no matching tracked item.

---
[What is this?](https://ela.st/github-ai-tools) | [From workflow: Bug Hunter](https://github.com/elastic/beats/actions/runs/27137748178)

Give us feedback! React with 🚀 if perfect, 👍 if helpful, 👎 if not.
> - [x] expires on Jun 15, 2026, 12:44 PM UTC

Contributor guide

Open the contributing guide

Assessment

This issue has not been assessed yet.

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.