@@ -44,14 +44,17 @@ const _mysqlChannelIntegration = (() => {
4444 // asyncEnd for some reason.
4545 const spans = new WeakMap < object , Span > ( ) ;
4646
47- // `subscribe()` requires all five lifecycle hooks. For callback-style mysql:
48- // - `start` — synchronous entry to `Connection.query`. Start span here.
49- // - `end` — synchronous return from `Connection.query` (BEFORE the
50- // network round-trip completes). Not the right span end.
51- // - `error` — the query callback fired with an error.
52- // - `asyncStart` — about to invoke the user callback (query result is ready).
53- // - `asyncEnd` — user callback returned. This is the moment we want to
54- // end the span — it captures the full query latency.
47+ // `subscribe()` requires all five lifecycle hooks. The orchestrion
48+ // `wrapCallback` transform fires them in one of three orders:
49+ // - sync throw from `query()` : start → error → end (NO asyncEnd)
50+ // - async error from callback : start → end → error → asyncStart → asyncEnd
51+ // - async success : start → end → asyncStart → asyncEnd
52+ // We end the span on `asyncEnd` for the two async paths (so the span
53+ // covers the full network round-trip + callback duration), and fall back
54+ // to `end` for the sync-throw path so the span isn't left unfinished.
55+ // The discriminator between "end fired before any error" and "end fired
56+ // after a sync throw" is whether `ctx.error` is set when `end` runs —
57+ // orchestrion populates it before publishing `error`.
5558 queryCh . subscribe ( {
5659 start ( rawCtx ) {
5760 const ctx = rawCtx as MysqlQueryChannelContext ;
@@ -71,8 +74,14 @@ const _mysqlChannelIntegration = (() => {
7174 spans . set ( rawCtx , span ) ;
7275 } ,
7376
74- end ( ) {
75- // No-op: span ends in `asyncEnd` once the network round-trip completes.
77+ end ( rawCtx ) {
78+ // Only acts for sync throws: `end` fires AFTER `error` (both inside
79+ // the wrapper's `try/catch/finally`), so `ctx.error` is already set.
80+ // For async paths `end` fires before `error`, so `ctx.error` is still
81+ // undefined here and we leave the span open for `asyncEnd` to close.
82+ const ctx = rawCtx as MysqlQueryChannelContext ;
83+ if ( ctx . error === undefined ) return ;
84+ finishSpan ( rawCtx ) ;
7685 } ,
7786
7887 error ( rawCtx ) {
@@ -90,12 +99,16 @@ const _mysqlChannelIntegration = (() => {
9099 } ,
91100
92101 asyncEnd ( rawCtx ) {
93- const span = spans . get ( rawCtx ) ;
94- if ( ! span ) return ;
95- span . end ( ) ;
96- spans . delete ( rawCtx ) ;
102+ finishSpan ( rawCtx ) ;
97103 } ,
98104 } ) ;
105+
106+ function finishSpan ( rawCtx : object ) : void {
107+ const span = spans . get ( rawCtx ) ;
108+ if ( ! span ) return ;
109+ span . end ( ) ;
110+ spans . delete ( rawCtx ) ;
111+ }
99112 } ,
100113 } ;
101114} ) satisfies IntegrationFn ;
0 commit comments