ava 3.12.1 → 3.13.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/lib/api.js CHANGED
@@ -17,6 +17,7 @@ const RunStatus = require('./run-status');
17
17
  const fork = require('./fork');
18
18
  const serializeError = require('./serialize-error');
19
19
  const {getApplicableLineNumbers} = require('./line-numbers');
20
+ const sharedWorkers = require('./plugin-support/shared-workers');
20
21
 
21
22
  function resolveModules(modules) {
22
23
  return arrify(modules).map(name => {
@@ -206,6 +207,8 @@ class Api extends Emittery {
206
207
  concurrency = 1;
207
208
  }
208
209
 
210
+ const deregisteredSharedWorkers = [];
211
+
209
212
  // Try and run each file, limited by `concurrency`.
210
213
  await pMap(selectedFiles, async file => {
211
214
  // No new files should be run once a test has timed out or failed,
@@ -231,6 +234,7 @@ class Api extends Emittery {
231
234
 
232
235
  const worker = fork(file, options, apiOptions.nodeArguments);
233
236
  runStatus.observeWorker(worker, file, {selectingLines: lineNumbers.length > 0});
237
+ deregisteredSharedWorkers.push(sharedWorkers.observeWorkerProcess(worker, runStatus));
234
238
 
235
239
  pendingWorkers.add(worker);
236
240
  worker.promise.then(() => {
@@ -238,8 +242,11 @@ class Api extends Emittery {
238
242
  });
239
243
  restartTimer();
240
244
 
241
- return worker.promise;
245
+ await worker.promise;
242
246
  }, {concurrency, stopOnError: false});
247
+
248
+ // Allow shared workers to clean up before the run ends.
249
+ await Promise.all(deregisteredSharedWorkers);
243
250
  } catch (error) {
244
251
  if (error && error.name === 'AggregateError') {
245
252
  for (const err of error) {
package/lib/assert.js CHANGED
@@ -87,8 +87,16 @@ function getErrorWithLongStackTrace() {
87
87
  return err;
88
88
  }
89
89
 
90
- function validateExpectations(assertion, expectations, numberArgs) { // eslint-disable-line complexity
90
+ function validateExpectations(assertion, expectations, numberArgs, experiments) { // eslint-disable-line complexity
91
91
  if (numberArgs === 1 || expectations === null || expectations === undefined) {
92
+ if (experiments.disableNullExpectations && expectations === null) {
93
+ throw new AssertionError({
94
+ assertion,
95
+ message: `The second argument to \`t.${assertion}()\` must be an expectation object or \`undefined\``,
96
+ values: [formatWithLabel('Called with:', expectations)]
97
+ });
98
+ }
99
+
92
100
  expectations = {};
93
101
  } else if (
94
102
  typeof expectations === 'function' ||
@@ -465,7 +473,7 @@ class Assertions {
465
473
  }
466
474
 
467
475
  try {
468
- expectations = validateExpectations('throws', expectations, args.length);
476
+ expectations = validateExpectations('throws', expectations, args.length, experiments);
469
477
  } catch (error) {
470
478
  fail(error);
471
479
  return;
@@ -531,7 +539,7 @@ class Assertions {
531
539
  }
532
540
 
533
541
  try {
534
- expectations = validateExpectations('throwsAsync', expectations, args.length);
542
+ expectations = validateExpectations('throwsAsync', expectations, args.length, experiments);
535
543
  } catch (error) {
536
544
  fail(error);
537
545
  return Promise.resolve();
package/lib/cli.js CHANGED
@@ -430,14 +430,17 @@ exports.run = async () => { // eslint-disable-line complexity
430
430
  reporter.startRun(plan);
431
431
 
432
432
  if (process.env.AVA_EMIT_RUN_STATUS_OVER_IPC === 'I\'ll find a payphone baby / Take some time to talk to you') {
433
+ const {controlFlow} = require('./ipc-flow-control');
434
+ const bufferedSend = controlFlow(process);
435
+
433
436
  if (process.versions.node >= '12.16.0') {
434
437
  plan.status.on('stateChange', evt => {
435
- process.send(evt);
438
+ bufferedSend(evt);
436
439
  });
437
440
  } else {
438
441
  const v8 = require('v8');
439
442
  plan.status.on('stateChange', evt => {
440
- process.send([...v8.serialize(evt)]);
443
+ bufferedSend([...v8.serialize(evt)]);
441
444
  });
442
445
  }
443
446
  }
package/lib/fork.js CHANGED
@@ -3,6 +3,7 @@ const childProcess = require('child_process');
3
3
  const path = require('path');
4
4
  const fs = require('fs');
5
5
  const Emittery = require('emittery');
6
+ const {controlFlow} = require('./ipc-flow-control');
6
7
 
7
8
  if (fs.realpathSync(__filename) !== __filename) {
8
9
  console.warn('WARNING: `npm link ava` and the `--preserve-symlink` flag are incompatible. We have detected that AVA is linked via `npm link`, and that you are using either an early version of Node 6, or the `--preserve-symlink` flag. This breaks AVA. You should upgrade to Node 6.2.0+, avoid the `--preserve-symlink` flag, or avoid using `npm link ava`.');
@@ -11,10 +12,57 @@ if (fs.realpathSync(__filename) !== __filename) {
11
12
  // In case the test file imports a different AVA install,
12
13
  // the presence of this variable allows it to require this one instead
13
14
  const AVA_PATH = path.resolve(__dirname, '..');
15
+ const WORKER_PATH = require.resolve('./worker/subprocess');
16
+
17
+ class SharedWorkerChannel extends Emittery {
18
+ constructor({channelId, filename, initialData}, sendToFork) {
19
+ super();
20
+
21
+ this.id = channelId;
22
+ this.filename = filename;
23
+ this.initialData = initialData;
24
+ this.sendToFork = sendToFork;
25
+ }
26
+
27
+ signalReady() {
28
+ this.sendToFork({
29
+ type: 'shared-worker-ready',
30
+ channelId: this.id
31
+ });
32
+ }
33
+
34
+ signalError() {
35
+ this.sendToFork({
36
+ type: 'shared-worker-error',
37
+ channelId: this.id
38
+ });
39
+ }
40
+
41
+ emitMessage({messageId, replyTo, serializedData}) {
42
+ this.emit('message', {
43
+ messageId,
44
+ replyTo,
45
+ serializedData
46
+ });
47
+ }
48
+
49
+ forwardMessageToFork({messageId, replyTo, serializedData}) {
50
+ this.sendToFork({
51
+ type: 'shared-worker-message',
52
+ channelId: this.id,
53
+ messageId,
54
+ replyTo,
55
+ serializedData
56
+ });
57
+ }
58
+ }
14
59
 
15
- const workerPath = require.resolve('./worker/subprocess');
60
+ let forkCounter = 0;
16
61
 
17
62
  module.exports = (file, options, execArgv = process.execArgv) => {
63
+ const forkId = `fork/${++forkCounter}`;
64
+ const sharedWorkerChannels = new Map();
65
+
18
66
  let finished = false;
19
67
 
20
68
  const emitter = new Emittery();
@@ -25,12 +73,13 @@ module.exports = (file, options, execArgv = process.execArgv) => {
25
73
  };
26
74
 
27
75
  options = {
28
- file,
29
76
  baseDir: process.cwd(),
77
+ file,
78
+ forkId,
30
79
  ...options
31
80
  };
32
81
 
33
- const subprocess = childProcess.fork(workerPath, options.workerArgv, {
82
+ const subprocess = childProcess.fork(WORKER_PATH, options.workerArgv, {
34
83
  cwd: options.projectDir,
35
84
  silent: true,
36
85
  env: {NODE_ENV: 'test', ...process.env, ...options.environmentVariables, AVA_PATH},
@@ -45,12 +94,12 @@ module.exports = (file, options, execArgv = process.execArgv) => {
45
94
  emitStateChange({type: 'worker-stderr', chunk});
46
95
  });
47
96
 
97
+ const bufferedSend = controlFlow(subprocess);
98
+
48
99
  let forcedExit = false;
49
100
  const send = evt => {
50
- if (subprocess.connected && !finished && !forcedExit) {
51
- subprocess.send({ava: evt}, () => {
52
- // Disregard errors.
53
- });
101
+ if (!finished && !forcedExit) {
102
+ bufferedSend({ava: evt});
54
103
  }
55
104
  };
56
105
 
@@ -65,15 +114,25 @@ module.exports = (file, options, execArgv = process.execArgv) => {
65
114
  return;
66
115
  }
67
116
 
68
- if (message.ava.type === 'ready-for-options') {
69
- send({type: 'options', options});
70
- return;
71
- }
72
-
73
- if (message.ava.type === 'ping') {
74
- send({type: 'pong'});
75
- } else {
76
- emitStateChange(message.ava);
117
+ switch (message.ava.type) {
118
+ case 'ready-for-options':
119
+ send({type: 'options', options});
120
+ break;
121
+ case 'shared-worker-connect': {
122
+ const channel = new SharedWorkerChannel(message.ava, send);
123
+ sharedWorkerChannels.set(channel.id, channel);
124
+ emitter.emit('connectSharedWorker', channel);
125
+ break;
126
+ }
127
+
128
+ case 'shared-worker-message':
129
+ sharedWorkerChannels.get(message.ava.channelId).emitMessage(message.ava);
130
+ break;
131
+ case 'ping':
132
+ send({type: 'pong'});
133
+ break;
134
+ default:
135
+ emitStateChange(message.ava);
77
136
  }
78
137
  });
79
138
 
@@ -98,6 +157,10 @@ module.exports = (file, options, execArgv = process.execArgv) => {
98
157
  });
99
158
 
100
159
  return {
160
+ file,
161
+ forkId,
162
+ promise,
163
+
101
164
  exit() {
102
165
  forcedExit = true;
103
166
  subprocess.kill();
@@ -107,11 +170,12 @@ module.exports = (file, options, execArgv = process.execArgv) => {
107
170
  send({type: 'peer-failed'});
108
171
  },
109
172
 
110
- onStateChange(listener) {
111
- return emitter.on('stateChange', listener);
173
+ onConnectSharedWorker(listener) {
174
+ return emitter.on('connectSharedWorker', listener);
112
175
  },
113
176
 
114
- file,
115
- promise
177
+ onStateChange(listener) {
178
+ return emitter.on('stateChange', listener);
179
+ }
116
180
  };
117
181
  };
@@ -0,0 +1,39 @@
1
+ function controlFlow(channel) {
2
+ let errored = false;
3
+ let deliverImmediately = true;
4
+
5
+ const backlog = [];
6
+ const deliverNext = error => {
7
+ if (error !== null) {
8
+ errored = true;
9
+ }
10
+
11
+ if (errored || !channel.connected) {
12
+ backlog.length = 0; // Free memory.
13
+ return; // We can't send.
14
+ }
15
+
16
+ let ok = true;
17
+ while (ok && backlog.length > 0) { // Stop sending after backpressure.
18
+ ok = channel.send(backlog.shift(), deliverNext);
19
+ }
20
+
21
+ // Re-enable immediate delivery if there is no backpressure and the backlog
22
+ // has been cleared.
23
+ deliverImmediately = ok && backlog.length === 0;
24
+ };
25
+
26
+ return message => {
27
+ if (errored || !channel.connected) {
28
+ return;
29
+ }
30
+
31
+ if (deliverImmediately) {
32
+ deliverImmediately = channel.send(message, deliverNext);
33
+ } else {
34
+ backlog.push(message);
35
+ }
36
+ };
37
+ }
38
+
39
+ exports.controlFlow = controlFlow;
@@ -2,12 +2,18 @@
2
2
  const fs = require('fs');
3
3
  const path = require('path');
4
4
  const vm = require('vm');
5
- const isPlainObject = require('is-plain-object');
5
+ const {isPlainObject} = require('is-plain-object');
6
6
  const pkgConf = require('pkg-conf');
7
7
 
8
8
  const NO_SUCH_FILE = Symbol('no ava.config.js file');
9
9
  const MISSING_DEFAULT_EXPORT = Symbol('missing default export');
10
- const EXPERIMENTS = new Set(['configurableModuleFormat', 'disableSnapshotsInHooks', 'reverseTeardowns']);
10
+ const EXPERIMENTS = new Set([
11
+ 'configurableModuleFormat',
12
+ 'disableNullExpectations',
13
+ 'disableSnapshotsInHooks',
14
+ 'reverseTeardowns',
15
+ 'sharedWorkers'
16
+ ]);
11
17
 
12
18
  // *Very* rudimentary support for loading ava.config.js files containing an `export default` statement.
13
19
  const evaluateJsConfig = configFile => {
@@ -0,0 +1,252 @@
1
+ const {EventEmitter, on} = require('events');
2
+ const v8 = require('v8');
3
+ const {workerData, parentPort} = require('worker_threads'); // eslint-disable-line node/no-unsupported-features/node-builtins
4
+ const pkg = require('../../package.json');
5
+
6
+ // Used to forward messages received over the `parentPort`. Every subscription
7
+ // adds a listener, so do not enforce any maximums.
8
+ const events = new EventEmitter().setMaxListeners(0);
9
+
10
+ // Map of active test workers, used in receiveMessages() to get a reference to
11
+ // the TestWorker instance, and relevant release functions.
12
+ const activeTestWorkers = new Map();
13
+
14
+ class TestWorker {
15
+ constructor(id, file) {
16
+ this.id = id;
17
+ this.file = file;
18
+ }
19
+
20
+ teardown(fn) {
21
+ let done = false;
22
+ const teardownFn = async () => {
23
+ if (done) {
24
+ return;
25
+ }
26
+
27
+ done = true;
28
+ if (activeTestWorkers.has(this.id)) {
29
+ activeTestWorkers.get(this.id).teardownFns.delete(teardownFn);
30
+ }
31
+
32
+ await fn();
33
+ };
34
+
35
+ activeTestWorkers.get(this.id).teardownFns.add(teardownFn);
36
+
37
+ return teardownFn;
38
+ }
39
+
40
+ publish(data) {
41
+ return publishMessage(this, data);
42
+ }
43
+
44
+ async * subscribe() {
45
+ yield * receiveMessages(this);
46
+ }
47
+ }
48
+
49
+ class ReceivedMessage {
50
+ constructor(testWorker, id, serializedData) {
51
+ this.testWorker = testWorker;
52
+ this.id = id;
53
+ this.data = v8.deserialize(new Uint8Array(serializedData));
54
+ }
55
+
56
+ reply(data) {
57
+ return publishMessage(this.testWorker, data, this.id);
58
+ }
59
+ }
60
+
61
+ // Ensure that, no matter how often it's received, we have a stable message
62
+ // object.
63
+ const messageCache = new WeakMap();
64
+
65
+ async function * receiveMessages(fromTestWorker, replyTo) {
66
+ for await (const [message] of on(events, 'message')) {
67
+ if (fromTestWorker !== undefined) {
68
+ if (message.type === 'deregister-test-worker' && message.id === fromTestWorker.id) {
69
+ return;
70
+ }
71
+
72
+ if (message.type === 'message' && message.testWorkerId !== fromTestWorker.id) {
73
+ continue;
74
+ }
75
+ }
76
+
77
+ if (message.type !== 'message') {
78
+ continue;
79
+ }
80
+
81
+ if (replyTo === undefined && message.replyTo !== undefined) {
82
+ continue;
83
+ }
84
+
85
+ if (replyTo !== undefined && message.replyTo !== replyTo) {
86
+ continue;
87
+ }
88
+
89
+ const active = activeTestWorkers.get(message.testWorkerId);
90
+ // It is possible for a message to have been buffering for so long — perhaps
91
+ // due to the caller waiting before iterating to the next message — that the
92
+ // test worker has been deregistered. Ignore such messages.
93
+ //
94
+ // (This is really hard to write a test for, however!)
95
+ if (active === undefined) {
96
+ continue;
97
+ }
98
+
99
+ let received = messageCache.get(message);
100
+ if (received === undefined) {
101
+ received = new ReceivedMessage(active.instance, message.messageId, message.serializedData);
102
+ messageCache.set(message, received);
103
+ }
104
+
105
+ yield received;
106
+ }
107
+ }
108
+
109
+ let messageCounter = 0;
110
+ const messageIdPrefix = `${workerData.id}/message`;
111
+ const nextMessageId = () => `${messageIdPrefix}/${++messageCounter}`;
112
+
113
+ function publishMessage(testWorker, data, replyTo) {
114
+ const id = nextMessageId();
115
+ parentPort.postMessage({
116
+ type: 'message',
117
+ messageId: id,
118
+ testWorkerId: testWorker.id,
119
+ serializedData: [...v8.serialize(data)],
120
+ replyTo
121
+ });
122
+
123
+ return {
124
+ id,
125
+ async * replies() {
126
+ yield * receiveMessages(testWorker, id);
127
+ }
128
+ };
129
+ }
130
+
131
+ function broadcastMessage(data) {
132
+ const id = nextMessageId();
133
+ parentPort.postMessage({
134
+ type: 'broadcast',
135
+ messageId: id,
136
+ serializedData: [...v8.serialize(data)]
137
+ });
138
+
139
+ return {
140
+ id,
141
+ async * replies() {
142
+ yield * receiveMessages(undefined, id);
143
+ }
144
+ };
145
+ }
146
+
147
+ async function loadFactory() {
148
+ try {
149
+ const mod = require(workerData.filename);
150
+ if (typeof mod === 'function') {
151
+ return mod;
152
+ }
153
+
154
+ return mod.default;
155
+ } catch (error) {
156
+ if (error && (error.code === 'ERR_REQUIRE_ESM' || (error.code === 'MODULE_NOT_FOUND' && workerData.filename.startsWith('file://')))) {
157
+ const {default: factory} = await import(workerData.filename); // eslint-disable-line node/no-unsupported-features/es-syntax
158
+ return factory;
159
+ }
160
+
161
+ throw error;
162
+ }
163
+ }
164
+
165
+ let signalAvailable = () => {
166
+ parentPort.postMessage({type: 'available'});
167
+ signalAvailable = () => {};
168
+ };
169
+
170
+ let fatal;
171
+ loadFactory(workerData.filename).then(factory => {
172
+ if (typeof factory !== 'function') {
173
+ throw new TypeError(`Missing default factory function export for shared worker plugin at ${workerData.filename}`);
174
+ }
175
+
176
+ factory({
177
+ negotiateProtocol(supported) {
178
+ if (!supported.includes('experimental')) {
179
+ fatal = new Error(`This version of AVA (${pkg.version}) is not compatible with shared worker plugin at ${workerData.filename}`);
180
+ throw fatal;
181
+ }
182
+
183
+ const produceTestWorker = instance => events.emit('testWorker', instance);
184
+
185
+ parentPort.on('message', async message => {
186
+ if (message.type === 'register-test-worker') {
187
+ const {id, file} = message;
188
+ const instance = new TestWorker(id, file);
189
+
190
+ activeTestWorkers.set(id, {instance, teardownFns: new Set()});
191
+
192
+ produceTestWorker(instance);
193
+ }
194
+
195
+ if (message.type === 'deregister-test-worker') {
196
+ const {id} = message;
197
+ const {teardownFns} = activeTestWorkers.get(id);
198
+ activeTestWorkers.delete(id);
199
+
200
+ // Run possibly asynchronous release functions serially, in reverse
201
+ // order. Any error will crash the worker.
202
+ for await (const fn of [...teardownFns].reverse()) {
203
+ await fn();
204
+ }
205
+
206
+ parentPort.postMessage({
207
+ type: 'deregistered-test-worker',
208
+ id
209
+ });
210
+ }
211
+
212
+ // Wait for a turn of the event loop, to allow new subscriptions to be
213
+ // set up in response to the previous message.
214
+ setImmediate(() => events.emit('message', message));
215
+ });
216
+
217
+ return {
218
+ initialData: workerData.initialData,
219
+ protocol: 'experimental',
220
+
221
+ ready() {
222
+ signalAvailable();
223
+ return this;
224
+ },
225
+
226
+ broadcast(data) {
227
+ return broadcastMessage(data);
228
+ },
229
+
230
+ async * subscribe() {
231
+ yield * receiveMessages();
232
+ },
233
+
234
+ async * testWorkers() {
235
+ for await (const [worker] of on(events, 'testWorker')) {
236
+ yield worker;
237
+ }
238
+ }
239
+ };
240
+ }
241
+ });
242
+ }).catch(error => {
243
+ if (fatal === undefined) {
244
+ fatal = error;
245
+ }
246
+ }).finally(() => {
247
+ if (fatal !== undefined) {
248
+ process.nextTick(() => {
249
+ throw fatal;
250
+ });
251
+ }
252
+ });
@@ -0,0 +1,138 @@
1
+ const events = require('events');
2
+ const serializeError = require('../serialize-error');
3
+
4
+ let Worker;
5
+ try {
6
+ ({Worker} = require('worker_threads')); // eslint-disable-line node/no-unsupported-features/node-builtins
7
+ } catch {}
8
+
9
+ const LOADER = require.resolve('./shared-worker-loader');
10
+
11
+ let sharedWorkerCounter = 0;
12
+ const launchedWorkers = new Map();
13
+
14
+ const waitForAvailable = async worker => {
15
+ for await (const [message] of events.on(worker, 'message')) {
16
+ if (message.type === 'available') {
17
+ return;
18
+ }
19
+ }
20
+ };
21
+
22
+ function launchWorker({filename, initialData}) {
23
+ if (launchedWorkers.has(filename)) {
24
+ return launchedWorkers.get(filename);
25
+ }
26
+
27
+ const id = `shared-worker/${++sharedWorkerCounter}`;
28
+ const worker = new Worker(LOADER, {
29
+ // Ensure the worker crashes for unhandled rejections, rather than allowing undefined behavior.
30
+ execArgv: ['--unhandled-rejections=strict'],
31
+ workerData: {
32
+ filename,
33
+ id,
34
+ initialData
35
+ }
36
+ });
37
+ const launched = {
38
+ statePromises: {
39
+ available: waitForAvailable(worker),
40
+ error: events.once(worker, 'error').then(([error]) => error) // eslint-disable-line promise/prefer-await-to-then
41
+ },
42
+ exited: false,
43
+ worker
44
+ };
45
+
46
+ launchedWorkers.set(filename, launched);
47
+ worker.once('exit', () => {
48
+ launched.exited = true;
49
+ });
50
+
51
+ return launched;
52
+ }
53
+
54
+ async function observeWorkerProcess(fork, runStatus) {
55
+ let registrationCount = 0;
56
+ let signalDeregistered;
57
+ const deregistered = new Promise(resolve => {
58
+ signalDeregistered = resolve;
59
+ });
60
+
61
+ fork.promise.finally(() => {
62
+ if (registrationCount === 0) {
63
+ signalDeregistered();
64
+ }
65
+ });
66
+
67
+ fork.onConnectSharedWorker(async channel => {
68
+ const launched = launchWorker(channel);
69
+
70
+ const handleChannelMessage = ({messageId, replyTo, serializedData}) => {
71
+ launched.worker.postMessage({
72
+ type: 'message',
73
+ testWorkerId: fork.forkId,
74
+ messageId,
75
+ replyTo,
76
+ serializedData
77
+ });
78
+ };
79
+
80
+ const handleWorkerMessage = async message => {
81
+ if (message.type === 'broadcast' || (message.type === 'message' && message.testWorkerId === fork.forkId)) {
82
+ const {messageId, replyTo, serializedData} = message;
83
+ channel.forwardMessageToFork({messageId, replyTo, serializedData});
84
+ }
85
+
86
+ if (message.type === 'deregistered-test-worker' && message.id === fork.forkId) {
87
+ launched.worker.off('message', handleWorkerMessage);
88
+
89
+ registrationCount--;
90
+ if (registrationCount === 0) {
91
+ signalDeregistered();
92
+ }
93
+ }
94
+ };
95
+
96
+ launched.statePromises.error.then(error => { // eslint-disable-line promise/prefer-await-to-then
97
+ signalDeregistered();
98
+ launched.worker.off('message', handleWorkerMessage);
99
+ runStatus.emitStateChange({type: 'shared-worker-error', err: serializeError('Shared worker error', true, error)});
100
+ channel.signalError();
101
+ });
102
+
103
+ try {
104
+ await launched.statePromises.available;
105
+
106
+ registrationCount++;
107
+ launched.worker.postMessage({
108
+ type: 'register-test-worker',
109
+ id: fork.forkId,
110
+ file: fork.file
111
+ });
112
+
113
+ fork.promise.finally(() => {
114
+ launched.worker.postMessage({
115
+ type: 'deregister-test-worker',
116
+ id: fork.forkId
117
+ });
118
+
119
+ channel.off('message', handleChannelMessage);
120
+ });
121
+
122
+ launched.worker.on('message', handleWorkerMessage);
123
+ channel.on('message', handleChannelMessage);
124
+ channel.signalReady();
125
+ } catch {
126
+ return;
127
+ } finally {
128
+ // Attaching listeners has the side-effect of referencing the worker.
129
+ // Explicitly unreference it now so it does not prevent the main process
130
+ // from exiting.
131
+ launched.worker.unref();
132
+ }
133
+ });
134
+
135
+ return deregistered;
136
+ }
137
+
138
+ exports.observeWorkerProcess = observeWorkerProcess;