Skip to content

ref(flamegraph): More robust concurrency structure - #678

Open
mjq wants to merge 1 commit into
mainfrom
mjq/flamegraph-concurrent-structure
Open

mjq wants to merge 1 commit into
mainfrom
mjq/flamegraph-concurrent-structure

Conversation

@mjq

@mjq mjq commented Sep 22, 2026

Copy link
Copy Markdown
Member

GetFlamegraphFromCandidates's design:

  • create an unbuffered results channel
  • spin up a goroutine to dispatch one job per candidate (possibly a transaction profile or a continuous profile) to jobs (actually the global readJobs channel). Each job includes a reference to results so the result can be written back.
  • read from the results channel once per candidate and add its data to the flamegraph
  • return the flamegraph

(This relies on an assumption that every job submitted to readJobs will write its result to results, which is handled elsewhere).

There are two minor gaps:

  1. Although the unexpected result from storage error is never expected to happen, it's an error and not a panic. But, it may result in a panic anyway: it will cause a return, which will trigger the deferred close(results), which will panic the next time a readJobs worker finishes a job and tries to write to it. Even if we fixed the panic, we would still need to drain results to avoid blocking readJobs workers while they write their results.
  2. The dispatch goroutine may not finish before GetFlamegraphFromCandidates finishes, for example when there are no candidates.

To fix this issue and clarify the flow:

  • Completely drain results even if we get an unexpected error, so that we can't block the readJobs workers. We still return the error.
  • Don't close(results). It isn't necessary for signalling (since we always read one result per submitted candidate) and closing it risks a panic if any worker writes after the receiver returns.
  • Wait for the dispatch goroutine to finish, ensuring both the goroutine and its dispatch span finish before GetFlamegraphFromCandidates returns.

@cursor cursor 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.

✅ Bugbot reviewed your changes and found no new issues!

Comment @cursor review or bugbot run to trigger another review on this PR

Reviewed by Cursor Bugbot for commit 058a669. Configure here.

@mjq
mjq marked this pull request as ready for review September 23, 2026 14:46
@mjq
mjq requested a review from a team as a code owner September 23, 2026 14:46
`GetFlamegraphFromCandidates`'s design:

- create an unbuffered `results` channel
- spin up a goroutine to dispatch one job per candidate (possibly a transaction
  profile or a continuous profile) to `jobs` (actually the global `readJobs`
  channel). Each job includes a reference to `results` so the result can be
  written back.
- read from the `results` channel once per candidate and add its data to the
  flamegraph
- return the flamegraph

(This relies on an assumption that every job submitted to `readJobs` will write
its result to `results`, which is handled elsewhere).

There are two minor gaps:

1. Although the `unexpected result from storage` error is never expected to
  happen, it's an error and not a panic. But, it may result in a panic anyway: it
  will cause a `return`, which will trigger the deferred `close(results)`, which
  will panic the next time a `readJobs` worker finishes a job and tries to write
  to it. Even if we fixed the panic, we would still need to drain `results` to
  avoid blocking `readJobs` workers while they write their results.
2. The dispatch goroutine may not finish before `GetFlamegraphFromCandidates`
  finishes, for example when there are no candidates.

To fix this issue and clarify the flow:

- Completely drain `results` even if we get an unexpected error, so that we
  can't block the `readJobs` workers. We still return the error.
- Don't `close(results)`. It isn't necessary for signalling (since we always
  read one result per submitted candidate) and closing it risks a panic if any
  worker writes after the receiver returns.
- Wait for the dispatch goroutine to finish, ensuring both the goroutine and
  its dispatch span finish before `GetFlamegraphFromCandidates` returns.
@mjq
mjq force-pushed the mjq/flamegraph-concurrent-structure branch from 058a669 to 275a6e5 Compare September 23, 2026 14:57
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