Skip to content

Commit 88f9ebb

Browse files
Stephen Belangertargos
Stephen Belanger
authored andcommittedNov 10, 2023
diagnostics_channel: fix ref counting bug when reaching zero subscribers
PR-URL: #47520 Reviewed-By: Robert Nagy <ronagy@icloud.com> Reviewed-By: Rafael Gonzaga <rafael.nunu@hotmail.com> Reviewed-By: Gerhard Stöbich <deb2001-github@yahoo.de>
1 parent 4f78233 commit 88f9ebb

File tree

2 files changed

+38
-21
lines changed

2 files changed

+38
-21
lines changed
 

‎lib/diagnostics_channel.js

+31-21
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ const {
44
ArrayPrototypeIndexOf,
55
ArrayPrototypePush,
66
ArrayPrototypeSplice,
7+
SafeFinalizationRegistry,
78
ObjectGetPrototypeOf,
89
ObjectSetPrototypeOf,
910
Promise,
@@ -28,14 +29,29 @@ const { triggerUncaughtException } = internalBinding('errors');
2829

2930
const { WeakReference } = internalBinding('util');
3031

31-
function decRef(channel) {
32-
if (channels.get(channel.name).decRef() === 0) {
33-
channels.delete(channel.name);
32+
// Can't delete when weakref count reaches 0 as it could increment again.
33+
// Only GC can be used as a valid time to clean up the channels map.
34+
class WeakRefMap extends SafeMap {
35+
#finalizers = new SafeFinalizationRegistry((key) => {
36+
this.delete(key);
37+
});
38+
39+
set(key, value) {
40+
this.#finalizers.register(value, key);
41+
return super.set(key, new WeakReference(value));
3442
}
35-
}
3643

37-
function incRef(channel) {
38-
channels.get(channel.name).incRef();
44+
get(key) {
45+
return super.get(key)?.get();
46+
}
47+
48+
incRef(key) {
49+
return super.get(key)?.incRef();
50+
}
51+
52+
decRef(key) {
53+
return super.get(key)?.decRef();
54+
}
3955
}
4056

4157
function markActive(channel) {
@@ -80,7 +96,7 @@ class ActiveChannel {
8096
subscribe(subscription) {
8197
validateFunction(subscription, 'subscription');
8298
ArrayPrototypePush(this._subscribers, subscription);
83-
incRef(this);
99+
channels.incRef(this.name);
84100
}
85101

86102
unsubscribe(subscription) {
@@ -89,15 +105,15 @@ class ActiveChannel {
89105

90106
ArrayPrototypeSplice(this._subscribers, index, 1);
91107

92-
decRef(this);
108+
channels.decRef(this.name);
93109
maybeMarkInactive(this);
94110

95111
return true;
96112
}
97113

98114
bindStore(store, transform) {
99115
const replacing = this._stores.has(store);
100-
if (!replacing) incRef(this);
116+
if (!replacing) channels.incRef(this.name);
101117
this._stores.set(store, transform);
102118
}
103119

@@ -108,7 +124,7 @@ class ActiveChannel {
108124

109125
this._stores.delete(store);
110126

111-
decRef(this);
127+
channels.decRef(this.name);
112128
maybeMarkInactive(this);
113129

114130
return true;
@@ -153,7 +169,7 @@ class Channel {
153169
this._stores = undefined;
154170
this.name = name;
155171

156-
channels.set(name, new WeakReference(this));
172+
channels.set(name, this);
157173
}
158174

159175
static [SymbolHasInstance](instance) {
@@ -191,12 +207,10 @@ class Channel {
191207
}
192208
}
193209

194-
const channels = new SafeMap();
210+
const channels = new WeakRefMap();
195211

196212
function channel(name) {
197-
let channel;
198-
const ref = channels.get(name);
199-
if (ref) channel = ref.get();
213+
const channel = channels.get(name);
200214
if (channel) return channel;
201215

202216
if (typeof name !== 'string' && typeof name !== 'symbol') {
@@ -215,12 +229,8 @@ function unsubscribe(name, subscription) {
215229
}
216230

217231
function hasSubscribers(name) {
218-
let channel;
219-
const ref = channels.get(name);
220-
if (ref) channel = ref.get();
221-
if (!channel) {
222-
return false;
223-
}
232+
const channel = channels.get(name);
233+
if (!channel) return false;
224234

225235
return channel.hasSubscribers;
226236
}

‎test/parallel/test-diagnostics-channel-pub-sub.js

+7
Original file line numberDiff line numberDiff line change
@@ -42,3 +42,10 @@ assert.ok(!dc.unsubscribe(name, subscriber));
4242
assert.throws(() => {
4343
dc.subscribe(name, null);
4444
}, { code: 'ERR_INVALID_ARG_TYPE' });
45+
46+
// Reaching zero subscribers should not delete from the channels map as there
47+
// will be no more weakref to incRef if another subscribe happens while the
48+
// channel object itself exists.
49+
channel.subscribe(subscriber);
50+
channel.unsubscribe(subscriber);
51+
channel.subscribe(subscriber);

0 commit comments

Comments
 (0)
Please sign in to comment.