summaryrefslogtreecommitdiff
path: root/test/async-hooks/test-enable-disable.js
blob: f71fd63e1c77567e1bc635f6201ab9644d8897d5 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
/*
 *  Test Steps Explained
 *  ====================
 *
 *  Initializing hooks:
 *
 *  We initialize 3 hooks. For hook2 and hook3 we register a callback for the
 *  "before" and in case of hook3 also for the "after" invocations.
 *
 *  Enabling hooks initially:
 *
 *  We only enable hook1 and hook3 initially.
 *
 *  Enabling hook2:
 *
 *  When hook3's "before" invocation occurs we enable hook2.  Since this
 *  happens right before calling `onfirstImmediate` hook2 will miss all hook
 *  invocations until then, including the "init" and "before" of the first
 *  Immediate.
 *  However afterwards it collects all invocations that follow on the first
 *  Immediate as well as all invocations on the second Immediate.
 *
 *  This shows that a hook can enable another hook inside a life time event
 *  callback.
 *
 *
 *  Disabling hook1
 *
 *  Since we registered the "before" callback for hook2 it will execute it
 *  right before `onsecondImmediate` is called.
 *  At that point we disable hook1 which is why it will miss all invocations
 *  afterwards and thus won't include the second "after" as well as the
 *  "destroy" invocations
 *
 *  This shows that a hook can disable another hook inside a life time event
 *  callback.
 *
 *  Disabling hook3
 *
 *  When the second "after" invocation occurs (after onsecondImmediate), hook3
 *  disables itself.
 *  As a result it will not receive the "destroy" invocation.
 *
 *  This shows that a hook can disable itself inside a life time event callback.
 *
 *  Sample Test Log
 *  ===============
 *
 *  - setting up first Immediate
 *  hook1.init.uid-5
 *  hook3.init.uid-5
 *  - finished setting first Immediate

 *  hook1.before.uid-5
 *  hook3.before.uid-5
 *  - enabled hook2
 *  - entering onfirstImmediate

 *  - setting up second Immediate
 *  hook1.init.uid-6
 *  hook3.init.uid-6
 *  hook2.init.uid-6
 *  - finished setting second Immediate

 *  - exiting onfirstImmediate
 *  hook1.after.uid-5
 *  hook3.after.uid-5
 *  hook2.after.uid-5
 *  hook1.destroy.uid-5
 *  hook3.destroy.uid-5
 *  hook2.destroy.uid-5
 *  hook1.before.uid-6
 *  hook3.before.uid-6
 *  hook2.before.uid-6
 *  - disabled hook1
 *  - entering onsecondImmediate
 *  - exiting onsecondImmediate
 *  hook3.after.uid-6
 *  - disabled hook3
 *  hook2.after.uid-6
 *  hook2.destroy.uid-6
 */

'use strict';

const common = require('../common');
const assert = require('assert');
const tick = require('../common/tick');
const initHooks = require('./init-hooks');
const { checkInvocations } = require('./hook-checks');

if (!common.isMainThread)
  common.skip('Worker bootstrapping works differently -> different timing');

// Include "Unknown"s because hook2 will not be able to identify
// the type of the first Immediate  since it will miss its `init` invocation.
const types = [ 'Immediate', 'Unknown' ];

//
// Initializing hooks
//
const hook1 = initHooks();
const hook2 = initHooks({ onbefore: onhook2Before, allowNoInit: true });
const hook3 = initHooks({ onbefore: onhook3Before, onafter: onhook3After });

//
// Enabling hook1 and hook3 only, hook2 is still disabled
//
hook1.enable();
// Verify that the hook is enabled even if .enable() is called twice.
hook1.enable();
hook3.enable();

//
// Enabling hook2
//
let enabledHook2 = false;
function onhook3Before() {
  if (enabledHook2) return;
  hook2.enable();
  enabledHook2 = true;
}

//
// Disabling hook1
//
let disabledHook3 = false;
function onhook2Before() {
  if (disabledHook3) return;
  hook1.disable();
  // Verify that the hook is disabled even if .disable() is called twice.
  hook1.disable();
  disabledHook3 = true;
}

//
// Disabling hook3 during the second "after" invocations it sees
//
let count = 2;
function onhook3After() {
  if (!--count) {
    hook3.disable();
  }
}

setImmediate(common.mustCall(onfirstImmediate));

//
// onfirstImmediate is called after all "init" and "before" callbacks of the
// active hooks were invoked
//
function onfirstImmediate() {
  const as1 = hook1.activitiesOfTypes(types);
  const as2 = hook2.activitiesOfTypes(types);
  const as3 = hook3.activitiesOfTypes(types);
  assert.strictEqual(as1.length, 1);
  // hook2 was not enabled yet .. it is enabled after hook3's "before" completed
  assert.strictEqual(as2.length, 0);
  assert.strictEqual(as3.length, 1);

  // Check that hook1 and hook3 captured the same Immediate and that it is valid
  const firstImmediate = as1[0];
  assert.strictEqual(as3[0].uid, as1[0].uid);
  assert.strictEqual(firstImmediate.type, 'Immediate');
  assert.strictEqual(typeof firstImmediate.uid, 'number');
  assert.strictEqual(typeof firstImmediate.triggerAsyncId, 'number');
  checkInvocations(as1[0], { init: 1, before: 1 },
                   'hook1[0]: on first immediate');
  checkInvocations(as3[0], { init: 1, before: 1 },
                   'hook3[0]: on first immediate');

  // Setup the second Immediate, note that now hook2 is enabled and thus
  // will capture all lifetime events of this Immediate
  setImmediate(common.mustCall(onsecondImmediate));
}

//
// Once we exit onfirstImmediate the "after" callbacks of the active hooks are
// invoked
//

let hook1First, hook2First, hook3First;
let hook1Second, hook2Second, hook3Second;

//
// onsecondImmediate is called after all "before" callbacks of the active hooks
// are invoked again
//
function onsecondImmediate() {
  const as1 = hook1.activitiesOfTypes(types);
  const as2 = hook2.activitiesOfTypes(types);
  const as3 = hook3.activitiesOfTypes(types);
  assert.strictEqual(as1.length, 2);
  assert.strictEqual(as2.length, 2);
  assert.strictEqual(as3.length, 2);

  // Assign the info collected by each hook for each immediate for easier
  // reference.
  // hook2 saw the "init" of the second immediate before the
  // "after" of the first which is why they are ordered the opposite way
  hook1First = as1[0];
  hook1Second = as1[1];
  hook2First = as2[1];
  hook2Second = as2[0];
  hook3First = as3[0];
  hook3Second = as3[1];

  // Check that all hooks captured the same Immediate and that it is valid
  const secondImmediate = hook1Second;
  assert.strictEqual(hook2Second.uid, hook3Second.uid);
  assert.strictEqual(hook1Second.uid, hook3Second.uid);
  assert.strictEqual(secondImmediate.type, 'Immediate');
  assert.strictEqual(typeof secondImmediate.uid, 'number');
  assert.strictEqual(typeof secondImmediate.triggerAsyncId, 'number');

  checkInvocations(hook1First, { init: 1, before: 1, after: 1, destroy: 1 },
                   'hook1First: on second immediate');
  checkInvocations(hook1Second, { init: 1, before: 1 },
                   'hook1Second: on second immediate');
  // hook2 missed the "init" and "before" since it was enabled after they
  // occurred
  checkInvocations(hook2First, { after: 1, destroy: 1 },
                   'hook2First: on second immediate');
  checkInvocations(hook2Second, { init: 1, before: 1 },
                   'hook2Second: on second immediate');
  checkInvocations(hook3First, { init: 1, before: 1, after: 1, destroy: 1 },
                   'hook3First: on second immediate');
  checkInvocations(hook3Second, { init: 1, before: 1 },
                   'hook3Second: on second immediate');
  tick(1);
}

//
// Once we exit onsecondImmediate the "after" callbacks of the active hooks are
// invoked again.
// During this second "after" invocation hook3 disables itself
// (see onhook3After).
//

process.on('exit', onexit);

function onexit() {
  hook1.disable();
  hook2.disable();
  hook3.disable();
  hook1.sanityCheck();
  hook2.sanityCheck();
  hook3.sanityCheck();

  checkInvocations(hook1First, { init: 1, before: 1, after: 1, destroy: 1 },
                   'hook1First: when process exits');
  // hook1 was disabled during hook2's "before" of the second immediate
  // and thus did not see "after" and "destroy"
  checkInvocations(hook1Second, { init: 1, before: 1 },
                   'hook1Second: when process exits');
  // hook2 missed the "init" and "before" since it was enabled after they
  // occurred
  checkInvocations(hook2First, { after: 1, destroy: 1 },
                   'hook2First: when process exits');
  checkInvocations(hook2Second, { init: 1, before: 1, after: 1, destroy: 1 },
                   'hook2Second: when process exits');
  checkInvocations(hook3First, { init: 1, before: 1, after: 1, destroy: 1 },
                   'hook3First: when process exits');
  // We don't see a "destroy" invocation here since hook3 disabled itself
  // during its "after" invocation
  checkInvocations(hook3Second, { init: 1, before: 1, after: 1 },
                   'hook3Second: when process exits');
}