1
1
'use strict' ;
2
2
3
3
const {
4
+ ArrayPrototypeAt,
4
5
ArrayPrototypeIndexOf,
5
6
ArrayPrototypePush,
6
7
ArrayPrototypeSplice,
8
+ FunctionPrototypeBind,
7
9
ObjectCreate,
8
10
ObjectGetPrototypeOf,
9
11
ObjectSetPrototypeOf,
12
+ PromisePrototypeThen,
13
+ PromiseReject,
14
+ ReflectApply,
15
+ SafeMap,
10
16
SymbolHasInstance,
11
17
} = primordials ;
12
18
@@ -23,11 +29,44 @@ const { triggerUncaughtException } = internalBinding('errors');
23
29
24
30
const { WeakReference } = internalBinding ( 'util' ) ;
25
31
32
+ function decRef ( channel ) {
33
+ channel . _weak . decRef ( ) ;
34
+ if ( channel . _weak . getRef ( ) === 0 ) {
35
+ delete channels [ channel . name ] ;
36
+ }
37
+ }
38
+
39
+ function markActive ( channel ) {
40
+ // eslint-disable-next-line no-use-before-define
41
+ ObjectSetPrototypeOf ( channel , ActiveChannel . prototype ) ;
42
+ channel . _subscribers = [ ] ;
43
+ channel . _stores = new SafeMap ( ) ;
44
+ }
45
+
46
+ function maybeMarkInactive ( channel ) {
47
+ // When there are no more active subscribers, restore to fast prototype.
48
+ if ( ! channel . _subscribers . length && ! channel . _stores . size ) {
49
+ // eslint-disable-next-line no-use-before-define
50
+ ObjectSetPrototypeOf ( channel , Channel . prototype ) ;
51
+ channel . _subscribers = undefined ;
52
+ channel . _stores = undefined ;
53
+ }
54
+ }
55
+
56
+ function defaultTransform ( data ) {
57
+ return data
58
+ }
59
+
60
+ function wrapStoreRun ( store , data , next , transform = defaultTransform ) {
61
+ return ( ) => store . run ( transform ( data ) , next ) ;
62
+ }
63
+
26
64
// TODO(qard): should there be a C++ channel interface?
27
65
class ActiveChannel {
28
66
subscribe ( subscription ) {
29
67
validateFunction ( subscription , 'subscription' ) ;
30
68
ArrayPrototypePush ( this . _subscribers , subscription ) ;
69
+ this . _weak . incRef ( ) ;
31
70
}
32
71
33
72
unsubscribe ( subscription ) {
@@ -36,12 +75,28 @@ class ActiveChannel {
36
75
37
76
ArrayPrototypeSplice ( this . _subscribers , index , 1 ) ;
38
77
39
- // When there are no more active subscribers, restore to fast prototype.
40
- if ( ! this . _subscribers . length ) {
41
- // eslint-disable-next-line no-use-before-define
42
- ObjectSetPrototypeOf ( this , Channel . prototype ) ;
78
+ decRef ( this ) ;
79
+ maybeMarkInactive ( this ) ;
80
+
81
+ return true ;
82
+ }
83
+
84
+ bindStore ( store , transform ) {
85
+ const replacing = this . _stores . has ( store ) ;
86
+ if ( ! replacing ) this . _weak . incRef ( ) ;
87
+ this . _stores . set ( store , transform ) ;
88
+ }
89
+
90
+ unbindStore ( store ) {
91
+ if ( ! this . _stores . has ( store ) ) {
92
+ return false ;
43
93
}
44
94
95
+ this . _stores . delete ( store ) ;
96
+
97
+ decRef ( this ) ;
98
+ maybeMarkInactive ( this ) ;
99
+
45
100
return true ;
46
101
}
47
102
@@ -61,11 +116,28 @@ class ActiveChannel {
61
116
}
62
117
}
63
118
}
119
+
120
+ runStores ( data , fn , thisArg , ...args ) {
121
+ this . publish ( data ) ;
122
+
123
+ // Bind base fn first due to AsyncLocalStorage.run not having thisArg
124
+ fn = FunctionPrototypeBind ( fn , thisArg , ...args ) ;
125
+
126
+ for ( const entry of this . _stores . entries ( ) ) {
127
+ const store = entry [ 0 ] ;
128
+ const transform = entry [ 1 ] ;
129
+ fn = wrapStoreRun ( store , data , fn , transform ) ;
130
+ }
131
+
132
+ return fn ( ) ;
133
+ }
64
134
}
65
135
66
136
class Channel {
67
137
constructor ( name ) {
68
138
this . _subscribers = undefined ;
139
+ this . _stores = undefined ;
140
+ this . _weak = undefined ;
69
141
this . name = name ;
70
142
}
71
143
@@ -76,20 +148,32 @@ class Channel {
76
148
}
77
149
78
150
subscribe ( subscription ) {
79
- ObjectSetPrototypeOf ( this , ActiveChannel . prototype ) ;
80
- this . _subscribers = [ ] ;
151
+ markActive ( this ) ;
81
152
this . subscribe ( subscription ) ;
82
153
}
83
154
84
155
unsubscribe ( ) {
85
156
return false ;
86
157
}
87
158
159
+ bindStore ( store , transform ) {
160
+ markActive ( this ) ;
161
+ this . bindStore ( store , transform ) ;
162
+ }
163
+
164
+ unbindStore ( ) {
165
+ return false ;
166
+ }
167
+
88
168
get hasSubscribers ( ) {
89
169
return false ;
90
170
}
91
171
92
172
publish ( ) { }
173
+
174
+ runStores ( data , fn , thisArg , ...args ) {
175
+ return ReflectApply ( fn , thisArg , args ) ;
176
+ }
93
177
}
94
178
95
179
const channels = ObjectCreate ( null ) ;
@@ -105,27 +189,17 @@ function channel(name) {
105
189
}
106
190
107
191
channel = new Channel ( name ) ;
108
- channels [ name ] = new WeakReference ( channel ) ;
192
+ channel . _weak = new WeakReference ( channel ) ;
193
+ channels [ name ] = channel . _weak ;
109
194
return channel ;
110
195
}
111
196
112
197
function subscribe ( name , subscription ) {
113
- const chan = channel ( name ) ;
114
- channels [ name ] . incRef ( ) ;
115
- chan . subscribe ( subscription ) ;
198
+ return channel ( name ) . subscribe ( subscription ) ;
116
199
}
117
200
118
201
function unsubscribe ( name , subscription ) {
119
- const chan = channel ( name ) ;
120
- if ( ! chan . unsubscribe ( subscription ) ) {
121
- return false ;
122
- }
123
-
124
- channels [ name ] . decRef ( ) ;
125
- if ( channels [ name ] . getRef ( ) === 0 ) {
126
- delete channels [ name ] ;
127
- }
128
- return true ;
202
+ return channel ( name ) . unsubscribe ( subscription ) ;
129
203
}
130
204
131
205
function hasSubscribers ( name ) {
@@ -139,10 +213,165 @@ function hasSubscribers(name) {
139
213
return channel . hasSubscribers ;
140
214
}
141
215
216
+ const traceEvents = [
217
+ 'start' ,
218
+ 'end' ,
219
+ 'asyncStart' ,
220
+ 'asyncEnd' ,
221
+ 'error' ,
222
+ ] ;
223
+
224
+ function assertChannel ( value , name ) {
225
+ if ( ! ( value instanceof Channel ) ) {
226
+ throw new ERR_INVALID_ARG_TYPE ( name , [ 'Channel' ] , value ) ;
227
+ }
228
+ }
229
+
230
+ class TracingChannel {
231
+ constructor ( nameOrChannels ) {
232
+ if ( typeof nameOrChannels === 'string' ) {
233
+ this . start = channel ( `tracing:${ nameOrChannels } :start` ) ;
234
+ this . end = channel ( `tracing:${ nameOrChannels } :end` ) ;
235
+ this . asyncStart = channel ( `tracing:${ nameOrChannels } :asyncStart` ) ;
236
+ this . asyncEnd = channel ( `tracing:${ nameOrChannels } :asyncEnd` ) ;
237
+ this . error = channel ( `tracing:${ nameOrChannels } :error` ) ;
238
+ } else if ( typeof nameOrChannels === 'object' ) {
239
+ const { start, end, asyncStart, asyncEnd, error } = nameOrChannels ;
240
+
241
+ assertChannel ( start , 'nameOrChannels.start' ) ;
242
+ assertChannel ( end , 'nameOrChannels.end' ) ;
243
+ assertChannel ( asyncStart , 'nameOrChannels.asyncStart' ) ;
244
+ assertChannel ( asyncEnd , 'nameOrChannels.asyncEnd' ) ;
245
+ assertChannel ( error , 'nameOrChannels.error' ) ;
246
+
247
+ this . start = start ;
248
+ this . end = end ;
249
+ this . asyncStart = asyncStart ;
250
+ this . asyncEnd = asyncEnd ;
251
+ this . error = error ;
252
+ } else {
253
+ throw new ERR_INVALID_ARG_TYPE ( 'nameOrChannels' ,
254
+ [ 'string' , 'object' , 'Channel' ] ,
255
+ nameOrChannels ) ;
256
+ }
257
+ }
258
+
259
+ subscribe ( handlers ) {
260
+ for ( const name of traceEvents ) {
261
+ if ( ! handlers [ name ] ) continue ;
262
+
263
+ this [ name ] ?. subscribe ( handlers [ name ] ) ;
264
+ }
265
+ }
266
+
267
+ unsubscribe ( handlers ) {
268
+ let done = true ;
269
+
270
+ for ( const name of traceEvents ) {
271
+ if ( ! handlers [ name ] ) continue ;
272
+
273
+ if ( ! this [ name ] ?. unsubscribe ( handlers [ name ] ) ) {
274
+ done = false ;
275
+ }
276
+ }
277
+
278
+ return done ;
279
+ }
280
+
281
+ traceSync ( fn , ctx = { } , thisArg , ...args ) {
282
+ const { start, end, error } = this ;
283
+
284
+ try {
285
+ const result = start . runStores ( ctx , fn , thisArg , ...args ) ;
286
+ ctx . result = result ;
287
+ return result ;
288
+ } catch ( err ) {
289
+ ctx . error = err ;
290
+ error . publish ( ctx ) ;
291
+ throw err ;
292
+ } finally {
293
+ end . publish ( ctx ) ;
294
+ }
295
+ }
296
+
297
+ tracePromise ( fn , ctx = { } , thisArg , ...args ) {
298
+ const { start, end, asyncStart, asyncEnd, error } = this ;
299
+
300
+ function reject ( err ) {
301
+ ctx . error = err ;
302
+ error . publish ( ctx ) ;
303
+ asyncStart . publish ( ctx ) ;
304
+ // TODO: Is there a way to have asyncEnd _after_ the continuation?
305
+ asyncEnd . publish ( ctx ) ;
306
+ return PromiseReject ( err ) ;
307
+ }
308
+
309
+ function resolve ( result ) {
310
+ ctx . result = result ;
311
+ asyncStart . publish ( ctx ) ;
312
+ // TODO: Is there a way to have asyncEnd _after_ the continuation?
313
+ asyncEnd . publish ( ctx ) ;
314
+ return result ;
315
+ }
316
+
317
+ try {
318
+ const promise = start . runStores ( ctx , fn , thisArg , ...args ) ;
319
+ return PromisePrototypeThen ( promise , resolve , reject ) ;
320
+ } catch ( err ) {
321
+ ctx . error = err ;
322
+ error . publish ( ctx ) ;
323
+ throw err ;
324
+ } finally {
325
+ end . publish ( ctx ) ;
326
+ }
327
+ }
328
+
329
+ traceCallback ( fn , position = 0 , ctx = { } , thisArg , ...args ) {
330
+ const { start, end, asyncStart, asyncEnd, error } = this ;
331
+
332
+ function wrappedCallback ( err , res ) {
333
+ if ( err ) {
334
+ ctx . error = err ;
335
+ error . publish ( ctx ) ;
336
+ } else {
337
+ ctx . result = res ;
338
+ }
339
+
340
+ asyncStart . publish ( ctx ) ;
341
+ try {
342
+ if ( callback ) {
343
+ return ReflectApply ( callback , this , arguments ) ;
344
+ }
345
+ } finally {
346
+ asyncEnd . publish ( ctx ) ;
347
+ }
348
+ }
349
+
350
+ const callback = ArrayPrototypeAt ( args , position ) ;
351
+ ArrayPrototypeSplice ( args , position , 1 , wrappedCallback ) ;
352
+
353
+ try {
354
+ return start . runStores ( ctx , fn , thisArg , ...args ) ;
355
+ } catch ( err ) {
356
+ ctx . error = err ;
357
+ error . publish ( ctx ) ;
358
+ throw err ;
359
+ } finally {
360
+ end . publish ( ctx ) ;
361
+ }
362
+ }
363
+ }
364
+
365
+ function tracingChannel ( nameOrChannels ) {
366
+ return new TracingChannel ( nameOrChannels ) ;
367
+ }
368
+
142
369
module . exports = {
143
370
channel,
144
371
hasSubscribers,
145
372
subscribe,
373
+ tracingChannel,
146
374
unsubscribe,
147
- Channel
375
+ Channel,
376
+ TracingChannel
148
377
} ;
0 commit comments