Skip to content

Fix browser test goroutine leak on cleanup - #5643

Merged
ankur22 merged 1 commit into
masterfrom
fix/browser-test-goroutine-leak
Feb 12, 2026
Merged

ankur22 merged 1 commit into
masterfrom
fix/browser-test-goroutine-leak

Conversation

@ankur22

@ankur22 ankur22 commented Feb 11, 2026 •

Copy link
Copy Markdown
Contributor

What?

Fixes a goroutine leak when running the browser tests. Emit an Exit event on the Global event system during test cleanup, which triggers handleExitEvent -> unsubscribe -> channel close, allowing both goroutines to exit cleanly.

Why?

The browser registry spawns handleIterEvents and handleExitEvent goroutines per VU, which block on channel receives. These channels are only closed when an Exit event is emitted, but the test VU never emitted one. This caused goroutines to leak across tests, accumulating hundreds of stuck goroutines and eventually causing the browser test suite to exceed its timeout.

This should:

  1. help run the browser tests a tiny bit faster, and help avoid CI failures such as https://github.com/grafana/k6/actions/runs/21440300997/job/61741331170
  2. reduce the number of goroutine leaks

Goroutine leak comparison (800s timeout)

Goroutine Before fix After fix
browserRegistry.handleIterEvents 679 1 (active test)
browserRegistry.handleExitEvent 679 1 (active test)
BaseEventEmitter.syncAll 32 28
Browser.initEvents 4 4
Connection.sendLoop 4 4
Connection.handleIOError 3 3
BrowserProcess.handleClose 4 4
NetworkManager.handleEvents 4 4
Total leaked browser goroutines 1409 49

The 679 leaked handleIterEvents/handleExitEvent goroutine pairs (one per newTestBrowser call) are completely eliminated. The remaining single instance of each is from the actively running test at timeout. The remaining 47 goroutines are from a separate leak in tests that create browser connections without closing them (addressed in a follow-up PR).

Checklist

  • I have performed a self-review of my code.
  • I have commented on my code, particularly in hard-to-understand areas.
  • I have added tests for my changes.
  • I have run linter and tests locally (make check) and all pass.

AI tool usage

  • Claude
  • Cursor
  • Codex
  • Other
  • I acknowledge that I fully understand the code change

Checklist: Documentation (only for k6 maintainers and if relevant)

Please do not merge this PR until the following items are filled out.

  • I have added the correct milestone and labels to the PR.
  • I have updated the release notes: link
  • I have updated or added an issue to the k6-documentation: grafana/k6-docs#NUMBER if applicable
  • I have updated or added an issue to the TypeScript definitions: grafana/k6-DefinitelyTyped#NUMBER if applicable

Related PR(s)/Issue(s)

@ankur22
ankur22 temporarily deployed to azure-trusted-signing February 11, 2026 12:39 — with GitHub Actions Inactive
@ankur22
ankur22 temporarily deployed to azure-trusted-signing February 11, 2026 12:41 — with GitHub Actions Inactive
@ankur22
ankur22 marked this pull request as ready for review February 11, 2026 16:22
@ankur22
ankur22 requested a review from a team as a code owner February 11, 2026 16:22
@ankur22
ankur22 requested review from codebien and mstoykov and removed request for a team February 11, 2026 16:22
The browser registry spawns handleIterEvents and handleExitEvent
goroutines per VU, which block on channel receives. These channels
are only closed when an Exit event is emitted, but the test VU never
emitted one. This caused goroutines to leak across tests, accumulating
hundreds of stuck goroutines and eventually causing the browser test
suite to exceed its timeout.

Emit an Exit event on the Global event system during test cleanup,
which triggers handleExitEvent -> unsubscribe -> channel close,
allowing both goroutines to exit cleanly.

Co-authored-by: Cursor <cursoragent@cursor.com>
@ankur22
ankur22 force-pushed the fix/browser-test-goroutine-leak branch from 3dfdedb to 842c506 Compare February 11, 2026 16:22
@ankur22
ankur22 temporarily deployed to azure-trusted-signing February 11, 2026 16:28 — with GitHub Actions Inactive
@ankur22
ankur22 temporarily deployed to azure-trusted-signing February 11, 2026 16:30 — with GitHub Actions Inactive
waitDone := globalEvents.Emit(&event.Event{
Type: event.Exit,
})
_ = waitDone(context.Background())

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

I didn't review yet in detail. But I assume this line means wait indefinitely. Can we have a time-limited context so it will time out at some point? or alternatively tb.Context()

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Yeah, good spot. If you follow the code path to what the exit event does it will eventually call this. From there it looks like it's pretty straight forward local executions (no IO or long running tasks), i guess there's potential to some sync contention though when trying to close the tracer, but apart form that it doesn't look computationally heavy.

What i'm trying to get at is that I don't think we need a timeout, and if something does go wrong and we're waiting indefinitely then it might be better to surface it?

If you have a strong opinion then i can change it to a context with timeout with 5 seconds.

@codebien codebien Feb 12, 2026 •

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

we're waiting indefinitely then it might be better to surface it?

I changed my mind, I agree with it 🤝

@ankur22
ankur22 requested a review from codebien February 12, 2026 09:11
@ankur22
ankur22 merged commit d001734 into master Feb 12, 2026
56 of 57 checks passed
@ankur22
ankur22 deleted the fix/browser-test-goroutine-leak branch February 12, 2026 09:50
@ankur22 ankur22 added this to the v1.7.0 milestone Feb 12, 2026

This branch was previously deployed

1 inactive deployment
azure-trusted-signing — 842c506f Deployed Feb 11, 2026 by ankur22 via sign-packages #8249
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.

3 participants