|
4 | 4 | ArrayPrototypeIndexOf, |
5 | 5 | ArrayPrototypePush, |
6 | 6 | ArrayPrototypeSplice, |
| 7 | + SafeFinalizationRegistry, |
7 | 8 | ObjectGetPrototypeOf, |
8 | 9 | ObjectSetPrototypeOf, |
9 | 10 | Promise, |
@@ -28,14 +29,29 @@ const { triggerUncaughtException } = internalBinding('errors'); |
28 | 29 |
|
29 | 30 | const { WeakReference } = internalBinding('util'); |
30 | 31 |
|
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)); |
34 | 42 | } |
35 | | -} |
36 | 43 |
|
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 | + } |
39 | 55 | } |
40 | 56 |
|
41 | 57 | function markActive(channel) { |
@@ -80,7 +96,7 @@ class ActiveChannel { |
80 | 96 | subscribe(subscription) { |
81 | 97 | validateFunction(subscription, 'subscription'); |
82 | 98 | ArrayPrototypePush(this._subscribers, subscription); |
83 | | - incRef(this); |
| 99 | + channels.incRef(this.name); |
84 | 100 | } |
85 | 101 |
|
86 | 102 | unsubscribe(subscription) { |
@@ -89,15 +105,15 @@ class ActiveChannel { |
89 | 105 |
|
90 | 106 | ArrayPrototypeSplice(this._subscribers, index, 1); |
91 | 107 |
|
92 | | - decRef(this); |
| 108 | + channels.decRef(this.name); |
93 | 109 | maybeMarkInactive(this); |
94 | 110 |
|
95 | 111 | return true; |
96 | 112 | } |
97 | 113 |
|
98 | 114 | bindStore(store, transform) { |
99 | 115 | const replacing = this._stores.has(store); |
100 | | - if (!replacing) incRef(this); |
| 116 | + if (!replacing) channels.incRef(this.name); |
101 | 117 | this._stores.set(store, transform); |
102 | 118 | } |
103 | 119 |
|
@@ -108,7 +124,7 @@ class ActiveChannel { |
108 | 124 |
|
109 | 125 | this._stores.delete(store); |
110 | 126 |
|
111 | | - decRef(this); |
| 127 | + channels.decRef(this.name); |
112 | 128 | maybeMarkInactive(this); |
113 | 129 |
|
114 | 130 | return true; |
@@ -153,7 +169,7 @@ class Channel { |
153 | 169 | this._stores = undefined; |
154 | 170 | this.name = name; |
155 | 171 |
|
156 | | - channels.set(name, new WeakReference(this)); |
| 172 | + channels.set(name, this); |
157 | 173 | } |
158 | 174 |
|
159 | 175 | static [SymbolHasInstance](instance) { |
@@ -191,12 +207,10 @@ class Channel { |
191 | 207 | } |
192 | 208 | } |
193 | 209 |
|
194 | | -const channels = new SafeMap(); |
| 210 | +const channels = new WeakRefMap(); |
195 | 211 |
|
196 | 212 | function channel(name) { |
197 | | - let channel; |
198 | | - const ref = channels.get(name); |
199 | | - if (ref) channel = ref.get(); |
| 213 | + const channel = channels.get(name); |
200 | 214 | if (channel) return channel; |
201 | 215 |
|
202 | 216 | if (typeof name !== 'string' && typeof name !== 'symbol') { |
@@ -215,12 +229,8 @@ function unsubscribe(name, subscription) { |
215 | 229 | } |
216 | 230 |
|
217 | 231 | 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; |
224 | 234 |
|
225 | 235 | return channel.hasSubscribers; |
226 | 236 | } |
|