Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Appearance settings

Commit 4ad07de

Browse filesBrowse files
Qardaduh95
authored andcommitted
diagnostics_channel: add BoundedChannel and scopes
PR-URL: #61680 Reviewed-By: James M Snell <jasnell@gmail.com>
1 parent 5eb034a commit 4ad07de
Copy full SHA for 4ad07de

12 files changed

+1,863-107Lines changed: 1863 additions & 107 deletions
Expand file treeCollapse file tree
Open diff view settings
Collapse file

‎doc/api/diagnostics_channel.md‎

Copy file name to clipboardExpand all lines: doc/api/diagnostics_channel.md
+397Lines changed: 397 additions & 0 deletions
  • Display the source diff
  • Display the rich diff
Large diffs are not rendered by default.
Collapse file

‎lib/diagnostics_channel.js‎

Copy file name to clipboardExpand all lines: lib/diagnostics_channel.js
+285-106Lines changed: 285 additions & 106 deletions
Large diffs are not rendered by default.
Collapse file
+66Lines changed: 66 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,66 @@
1+
'use strict';
2+
const common = require('../common');
3+
const assert = require('node:assert');
4+
const dc = require('node:diagnostics_channel');
5+
const { AsyncLocalStorage } = require('node:async_hooks');
6+
7+
// Test BoundedChannel.run() with store transform error
8+
// Transform errors are scheduled via process.nextTick(triggerUncaughtException)
9+
10+
const boundedChannel = dc.boundedChannel('test-run-transform-error');
11+
const store = new AsyncLocalStorage();
12+
const events = [];
13+
14+
const transformError = new Error('transform failed');
15+
16+
// Set up uncaughtException handler to catch the transform error
17+
process.on('uncaughtException', common.mustCall((err) => {
18+
assert.strictEqual(err, transformError);
19+
events.push('uncaughtException');
20+
}));
21+
22+
boundedChannel.subscribe({
23+
start(message) {
24+
events.push({ type: 'start', data: message });
25+
},
26+
end(message) {
27+
events.push({ type: 'end', data: message });
28+
},
29+
});
30+
31+
// Bind store with a transform that throws
32+
boundedChannel.start.bindStore(store, () => {
33+
throw transformError;
34+
});
35+
36+
// Store should remain undefined since transform will fail
37+
assert.strictEqual(store.getStore(), undefined);
38+
39+
const result = boundedChannel.run({ operationId: '123' }, common.mustCall(() => {
40+
// Store should still be undefined because transform threw
41+
assert.strictEqual(store.getStore(), undefined);
42+
43+
events.push('inside-run');
44+
45+
return 42;
46+
}));
47+
48+
// Should still return the result despite transform error
49+
assert.strictEqual(result, 42);
50+
51+
// Store should still be undefined after run
52+
assert.strictEqual(store.getStore(), undefined);
53+
54+
// Start and end events should still be published despite transform error
55+
assert.strictEqual(events.length, 3);
56+
assert.strictEqual(events[0].type, 'start');
57+
assert.strictEqual(events[0].data.operationId, '123');
58+
assert.strictEqual(events[1], 'inside-run');
59+
assert.strictEqual(events[2].type, 'end');
60+
assert.strictEqual(events[2].data.operationId, '123');
61+
62+
// Validate uncaughtException was triggered via nextTick
63+
process.on('beforeExit', common.mustCall(() => {
64+
assert.strictEqual(events.length, 4);
65+
assert.strictEqual(events[3], 'uncaughtException');
66+
}));
Collapse file
+125Lines changed: 125 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,125 @@
1+
'use strict';
2+
require('../common');
3+
const assert = require('node:assert');
4+
const dc = require('node:diagnostics_channel');
5+
const { AsyncLocalStorage } = require('node:async_hooks');
6+
7+
// Test basic run functionality
8+
{
9+
const boundedChannel = dc.boundedChannel('test-run-basic');
10+
const events = [];
11+
12+
boundedChannel.subscribe({
13+
start(message) {
14+
events.push({ type: 'start', data: message });
15+
},
16+
end(message) {
17+
events.push({ type: 'end', data: message });
18+
},
19+
});
20+
21+
const context = { id: 123 };
22+
const result = boundedChannel.run(context, () => {
23+
return 'success';
24+
});
25+
26+
assert.strictEqual(result, 'success');
27+
assert.strictEqual(events.length, 2);
28+
assert.deepStrictEqual(events, [
29+
{ type: 'start', data: { id: 123 } },
30+
{ type: 'end', data: { id: 123 } },
31+
]);
32+
}
33+
34+
// Test run with error
35+
{
36+
const boundedChannel = dc.boundedChannel('test-run-error');
37+
const events = [];
38+
39+
boundedChannel.subscribe({
40+
start(message) {
41+
events.push({ type: 'start', data: message });
42+
},
43+
end(message) {
44+
events.push({ type: 'end', data: message });
45+
},
46+
});
47+
48+
const context = { id: 456 };
49+
const testError = new Error('test error');
50+
51+
assert.throws(() => {
52+
boundedChannel.run(context, () => {
53+
throw testError;
54+
});
55+
}, testError);
56+
57+
// BoundedChannel does not handle errors - they just propagate
58+
// Only start and end events are published
59+
assert.strictEqual(events.length, 2);
60+
assert.deepStrictEqual(events, [
61+
{ type: 'start', data: { id: 456 } },
62+
{ type: 'end', data: { id: 456 } },
63+
]);
64+
}
65+
66+
// Test run with thisArg and args
67+
{
68+
const boundedChannel = dc.boundedChannel('test-run-args');
69+
70+
const obj = { value: 10 };
71+
const result = boundedChannel.run({}, function(a, b) {
72+
return this.value + a + b;
73+
}, obj, 5, 15);
74+
75+
assert.strictEqual(result, 30);
76+
}
77+
78+
// Test run with AsyncLocalStorage
79+
{
80+
const boundedChannel = dc.boundedChannel('test-run-store');
81+
const store = new AsyncLocalStorage();
82+
const events = [];
83+
84+
boundedChannel.start.bindStore(store, (context) => {
85+
return { traceId: context.traceId };
86+
});
87+
88+
boundedChannel.subscribe({
89+
start(message) {
90+
events.push({ type: 'start', store: store.getStore() });
91+
},
92+
end(message) {
93+
events.push({ type: 'end', store: store.getStore() });
94+
},
95+
});
96+
97+
const result = boundedChannel.run({ traceId: 'abc123' }, () => {
98+
events.push({ type: 'inside', store: store.getStore() });
99+
return 'result';
100+
});
101+
102+
assert.strictEqual(result, 'result');
103+
assert.strictEqual(events.length, 3);
104+
105+
// Innert events should have store set
106+
assert.deepStrictEqual(events, [
107+
{ type: 'start', store: { traceId: 'abc123' } },
108+
{ type: 'inside', store: { traceId: 'abc123' } },
109+
{ type: 'end', store: { traceId: 'abc123' } },
110+
]);
111+
112+
// Store should be undefined outside
113+
assert.strictEqual(store.getStore(), undefined);
114+
}
115+
116+
// Test run without subscribers
117+
{
118+
const boundedChannel = dc.boundedChannel('test-run-no-subs');
119+
120+
const result = boundedChannel.run({}, () => {
121+
return 'fast path';
122+
});
123+
124+
assert.strictEqual(result, 'fast path');
125+
}
Collapse file
+90Lines changed: 90 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,90 @@
1+
/* eslint-disable no-unused-vars */
2+
'use strict';
3+
require('../common');
4+
const assert = require('node:assert');
5+
const dc = require('node:diagnostics_channel');
6+
const { AsyncLocalStorage } = require('node:async_hooks');
7+
8+
// Test scope with thrown error
9+
{
10+
const boundedChannel = dc.boundedChannel('test-scope-throw');
11+
const events = [];
12+
13+
boundedChannel.subscribe({
14+
start(message) {
15+
events.push({ type: 'start', data: message });
16+
},
17+
end(message) {
18+
events.push({ type: 'end', data: message });
19+
},
20+
});
21+
22+
const context = { id: 1 };
23+
const testError = new Error('thrown error');
24+
25+
assert.throws(() => {
26+
using scope = boundedChannel.withScope(context);
27+
context.result = 'partial';
28+
throw testError;
29+
}, testError);
30+
31+
// End event should still be published
32+
assert.strictEqual(events.length, 2);
33+
assert.strictEqual(events[0].type, 'start');
34+
assert.strictEqual(events[1].type, 'end');
35+
36+
// Context should have partial result but no error from throw
37+
assert.strictEqual(context.result, 'partial');
38+
assert.strictEqual(context.error, undefined);
39+
}
40+
41+
// Test store restoration on error
42+
{
43+
const boundedChannel = dc.boundedChannel('test-scope-store-error');
44+
const store = new AsyncLocalStorage();
45+
46+
boundedChannel.start.bindStore(store, (context) => context.value);
47+
48+
boundedChannel.subscribe({
49+
start() {},
50+
end() {},
51+
});
52+
53+
store.enterWith('before');
54+
assert.strictEqual(store.getStore(), 'before');
55+
56+
const testError = new Error('test');
57+
58+
assert.throws(() => {
59+
using scope = boundedChannel.withScope({ value: 'during' });
60+
assert.strictEqual(store.getStore(), 'during');
61+
throw testError;
62+
}, testError);
63+
64+
// Store should be restored even after error
65+
assert.strictEqual(store.getStore(), 'before');
66+
}
67+
68+
// Test dispose during exception handling
69+
{
70+
const boundedChannel = dc.boundedChannel('test-scope-dispose-exception');
71+
const events = [];
72+
73+
boundedChannel.subscribe({
74+
start() {
75+
events.push('start');
76+
},
77+
end() {
78+
events.push('end');
79+
},
80+
});
81+
82+
// Dispose should complete even when exception is thrown
83+
assert.throws(() => {
84+
using scope = boundedChannel.withScope({});
85+
throw new Error('original error');
86+
}, /original error/);
87+
88+
// End event should have been called
89+
assert.deepStrictEqual(events, ['start', 'end']);
90+
}

0 commit comments

Comments
0 (0)
Morty Proxy This is a proxified and sanitized view of the page, visit original site.