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

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

@logaretm logaretm changed the title feat(server-utils): Move mysql orchestrion integration onto bindTracingChannelToSpan ref(server-utils): Move mysql orchestrion integration onto bindTracingChannelToSpan Jun 30, 2026
@logaretm logaretm changed the base branch from feat/tracing-channel-callback-context to develop June 30, 2026 15:15
@logaretm logaretm changed the base branch from develop to feat/tracing-channel-callback-context June 30, 2026 15:15
@github-actions

github-actions Bot commented Jun 30, 2026

Copy link
Copy Markdown
Contributor

size-limit report 📦

Path Size % Change Change
@sentry/browser 27.62 kB - -
@sentry/browser - with treeshaking flags 26.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 flags 74.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/react 29.41 kB - -
@sentry/react (incl. Tracing) 48.38 kB - -
@sentry/vue 32.85 kB - -
@sentry/vue (incl. Tracing) 47.93 kB - -
@sentry/svelte 27.64 kB - -
CDN Bundle 30.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 - uncompressed 89.42 kB - -
CDN Bundle (incl. Tracing) - uncompressed 145.35 kB - -
CDN Bundle (incl. Logs, Metrics) - uncompressed 94.12 kB - -
CDN Bundle (incl. Tracing, Logs, Metrics) - uncompressed 149.32 kB - -
CDN Bundle (incl. Replay, Logs, Metrics) - uncompressed 218.66 kB - -
CDN Bundle (incl. Tracing, Replay) - uncompressed 264.36 kB - -
CDN Bundle (incl. Tracing, Replay, Logs, Metrics) - uncompressed 268.32 kB - -
CDN Bundle (incl. Tracing, Replay, Feedback) - uncompressed 278.06 kB - -
CDN Bundle (incl. Tracing, Replay, Feedback, Logs, Metrics) - uncompressed 282.01 kB - -
@sentry/nextjs (client) 50.76 kB - -
@sentry/sveltekit (client) 46.46 kB - -
@sentry/core/server 77.75 kB - -
@sentry/core/browser 64.06 kB - -
@sentry/node-core 61.47 kB -0.01% -1 B 🔽
@sentry/node 122.92 kB +0.07% +74 B 🔺
@sentry/node/import (ESM hook with diagnostics-channel injection) 69.95 kB - -
@sentry/node/light 50.45 kB - -
@sentry/node - without tracing 73.2 kB +0.01% +1 B 🔺
@sentry/aws-serverless 84.09 kB - -
@sentry/cloudflare (withSentry) - minified 180.62 kB - -
@sentry/cloudflare (withSentry) 446.93 kB - -

View base workflow run

@logaretm logaretm force-pushed the feat/mysql-tracing-channel-helper branch from 5b73ec5 to a70f7f4 Compare June 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 owner June 30, 2026 19:13
@logaretm logaretm requested review from JPeer264, andreiborza, isaacs and mydea and removed request for a team June 30, 2026 19:13
Comment thread packages/server-utils/src/tracing-channel.ts

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

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 thread packages/server-utils/src/tracing-channel.ts
@logaretm logaretm force-pushed the feat/mysql-tracing-channel-helper branch 3 times, most recently from 020a61d to 972275c Compare June 30, 2026 19:24

@isaacs isaacs 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
Member Author

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 thread packages/server-utils/src/tracing-channel.ts
Base automatically changed from feat/tracing-channel-callback-context to develop June 30, 2026 22:37
logaretm added 5 commits June 30, 2026 20:34
…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 logaretm force-pushed the feat/mysql-tracing-channel-helper branch from 972275c to 4a057c8 Compare July 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 develop Jul 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 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.

2 participants