Skip to content

Commit 6b3f282

Browse files
committed
refactor(diagnostics): align async lifecycle with Node's tracePromise shape
maybeTraceMixed now publishes asyncStart immediately once it knows the operation is in-flight asynchronously and asyncEnd in a .finally once the promise settles, matching the shape subscribers expect from a tracePromise-style channel (asyncStart brackets the async tail rather than being paired with asyncEnd in a single microtask). Also brings the in-memory FakeTracingChannel in line with Node's actual traceSync behavior: Node sets ctx.result before publishing end, letting subscribers check isPromise(ctx.result) inside their end handler to decide whether asyncEnd will follow or the span is complete. The fake now does the same so unit tests aren't looser than real-Node behavior.
1 parent d7491c4 commit 6b3f282

File tree

2 files changed

+47
-42
lines changed

2 files changed

+47
-42
lines changed

src/__testUtils__/fakeDiagnosticsChannel.ts

Lines changed: 25 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -82,15 +82,22 @@ export class FakeTracingChannel implements MinimalTracingChannel {
8282
...args: Array<unknown>
8383
): T {
8484
this.start.publish(ctx);
85+
let result: T;
8586
try {
86-
return this.end.runStores(ctx, fn, thisArg, ...args);
87+
result = this.end.runStores(ctx, fn, thisArg, ...args);
8788
} catch (err) {
8889
(ctx as { error: unknown }).error = err;
8990
this.error.publish(ctx);
90-
throw err;
91-
} finally {
9291
this.end.publish(ctx);
92+
throw err;
9393
}
94+
// Node's real traceSync sets `ctx.result` before publishing `end`, so
95+
// subscribers can inspect `isPromise(ctx.result)` inside their `end`
96+
// handler to decide whether the operation is complete or async events
97+
// will follow. Match that semantic here.
98+
(ctx as { result: unknown }).result = result;
99+
this.end.publish(ctx);
100+
return result;
94101
}
95102

96103
tracePromise<T>(
@@ -110,21 +117,22 @@ export class FakeTracingChannel implements MinimalTracingChannel {
110117
throw err;
111118
}
112119
this.end.publish(ctx);
113-
return promise.then(
114-
(result) => {
115-
(ctx as { result: unknown }).result = result;
116-
this.asyncStart.publish(ctx);
120+
this.asyncStart.publish(ctx);
121+
return promise
122+
.then(
123+
(result) => {
124+
(ctx as { result: unknown }).result = result;
125+
return result;
126+
},
127+
(err: unknown) => {
128+
(ctx as { error: unknown }).error = err;
129+
this.error.publish(ctx);
130+
throw err;
131+
},
132+
)
133+
.finally(() => {
117134
this.asyncEnd.publish(ctx);
118-
return result;
119-
},
120-
(err: unknown) => {
121-
(ctx as { error: unknown }).error = err;
122-
this.error.publish(ctx);
123-
this.asyncStart.publish(ctx);
124-
this.asyncEnd.publish(ctx);
125-
throw err;
126-
},
127-
);
135+
});
128136
}
129137
}
130138

src/diagnostics.ts

Lines changed: 22 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -185,16 +185,7 @@ export function maybeTracePromise<T>(
185185
}
186186

187187
/**
188-
* Publish a mixed sync-or-promise operation through the named graphql tracing
189-
* channel. Delegates the start/end/error lifecycle to Node's `traceSync`
190-
* (which also runs `fn` inside `end.runStores` for AsyncLocalStorage context
191-
* propagation) and, when `fn` returns a promise, appends `asyncStart` and
192-
* `asyncEnd` on settlement plus `error` on rejection.
193-
*
194-
* Use this when the function may return either a value or a promise, which
195-
* is common on graphql-js's execution path where async-ness is determined
196-
* by resolvers only after the call begins. Short-circuits to `fn()` when
197-
* the channel isn't registered or nothing is listening.
188+
* Publish a mixed sync-or-promise operation through the named graphql tracing channel.
198189
*
199190
* @internal
200191
*/
@@ -211,24 +202,30 @@ export function maybeTraceMixed<T>(
211202
error?: unknown;
212203
result?: unknown;
213204
};
205+
206+
// traceSync fires start/end (and error, if fn throws synchronously)
214207
const result = channel.traceSync(fn, ctx);
215208
if (!isPromise(result)) {
216-
ctx.result = result;
217209
return result;
218210
}
219-
return result.then(
220-
(value) => {
221-
ctx.result = value;
222-
channel.asyncStart.publish(ctx);
223-
channel.asyncEnd.publish(ctx);
224-
return value;
225-
},
226-
(err: unknown) => {
227-
ctx.error = err;
228-
channel.error.publish(ctx);
229-
channel.asyncStart.publish(ctx);
211+
212+
// Fires off `asyncStart` and `asyncEnd` lifecycle events.
213+
channel.asyncStart.publish(ctx);
214+
return result
215+
.then(
216+
(value) => {
217+
ctx.result = value;
218+
219+
return value;
220+
},
221+
(err: unknown) => {
222+
ctx.error = err;
223+
channel.error.publish(ctx);
224+
225+
throw err;
226+
},
227+
)
228+
.finally(() => {
230229
channel.asyncEnd.publish(ctx);
231-
throw err;
232-
},
233-
);
230+
});
234231
}

0 commit comments

Comments
 (0)