Skip to content

ref(server-utils): Move mysql orchestrion integration onto bindTracingChannelToSpan - #21865

Merged
logaretm merged 5 commits into
developfrom
feat/mysql-tracing-channel-helper
Jul 1, 2026
Merged

ref(server-utils): Move mysql orchestrion integration onto bindTracingChannelToSpan#21865
logaretm merged 5 commits into
developfrom
feat/mysql-tracing-channel-helper

Conversation

@logaretm

@logaretmlogaretm commented Jun 30, 2026

Copy link
Copy Markdown
Member

Stacked on #21863.

For mysql it returns an event emitter, that signals the op end which doesn't fit inside a tracing channel lifecycle, For that I added deferSpanEnd option, which hands ownership back to the caller. mysql uses it to attach the emitter listeners and keep bindScopeToEmitter so user listeners on the stream still nest under the caller.

There had to be a change to one test because it wasn't simulating a channel publish correctly, it needed to run the channel stores first before asserting.

@logaretmlogaretm changed the title feat(server-utils): Move mysql orchestrion integration onto bindTracingChannelToSpanref(server-utils): Move mysql orchestrion integration onto bindTracingChannelToSpanJun 30, 2026
@logaretm
logaretm changed the base branch from feat/tracing-channel-callback-context to developJune 30, 2026 15:15
@logaretm
logaretm changed the base branch from develop to feat/tracing-channel-callback-contextJune 30, 2026 15:15
@github-actions

github-actionsBot commented Jun 30, 2026

Copy link
Copy Markdown
Contributor

size-limit report 📦

PathSize% ChangeChange
@sentry/browser27.62 kB--
@sentry/browser - with treeshaking flags26.05 kB--
@sentry/browser (incl. Tracing)46.07 kB--
@sentry/browser (incl. Tracing + Span Streaming)47.82 kB--
@sentry/browser (incl. Tracing, Profiling)50.84 kB--
@sentry/browser (incl. Tracing, Replay)85.31 kB--
@sentry/browser (incl. Tracing, Replay) - with treeshaking flags74.91 kB--
@sentry/browser (incl. Tracing, Replay with Canvas)89.99 kB--
@sentry/browser (incl. Tracing, Replay, Feedback)102.67 kB--
@sentry/browser (incl. Feedback)44.8 kB--
@sentry/browser (incl. sendFeedback)32.42 kB--
@sentry/browser (incl. FeedbackAsync)37.55 kB--
@sentry/browser (incl. Metrics)28.68 kB--
@sentry/browser (incl. Logs)28.93 kB--
@sentry/browser (incl. Metrics & Logs)29.61 kB--
@sentry/react29.41 kB--
@sentry/react (incl. Tracing)48.38 kB--
@sentry/vue32.85 kB--
@sentry/vue (incl. Tracing)47.93 kB--
@sentry/svelte27.64 kB--
CDN Bundle30.02 kB--
CDN Bundle (incl. Tracing)48.02 kB--
CDN Bundle (incl. Logs, Metrics)31.58 kB--
CDN Bundle (incl. Tracing, Logs, Metrics)49.35 kB--
CDN Bundle (incl. Replay, Logs, Metrics)70.79 kB--
CDN Bundle (incl. Tracing, Replay)85.51 kB--
CDN Bundle (incl. Tracing, Replay, Logs, Metrics)86.79 kB--
CDN Bundle (incl. Tracing, Replay, Feedback)91.32 kB--
CDN Bundle (incl. Tracing, Replay, Feedback, Logs, Metrics)92.56 kB--
CDN Bundle - uncompressed89.42 kB--
CDN Bundle (incl. Tracing) - uncompressed145.35 kB--
CDN Bundle (incl. Logs, Metrics) - uncompressed94.12 kB--
CDN Bundle (incl. Tracing, Logs, Metrics) - uncompressed149.32 kB--
CDN Bundle (incl. Replay, Logs, Metrics) - uncompressed218.66 kB--
CDN Bundle (incl. Tracing, Replay) - uncompressed264.36 kB--
CDN Bundle (incl. Tracing, Replay, Logs, Metrics) - uncompressed268.32 kB--
CDN Bundle (incl. Tracing, Replay, Feedback) - uncompressed278.06 kB--
CDN Bundle (incl. Tracing, Replay, Feedback, Logs, Metrics) - uncompressed282.01 kB--
@sentry/nextjs (client)50.76 kB--
@sentry/sveltekit (client)46.46 kB--
@sentry/core/server77.75 kB--
@sentry/core/browser64.06 kB--
@sentry/node-core61.47 kB-0.01%-1 B 🔽
@sentry/node122.92 kB+0.07%+74 B 🔺
@sentry/node/import (ESM hook with diagnostics-channel injection)69.95 kB--
@sentry/node/light50.45 kB--
@sentry/node - without tracing73.2 kB+0.01%+1 B 🔺
@sentry/aws-serverless84.09 kB--
@sentry/cloudflare (withSentry) - minified180.62 kB--
@sentry/cloudflare (withSentry)446.93 kB--

View base workflow run

@logaretm
logaretmforce-pushed the feat/mysql-tracing-channel-helper branch from 5b73ec5 to a70f7f4CompareJune 30, 2026 19:10
@logaretm
logaretm marked this pull request as ready for review June 30, 2026 19:13
@logaretm
logaretm requested a review from a team as a code ownerJune 30, 2026 19:13
@logaretm
logaretm requested review from JPeer264, andreiborza, isaacs and mydea and removed request for a teamJune 30, 2026 19:13
Comment threadpackages/server-utils/src/tracing-channel.ts

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

Cursor Bugbot has reviewed your changes and found 1 potential issue.

Fix All in Cursor

❌ Bugbot Autofix is OFF. To automatically fix reported issues with cloud agents, enable autofix in the Cursor dashboard.

Reviewed by Cursor Bugbot for commit a70f7f4. Configure here.

Comment threadpackages/server-utils/src/tracing-channel.ts
@logaretm
logaretmforce-pushed the feat/mysql-tracing-channel-helper branch 3 times, most recently from 020a61d to 972275cCompareJune 30, 2026 19:24

@isaacsisaacs left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Looks good! Significant improvement. I'll update my pg PR once this lands.

const span = spans.get(rawCtx);
if (!span) return;
// `tracingChannel` is unavailable before Node 18.19 so do nothing in that case.
if (!diagnosticsChannel.tracingChannel) {

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

It seems like this should be a guard at a higher level (not objecting to it here, and can definitely be a follow-up PR).

Like, if we don't have dc.tracingChannel, then we kind of have to use the OTel integration, or nothing, right?

Copy link
Copy Markdown
MemberAuthor

Choose a reason for hiding this comment

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

Okay I think I can follow up with another PR for this.

Comment threadpackages/server-utils/src/tracing-channel.ts
Base automatically changed from feat/tracing-channel-callback-context to developJune 30, 2026 22:37
…ngChannelToSpan
The mysql subscriber hand-rolled the whole channel lifecycle: a span WeakMap,
a parent-scope WeakMap, manual callback re-wrapping to restore the caller's
context, and per-path span ending. Most of that is what the helper already does
— now that it restores caller context on `asyncStart`, the manual re-wrap can go.
Add a `deferSpanEnd` option to the helper: return `true` to take ownership of
ending the span so it isn't ended on `end`/`asyncEnd`. mysql uses it for the one
path the helper can't model — `query(sql)` with no callback returns a streamed
`Query` emitter that settles via its own `'end'`/`'error'` events, not the
channel. That path keeps `bindScopeToEmitter` so user listeners nest correctly.
Net: the callback and sync-throw paths are fully the helper's; only the
streamed-emitter wiring stays mysql-specific.
The synthetic test published `start`/`asyncStart` with bare `publish`, which
doesn't run bound stores — fine for the old subscriber that opened the span on
`start`, but the span is now opened in the `start.bindStore` transform (only run
via `runStores`). Drive it the way orchestrion's `wrapCallback` actually does so
the bound store activates and the span opens.
…le parity
`deferSpanEnd` handed the caller the span and left them to end it, so the
streamed-mysql path reimplemented a thinner version of the lifecycle — bare
`setStatus`, no `error.type`, no `captureError`, no `beforeSpanEnd`.
Pass an `end(error?)` into `deferSpanEnd` instead: it owns *how* the span ends
(error status + attributes, captureError, beforeSpanEnd) and is idempotent, so
the deferred path can't drift from the channel lifecycle. The error annotation
is factored into a shared helper both paths use. mysql just wires the emitter:
`on('error', end)` / `on('end', end)`.
`deferSpanEnd` now takes a single object arg `{ span, data, end }`.
@logaretm
logaretmforce-pushed the feat/mysql-tracing-channel-helper branch from 972275c to 4a057c8CompareJuly 1, 2026 00:36
Comment on lines +147 to +149
const span = data._sentrySpan;
if (span && deferSpanEnd?.({ span, data, end: makeDeferredEnd(span, data) })) {
return;

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.

Bug: For MySQL queries with callbacks, deferSpanEnd is called in both end and asyncEnd handlers, attaching duplicate listeners and causing span.end() to be called twice.
Severity: MEDIUM

Suggested Fix

Ensure that span deferral logic is only executed once per operation. This can be achieved by setting a flag on the data object after the first successful call to deferSpanEnd and checking for this flag in subsequent handlers to prevent re-registering listeners. For example, set data.span_deferred = true in the end handler and skip the deferSpanEnd call in the asyncEnd handler if this flag is present.

Prompt for AI Agent
Review the code at the location below. A potential bug has been identified by an AI
agent. Verify if this is a real issue. If it is, propose a fix; if not, explain why it's
not valid.
Location: packages/server-utils/src/tracing-channel.ts#L147-L149
Potential issue: For callback-based MySQL queries, the `deferSpanEnd` function is
invoked in both the `end` and `asyncEnd` handlers. Each invocation creates a new
`makeDeferredEnd` closure with its own independent idempotency flag. Because the MySQL
driver returns a `Query` emitter synchronously which is stored on `ctx.result`, both
handlers successfully defer the span's end. This results in two separate sets of event
listeners being attached to the same `Query` emitter. When the query completes or
errors, both sets of listeners execute, leading to `span.end()` being called twice on
the same span, which can cause malformed trace data.

Also affects:

  • packages/server-utils/src/tracing-channel.ts:165~167

@logaretm
logaretm merged commit e316151 into developJul 1, 2026
199 of 228 checks passed
@logaretm
logaretm deleted the feat/mysql-tracing-channel-helper branch July 1, 2026 02:00
Sign up for freeto 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.

2 participants

@logaretm@isaacs