Skip to content

test: fix BroadcastChannel leakage in worker.js test suite - #816

Open
krajorama wants to merge 1 commit into
mainfrom
worker-channel-cleanup
Open

test: fix BroadcastChannel leakage in worker.js test suite#816
krajorama wants to merge 1 commit into
mainfrom
worker-channel-cleanup

Conversation

@krajorama

Copy link
Copy Markdown
Member

What

Follow-up to #806. While reviewing that PR, test/workerTest.js's "aggregates worker responses in thread id order" and two shutdown() tests turned out to only pass by accident: jest.resetModules() creates a fresh WorkerRegistry module instance per test, each with its own ANNOUNCEMENT_CHANNEL and per-thread BroadcastChannel. Those channels keep receiving messages until explicitly closed — even once nothing in JS still references them — so a previous test's listeners stayed alive and kept reacting to later tests' messages on the same channel names.

That leakage is exactly what let those three tests pass at all: their discovery promise, which waits for a non-primary ANNOUNCEMENT echo, only ever resolved via a stray listener left over from an earlier test — never from anything the test itself set up. Run any of the three in true isolation (-t "...") against main and they fail outright (timeout or InvalidStateError: BroadcastChannel is closed).

How

  • Add a guarded WorkerRegistry.resetForTesting() (throws outside test/development, mirroring the existing guard on cluster.js's scanListeners) that closes ANNOUNCEMENT_CHANNEL, the per-thread channel, and every registered worker's channel, then resets workers/requests/historicMetrics/listenersAdded.
  • Call it everywhere a test loads a fresh module instance via jest.resetModules(), including a 30-iteration loop that previously leaked 30 channels per run and closed none.
  • Replace the discovery/echo pattern in the two affected tests with polling the already-existing WorkerRegistry.workerCount() — a real signal, rather than hoping to observe an echo that a self-posted BroadcastChannel message never actually delivers back to its own sender.

Test plan

  • npx jest — full suite passes (581/581 on current main)
  • npx eslint . && npx prettier . --check && npx tsc --project . — all clean
  • Previously-flaky tests pass in the full file run (30/30 across repeated runs)
  • Previously-flaky tests pass in true isolation too (-t "...", 5/5 each) — this is the actual proof the fragility is gone, not just hidden
  • The "Jest did not exit... asynchronous operations" open-handle warning that showed up intermittently before this fix no longer appears

Signed-off-by: György Krajcsovits gyorgy.krajcsovits@grafana.com

🤖 Generated with Claude Code

jest.resetModules() creates a fresh WorkerRegistry module instance per
test, each with its own ANNOUNCEMENT_CHANNEL and per-thread channel.
BroadcastChannels keep receiving messages until explicitly closed, even
once nothing in JS still references them, so the previous instance's
listeners stayed alive and kept reacting to later tests' messages on
the same channel names. That leakage is what let
"aggregates worker responses in thread id order" and two shutdown()
tests pass at all: their `discovery` promise, which waits for a
non-primary ANNOUNCEMENT echo, only ever resolved via a stray listener
left over from an earlier test, not from anything the test itself set
up. In true isolation all three failed outright.

Add a guarded WorkerRegistry.resetForTesting() to close a module
instance's channels and reset its state, call it everywhere a test
loads a fresh instance, and replace the discovery/echo pattern with
polling WorkerRegistry.workerCount(), a real signal that already
existed for this purpose.

Signed-off-by: György Krajcsovits <gyorgy.krajcsovits@grafana.com>

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
@github-actions

Copy link
Copy Markdown

Thanks for opening this pull request! Each pull request require an update in the CHANGELOG. Please update it based on your changes.

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

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant