Skip to content

Commit 27df399

Browse files
committed
diagnostics_channel: return a disposable from subscribe
Signed-off-by: Guilherme Araújo <arauujogui@gmail.com>
1 parent b2b2b41 commit 27df399

3 files changed

Lines changed: 409 additions & 1 deletion

File tree

doc/api/diagnostics_channel.md

Lines changed: 161 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -165,12 +165,17 @@ const channel = diagnostics_channel.channel('my-channel');
165165
added:
166166
- v18.7.0
167167
- v16.17.0
168+
changes:
169+
- version: REPLACEME
170+
pr-url: https://github.com/nodejs/node/pull/65383
171+
description: Returns a `Disposable` which removes the handler.
168172
-->
169173

170174
* `name` {string|symbol} The channel name
171175
* `onMessage` {Function} The handler to receive channel messages
172176
* `message` {any} The message data
173177
* `name` {string|symbol} The name of the channel
178+
* Returns: {Disposable} A Disposable that removes the message handler.
174179

175180
Register a message handler to subscribe to this channel. This message handler
176181
will be run synchronously whenever a message is published to the channel. Any
@@ -192,6 +197,32 @@ diagnostics_channel.subscribe('my-channel', (message, name) => {
192197
});
193198
```
194199

200+
The returned Disposable removes the message handler, which allows the
201+
subscription to be scoped with the [`using`][] syntax. Disposing it more than
202+
once has no further effect.
203+
204+
```mjs
205+
import diagnostics_channel from 'node:diagnostics_channel';
206+
207+
{
208+
using subscription = diagnostics_channel.subscribe('my-channel', (message, name) => {
209+
// Received data
210+
});
211+
}
212+
// The handler is removed on scope exit
213+
```
214+
215+
```cjs
216+
const diagnostics_channel = require('node:diagnostics_channel');
217+
218+
{
219+
using subscription = diagnostics_channel.subscribe('my-channel', (message, name) => {
220+
// Received data
221+
});
222+
}
223+
// The handler is removed on scope exit
224+
```
225+
195226
#### `diagnostics_channel.unsubscribe(name, onMessage)`
196227

197228
<!-- YAML
@@ -425,6 +456,9 @@ added:
425456
- v15.1.0
426457
- v14.17.0
427458
changes:
459+
- version: REPLACEME
460+
pr-url: https://github.com/nodejs/node/pull/65383
461+
description: Returns a `Disposable` which removes the handler.
428462
- version:
429463
- v24.8.0
430464
- v22.20.0
@@ -440,6 +474,7 @@ changes:
440474
* `onMessage` {Function} The handler to receive channel messages
441475
* `message` {any} The message data
442476
* `name` {string|symbol} The name of the channel
477+
* Returns: {Disposable} A Disposable that removes the message handler.
443478

444479
Register a message handler to subscribe to this channel. This message handler
445480
will be run synchronously whenever a message is published to the channel. Any
@@ -465,6 +500,39 @@ channel.subscribe((message, name) => {
465500
});
466501
```
467502

503+
The returned Disposable removes the message handler, which allows the
504+
subscription to be scoped with the [`using`][] syntax instead of pairing the
505+
call with [`channel.unsubscribe(onMessage)`][]. Disposing it more than once has
506+
no further effect.
507+
508+
```mjs
509+
import diagnostics_channel from 'node:diagnostics_channel';
510+
511+
const channel = diagnostics_channel.channel('my-channel');
512+
513+
{
514+
using subscription = channel.subscribe((message, name) => {
515+
// Received data
516+
});
517+
console.log(channel.hasSubscribers); // true
518+
}
519+
console.log(channel.hasSubscribers); // false
520+
```
521+
522+
```cjs
523+
const diagnostics_channel = require('node:diagnostics_channel');
524+
525+
const channel = diagnostics_channel.channel('my-channel');
526+
527+
{
528+
using subscription = channel.subscribe((message, name) => {
529+
// Received data
530+
});
531+
console.log(channel.hasSubscribers); // true
532+
}
533+
console.log(channel.hasSubscribers); // false
534+
```
535+
468536
#### `channel.unsubscribe(onMessage)`
469537

470538
<!-- YAML
@@ -770,6 +838,10 @@ dynamically.
770838
added:
771839
- v19.9.0
772840
- v18.19.0
841+
changes:
842+
- version: REPLACEME
843+
pr-url: https://github.com/nodejs/node/pull/65383
844+
description: Returns a `Disposable` which removes the handlers.
773845
-->
774846

775847
* `subscribers` {Object} Set of [TracingChannel Channels][] subscribers
@@ -778,6 +850,8 @@ added:
778850
* `asyncStart` {Function} The [`asyncStart` event][] subscriber
779851
* `asyncEnd` {Function} The [`asyncEnd` event][] subscriber
780852
* `error` {Function} The [`error` event][] subscriber
853+
* Returns: {Disposable} A Disposable that removes every subscriber the call
854+
registered.
781855

782856
Helper to subscribe a collection of functions to the corresponding channels.
783857
This is the same as calling [`channel.subscribe(onMessage)`][] on each channel
@@ -831,6 +905,46 @@ channels.subscribe({
831905
});
832906
```
833907

908+
The returned Disposable removes every subscriber the call registered, which
909+
allows the whole set to be scoped with the [`using`][] syntax. Disposing it more
910+
than once has no further effect.
911+
912+
```mjs
913+
import diagnostics_channel from 'node:diagnostics_channel';
914+
915+
const channels = diagnostics_channel.tracingChannel('my-channel');
916+
917+
{
918+
using subscription = channels.subscribe({
919+
start(message) {
920+
// Handle start message
921+
},
922+
end(message) {
923+
// Handle end message
924+
},
925+
});
926+
}
927+
// Both handlers are removed on scope exit
928+
```
929+
930+
```cjs
931+
const diagnostics_channel = require('node:diagnostics_channel');
932+
933+
const channels = diagnostics_channel.tracingChannel('my-channel');
934+
935+
{
936+
using subscription = channels.subscribe({
937+
start(message) {
938+
// Handle start message
939+
},
940+
end(message) {
941+
// Handle end message
942+
},
943+
});
944+
}
945+
// Both handlers are removed on scope exit
946+
```
947+
834948
#### `tracingChannel.unsubscribe(subscribers)`
835949

836950
<!-- YAML
@@ -1208,11 +1322,17 @@ if (wc.hasSubscribers) {
12081322

12091323
<!-- YAML
12101324
added: v26.1.0
1325+
changes:
1326+
- version: REPLACEME
1327+
pr-url: https://github.com/nodejs/node/pull/65383
1328+
description: Returns a `Disposable` which removes the handlers.
12111329
-->
12121330

12131331
* `handlers` {Object} Set of channel subscribers
12141332
* `start` {Function} The start event subscriber
12151333
* `end` {Function} The end event subscriber
1334+
* Returns: {Disposable} A Disposable that removes every subscriber the call
1335+
registered.
12161336

12171337
Subscribe to the bounded channel events. This is equivalent to calling
12181338
[`channel.subscribe(onMessage)`][] on each channel individually.
@@ -1247,6 +1367,46 @@ wc.subscribe({
12471367
});
12481368
```
12491369

1370+
The returned Disposable removes every subscriber the call registered, which
1371+
allows the whole set to be scoped with the [`using`][] syntax. Disposing it more
1372+
than once has no further effect.
1373+
1374+
```mjs
1375+
import { boundedChannel } from 'node:diagnostics_channel';
1376+
1377+
const wc = boundedChannel('my-operation');
1378+
1379+
{
1380+
using subscription = wc.subscribe({
1381+
start(message) {
1382+
// Handle start
1383+
},
1384+
end(message) {
1385+
// Handle end
1386+
},
1387+
});
1388+
}
1389+
// Both handlers are removed on scope exit
1390+
```
1391+
1392+
```cjs
1393+
const { boundedChannel } = require('node:diagnostics_channel');
1394+
1395+
const wc = boundedChannel('my-operation');
1396+
1397+
{
1398+
using subscription = wc.subscribe({
1399+
start(message) {
1400+
// Handle start
1401+
},
1402+
end(message) {
1403+
// Handle end
1404+
},
1405+
});
1406+
}
1407+
// Both handlers are removed on scope exit
1408+
```
1409+
12501410
#### `boundedChannel.unsubscribe(handlers)`
12511411

12521412
<!-- YAML
@@ -1986,6 +2146,7 @@ statement, since both are still in use while the event is being delivered; see
19862146
[`process.execve()`]: process.md#processexecvefile-args-env
19872147
[`start` event]: #startevent
19882148
[`statement.close()`]: sqlite.md#statementclose
2149+
[`using`]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/using
19892150
[`worker_threads.locks`]: worker_threads.md#worker_threadslocks
19902151
[context loss]: async_context.md#troubleshooting-context-loss
19912152
[thenable object]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise#thenables

lib/diagnostics_channel.js

Lines changed: 59 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -93,6 +93,27 @@ function maybeMarkInactive(channel) {
9393
}
9494
}
9595

96+
class ChannelSubscription {
97+
#channel;
98+
#subscription;
99+
100+
constructor(channel, subscription) {
101+
this.#channel = channel;
102+
this.#subscription = subscription;
103+
}
104+
105+
[SymbolDispose]() {
106+
const channel = this.#channel;
107+
if (channel === undefined) return;
108+
109+
const subscription = this.#subscription;
110+
this.#channel = undefined;
111+
this.#subscription = undefined;
112+
113+
channel.unsubscribe(subscription);
114+
}
115+
}
116+
96117
class RunStoresScope {
97118
#stack;
98119

@@ -142,6 +163,7 @@ class ActiveChannel {
142163
ArrayPrototypePush(this._subscribers, subscription);
143164
channels.incRef(this.name);
144165
if (this._index !== undefined) dc_binding.subscribers[this._index]++;
166+
return new ChannelSubscription(this, subscription);
145167
}
146168

147169
unsubscribe(subscription) {
@@ -230,7 +252,7 @@ class Channel {
230252

231253
subscribe(subscription) {
232254
markActive(this);
233-
this.subscribe(subscription);
255+
return this.subscribe(subscription);
234256
}
235257

236258
unsubscribe() {
@@ -324,6 +346,27 @@ function channelFromMap(nameOrChannels, name, className) {
324346
nameOrChannels);
325347
}
326348

349+
class HandlersSubscription {
350+
#target;
351+
#handlers;
352+
353+
constructor(target, handlers) {
354+
this.#target = target;
355+
this.#handlers = handlers;
356+
}
357+
358+
[SymbolDispose]() {
359+
const target = this.#target;
360+
if (target === undefined) return;
361+
362+
const handlers = this.#handlers;
363+
this.#target = undefined;
364+
this.#handlers = undefined;
365+
366+
target.unsubscribe(handlers);
367+
}
368+
}
369+
327370
class BoundedChannelScope {
328371
#context;
329372
#end;
@@ -380,6 +423,12 @@ class BoundedChannel {
380423

381424
this[name]?.subscribe(handlers[name]);
382425
}
426+
427+
return new HandlersSubscription(this, {
428+
__proto__: null,
429+
start: handlers.start,
430+
end: handlers.end,
431+
});
383432
}
384433

385434
unsubscribe(handlers) {
@@ -486,6 +535,15 @@ class TracingChannel {
486535
if (handlers.error) {
487536
this.error.subscribe(handlers.error);
488537
}
538+
539+
return new HandlersSubscription(this, {
540+
__proto__: null,
541+
start: handlers.start,
542+
end: handlers.end,
543+
asyncStart: handlers.asyncStart,
544+
asyncEnd: handlers.asyncEnd,
545+
error: handlers.error,
546+
});
489547
}
490548

491549
unsubscribe(handlers) {

0 commit comments

Comments
 (0)