ava 3.11.0 → 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/index.d.ts +1 -1
- package/lib/api.js +8 -1
- package/lib/assert.js +11 -3
- package/lib/cli.js +13 -8
- package/lib/extensions.js +4 -1
- package/lib/fork.js +84 -20
- package/lib/ipc-flow-control.js +39 -0
- package/lib/load-config.js +8 -2
- package/lib/module-types.js +75 -0
- package/lib/plugin-support/shared-worker-loader.js +252 -0
- package/lib/plugin-support/shared-workers.js +138 -0
- package/lib/provider-manager.js +1 -1
- package/lib/reporters/default.js +83 -12
- package/lib/run-status.js +5 -0
- package/lib/runner.js +3 -0
- package/lib/test.js +2 -1
- package/lib/worker/ipc.js +174 -29
- package/lib/worker/plugin.js +121 -0
- package/lib/worker/subprocess.js +24 -1
- package/package.json +24 -22
- package/plugin.d.ts +81 -0
- package/plugin.js +9 -0
- package/readme.md +4 -3
- package/lib/reporters/while-corked.js +0 -13
|
@@ -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;
|
package/lib/provider-manager.js
CHANGED
|
@@ -21,7 +21,7 @@ function load(providerModule, projectDir) {
|
|
|
21
21
|
let level;
|
|
22
22
|
const provider = makeProvider({
|
|
23
23
|
negotiateProtocol(identifiers, {version}) {
|
|
24
|
-
const
|
|
24
|
+
const identifier = identifiers.find(identifier => Reflect.has(levelsByProtocol, identifier));
|
|
25
25
|
|
|
26
26
|
if (identifier === undefined) {
|
|
27
27
|
fatal = new Error(`This version of AVA (${ava.version}) is not compatible with ${providerModule}@${version}`);
|
package/lib/reporters/default.js
CHANGED
|
@@ -18,7 +18,6 @@ const colors = require('./colors');
|
|
|
18
18
|
const formatSerializedError = require('./format-serialized-error');
|
|
19
19
|
const improperUsageMessages = require('./improper-usage-messages');
|
|
20
20
|
const prefixTitle = require('./prefix-title');
|
|
21
|
-
const whileCorked = require('./while-corked');
|
|
22
21
|
|
|
23
22
|
const nodeInternals = require('stack-utils').nodeInternals();
|
|
24
23
|
|
|
@@ -97,6 +96,48 @@ class LineWriterWithSpinner extends LineWriter {
|
|
|
97
96
|
}
|
|
98
97
|
}
|
|
99
98
|
|
|
99
|
+
function manageCorking(stream) {
|
|
100
|
+
let corked = false;
|
|
101
|
+
const cork = () => {
|
|
102
|
+
corked = true;
|
|
103
|
+
stream.cork();
|
|
104
|
+
};
|
|
105
|
+
|
|
106
|
+
const uncork = () => {
|
|
107
|
+
corked = false;
|
|
108
|
+
stream.uncork();
|
|
109
|
+
};
|
|
110
|
+
|
|
111
|
+
return {
|
|
112
|
+
decorateFlushingWriter(fn) {
|
|
113
|
+
return function (...args) {
|
|
114
|
+
if (corked) {
|
|
115
|
+
stream.uncork();
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
try {
|
|
119
|
+
return fn.apply(this, args);
|
|
120
|
+
} finally {
|
|
121
|
+
if (corked) {
|
|
122
|
+
stream.cork();
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
};
|
|
126
|
+
},
|
|
127
|
+
|
|
128
|
+
decorateWriter(fn) {
|
|
129
|
+
return function (...args) {
|
|
130
|
+
cork();
|
|
131
|
+
try {
|
|
132
|
+
return fn.apply(this, args);
|
|
133
|
+
} finally {
|
|
134
|
+
uncork();
|
|
135
|
+
}
|
|
136
|
+
};
|
|
137
|
+
}
|
|
138
|
+
};
|
|
139
|
+
}
|
|
140
|
+
|
|
100
141
|
class Reporter {
|
|
101
142
|
constructor({
|
|
102
143
|
verbose,
|
|
@@ -112,13 +153,16 @@ class Reporter {
|
|
|
112
153
|
this.stdStream = stdStream;
|
|
113
154
|
this.watching = watching;
|
|
114
155
|
this.relativeFile = file => path.relative(projectDir, file);
|
|
115
|
-
|
|
156
|
+
|
|
157
|
+
const {decorateWriter, decorateFlushingWriter} = manageCorking(this.reportStream);
|
|
158
|
+
this.consumeStateChange = decorateWriter(this.consumeStateChange);
|
|
159
|
+
this.endRun = decorateWriter(this.endRun);
|
|
116
160
|
|
|
117
161
|
if (this.verbose) {
|
|
118
162
|
this.durationThreshold = durationThreshold || 100;
|
|
119
163
|
this.spinner = null;
|
|
164
|
+
this.clearSpinner = () => {};
|
|
120
165
|
this.lineWriter = new LineWriter(this.reportStream);
|
|
121
|
-
this.endRun = whileCorked(this.reportStream, this.endRun);
|
|
122
166
|
} else {
|
|
123
167
|
this.spinner = ora({
|
|
124
168
|
isEnabled: true,
|
|
@@ -128,8 +172,8 @@ class Reporter {
|
|
|
128
172
|
spinner: spinner || (process.platform === 'win32' ? 'line' : 'dots'),
|
|
129
173
|
stream: reportStream
|
|
130
174
|
});
|
|
175
|
+
this.clearSpinner = decorateFlushingWriter(this.spinner.clear.bind(this.spinner));
|
|
131
176
|
this.lineWriter = new LineWriterWithSpinner(this.reportStream, this.spinner);
|
|
132
|
-
this.endRun = whileCorked(this.reportStream, whileCorked(this.lineWriter, this.endRun));
|
|
133
177
|
}
|
|
134
178
|
|
|
135
179
|
this.reset();
|
|
@@ -151,6 +195,7 @@ class Reporter {
|
|
|
151
195
|
this.internalErrors = [];
|
|
152
196
|
this.knownFailures = [];
|
|
153
197
|
this.lineNumberErrors = [];
|
|
198
|
+
this.sharedWorkerErrors = [];
|
|
154
199
|
this.uncaughtExceptions = [];
|
|
155
200
|
this.unhandledRejections = [];
|
|
156
201
|
this.unsavedSnapshots = [];
|
|
@@ -296,6 +341,19 @@ class Reporter {
|
|
|
296
341
|
break;
|
|
297
342
|
}
|
|
298
343
|
|
|
344
|
+
case 'shared-worker-error': {
|
|
345
|
+
this.sharedWorkerErrors.push(event);
|
|
346
|
+
|
|
347
|
+
if (this.verbose) {
|
|
348
|
+
this.lineWriter.ensureEmptyLine();
|
|
349
|
+
this.lineWriter.writeLine(colors.error(`${figures.cross} Error in shared worker`));
|
|
350
|
+
this.lineWriter.writeLine();
|
|
351
|
+
this.writeErr(event);
|
|
352
|
+
}
|
|
353
|
+
|
|
354
|
+
break;
|
|
355
|
+
}
|
|
356
|
+
|
|
299
357
|
case 'snapshot-error':
|
|
300
358
|
this.unsavedSnapshots.push(event);
|
|
301
359
|
break;
|
|
@@ -362,9 +420,7 @@ class Reporter {
|
|
|
362
420
|
|
|
363
421
|
case 'worker-stderr': {
|
|
364
422
|
// Forcibly clear the spinner, writing the chunk corrupts the TTY.
|
|
365
|
-
|
|
366
|
-
this.spinner.clear();
|
|
367
|
-
}
|
|
423
|
+
this.clearSpinner();
|
|
368
424
|
|
|
369
425
|
this.stdStream.write(event.chunk);
|
|
370
426
|
// If the chunk does not end with a linebreak, *forcibly* write one to
|
|
@@ -386,9 +442,7 @@ class Reporter {
|
|
|
386
442
|
|
|
387
443
|
case 'worker-stdout': {
|
|
388
444
|
// Forcibly clear the spinner, writing the chunk corrupts the TTY.
|
|
389
|
-
|
|
390
|
-
this.spinner.clear();
|
|
391
|
-
}
|
|
445
|
+
this.clearSpinner();
|
|
392
446
|
|
|
393
447
|
this.stdStream.write(event.chunk);
|
|
394
448
|
// If the chunk does not end with a linebreak, *forcibly* write one to
|
|
@@ -670,7 +724,7 @@ class Reporter {
|
|
|
670
724
|
}
|
|
671
725
|
|
|
672
726
|
if (this.failures.length > 0) {
|
|
673
|
-
const writeTrailingLines = this.internalErrors.length > 0 || this.uncaughtExceptions.length > 0 || this.unhandledRejections.length > 0;
|
|
727
|
+
const writeTrailingLines = this.internalErrors.length > 0 || this.sharedWorkerErrors.length > 0 || this.uncaughtExceptions.length > 0 || this.unhandledRejections.length > 0;
|
|
674
728
|
|
|
675
729
|
const lastFailure = this.failures[this.failures.length - 1];
|
|
676
730
|
for (const event of this.failures) {
|
|
@@ -694,7 +748,7 @@ class Reporter {
|
|
|
694
748
|
|
|
695
749
|
if (!this.verbose) {
|
|
696
750
|
if (this.internalErrors.length > 0) {
|
|
697
|
-
const writeTrailingLines = this.uncaughtExceptions.length > 0 || this.unhandledRejections.length > 0;
|
|
751
|
+
const writeTrailingLines = this.sharedWorkerErrors.length > 0 || this.uncaughtExceptions.length > 0 || this.unhandledRejections.length > 0;
|
|
698
752
|
|
|
699
753
|
const last = this.internalErrors[this.internalErrors.length - 1];
|
|
700
754
|
for (const event of this.internalErrors) {
|
|
@@ -716,6 +770,23 @@ class Reporter {
|
|
|
716
770
|
}
|
|
717
771
|
}
|
|
718
772
|
|
|
773
|
+
if (this.sharedWorkerErrors.length > 0) {
|
|
774
|
+
const writeTrailingLines = this.uncaughtExceptions.length > 0 || this.unhandledRejections.length > 0;
|
|
775
|
+
|
|
776
|
+
const last = this.sharedWorkerErrors[this.sharedWorkerErrors.length - 1];
|
|
777
|
+
for (const evt of this.sharedWorkerErrors) {
|
|
778
|
+
this.lineWriter.writeLine(colors.error(`${figures.cross} Error in shared worker`));
|
|
779
|
+
this.lineWriter.writeLine();
|
|
780
|
+
this.writeErr(evt.err);
|
|
781
|
+
if (evt !== last || writeTrailingLines) {
|
|
782
|
+
this.lineWriter.writeLine();
|
|
783
|
+
this.lineWriter.writeLine();
|
|
784
|
+
}
|
|
785
|
+
|
|
786
|
+
wroteSomething = true;
|
|
787
|
+
}
|
|
788
|
+
}
|
|
789
|
+
|
|
719
790
|
if (this.uncaughtExceptions.length > 0) {
|
|
720
791
|
const writeTrailingLines = this.unhandledRejections.length > 0;
|
|
721
792
|
|
package/lib/run-status.js
CHANGED
|
@@ -27,6 +27,7 @@ class RunStatus extends Emittery {
|
|
|
27
27
|
passedKnownFailingTests: 0,
|
|
28
28
|
passedTests: 0,
|
|
29
29
|
selectedTests: 0,
|
|
30
|
+
sharedWorkerErrors: 0,
|
|
30
31
|
skippedTests: 0,
|
|
31
32
|
timeouts: 0,
|
|
32
33
|
todoTests: 0,
|
|
@@ -93,6 +94,9 @@ class RunStatus extends Emittery {
|
|
|
93
94
|
this.addPendingTest(event);
|
|
94
95
|
}
|
|
95
96
|
|
|
97
|
+
break;
|
|
98
|
+
case 'shared-worker-error':
|
|
99
|
+
stats.sharedWorkerErrors++;
|
|
96
100
|
break;
|
|
97
101
|
case 'test-failed':
|
|
98
102
|
stats.failedTests++;
|
|
@@ -164,6 +168,7 @@ class RunStatus extends Emittery {
|
|
|
164
168
|
this.stats.failedHooks > 0 ||
|
|
165
169
|
this.stats.failedTests > 0 ||
|
|
166
170
|
this.stats.failedWorkers > 0 ||
|
|
171
|
+
this.stats.sharedWorkerErrors > 0 ||
|
|
167
172
|
this.stats.timeouts > 0 ||
|
|
168
173
|
this.stats.uncaughtExceptions > 0 ||
|
|
169
174
|
this.stats.unhandledRejections > 0
|
package/lib/runner.js
CHANGED
|
@@ -42,6 +42,7 @@ class Runner extends Emittery {
|
|
|
42
42
|
serial: [],
|
|
43
43
|
todo: []
|
|
44
44
|
};
|
|
45
|
+
this.waitForReady = [];
|
|
45
46
|
|
|
46
47
|
const uniqueTestTitles = new Set();
|
|
47
48
|
this.registerUniqueTitle = title => {
|
|
@@ -444,6 +445,8 @@ class Runner extends Emittery {
|
|
|
444
445
|
});
|
|
445
446
|
}
|
|
446
447
|
|
|
448
|
+
await Promise.all(this.waitForReady);
|
|
449
|
+
|
|
447
450
|
if (concurrentTests.length === 0 && serialTests.length === 0) {
|
|
448
451
|
this.emit('finish');
|
|
449
452
|
// Don't run any hooks if there are no tests to run.
|
package/lib/test.js
CHANGED
|
@@ -187,7 +187,8 @@ class ExecutionContext extends assert.Assertions {
|
|
|
187
187
|
}
|
|
188
188
|
|
|
189
189
|
get passed() {
|
|
190
|
-
|
|
190
|
+
const test = testMap.get(this);
|
|
191
|
+
return test.isHook ? test.testPassed : !test.assertError;
|
|
191
192
|
}
|
|
192
193
|
|
|
193
194
|
_throwsArgStart(assertion, file, line) {
|
package/lib/worker/ipc.js
CHANGED
|
@@ -1,50 +1,42 @@
|
|
|
1
1
|
'use strict';
|
|
2
|
-
const
|
|
2
|
+
const events = require('events');
|
|
3
|
+
const pEvent = require('p-event');
|
|
4
|
+
const {controlFlow} = require('../ipc-flow-control');
|
|
5
|
+
const {get: getOptions} = require('./options');
|
|
3
6
|
|
|
4
|
-
const
|
|
5
|
-
process.on('message', message => {
|
|
6
|
-
if (!message.ava) {
|
|
7
|
-
return;
|
|
8
|
-
}
|
|
9
|
-
|
|
10
|
-
switch (message.ava.type) {
|
|
11
|
-
case 'options':
|
|
12
|
-
emitter.emit('options', message.ava.options);
|
|
13
|
-
break;
|
|
14
|
-
case 'peer-failed':
|
|
15
|
-
emitter.emit('peerFailed');
|
|
16
|
-
break;
|
|
17
|
-
case 'pong':
|
|
18
|
-
emitter.emit('pong');
|
|
19
|
-
break;
|
|
20
|
-
default:
|
|
21
|
-
break;
|
|
22
|
-
}
|
|
23
|
-
});
|
|
7
|
+
const selectAvaMessage = type => message => message.ava && message.ava.type === type;
|
|
24
8
|
|
|
25
|
-
exports.options =
|
|
26
|
-
exports.peerFailed =
|
|
9
|
+
exports.options = pEvent(process, 'message', selectAvaMessage('options')).then(message => message.ava.options);
|
|
10
|
+
exports.peerFailed = pEvent(process, 'message', selectAvaMessage('peer-failed'));
|
|
27
11
|
|
|
12
|
+
const bufferedSend = controlFlow(process);
|
|
28
13
|
function send(evt) {
|
|
29
|
-
|
|
30
|
-
process.send({ava: evt});
|
|
31
|
-
}
|
|
14
|
+
bufferedSend({ava: evt});
|
|
32
15
|
}
|
|
33
16
|
|
|
34
17
|
exports.send = send;
|
|
35
18
|
|
|
19
|
+
let refs = 1;
|
|
20
|
+
function ref() {
|
|
21
|
+
if (++refs === 1) {
|
|
22
|
+
process.channel.ref();
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
|
|
36
26
|
function unref() {
|
|
37
|
-
|
|
27
|
+
if (refs > 0 && --refs === 0) {
|
|
28
|
+
process.channel.unref();
|
|
29
|
+
}
|
|
38
30
|
}
|
|
39
31
|
|
|
40
32
|
exports.unref = unref;
|
|
41
33
|
|
|
42
34
|
let pendingPings = Promise.resolve();
|
|
43
35
|
async function flush() {
|
|
44
|
-
|
|
36
|
+
ref();
|
|
45
37
|
const promise = pendingPings.then(async () => { // eslint-disable-line promise/prefer-await-to-then
|
|
46
38
|
send({type: 'ping'});
|
|
47
|
-
await
|
|
39
|
+
await pEvent(process, 'message', selectAvaMessage('pong'));
|
|
48
40
|
if (promise === pendingPings) {
|
|
49
41
|
unref();
|
|
50
42
|
}
|
|
@@ -54,3 +46,156 @@ async function flush() {
|
|
|
54
46
|
}
|
|
55
47
|
|
|
56
48
|
exports.flush = flush;
|
|
49
|
+
|
|
50
|
+
let channelCounter = 0;
|
|
51
|
+
let messageCounter = 0;
|
|
52
|
+
|
|
53
|
+
const channelEmitters = new Map();
|
|
54
|
+
function createChannelEmitter(channelId) {
|
|
55
|
+
if (channelEmitters.size === 0) {
|
|
56
|
+
process.on('message', message => {
|
|
57
|
+
if (!message.ava) {
|
|
58
|
+
return;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
const {channelId, type, ...payload} = message.ava;
|
|
62
|
+
if (
|
|
63
|
+
type === 'shared-worker-error' ||
|
|
64
|
+
type === 'shared-worker-message' ||
|
|
65
|
+
type === 'shared-worker-ready'
|
|
66
|
+
) {
|
|
67
|
+
const emitter = channelEmitters.get(channelId);
|
|
68
|
+
if (emitter !== undefined) {
|
|
69
|
+
emitter.emit(type, payload);
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
});
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
const emitter = new events.EventEmitter();
|
|
76
|
+
channelEmitters.set(channelId, emitter);
|
|
77
|
+
return [emitter, () => channelEmitters.delete(channelId)];
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
function registerSharedWorker(filename, initialData) {
|
|
81
|
+
const channelId = `${getOptions().forkId}/channel/${++channelCounter}`;
|
|
82
|
+
const [channelEmitter, unsubscribe] = createChannelEmitter(channelId);
|
|
83
|
+
|
|
84
|
+
let forcedUnref = false;
|
|
85
|
+
let refs = 0;
|
|
86
|
+
const forceUnref = () => {
|
|
87
|
+
if (forcedUnref) {
|
|
88
|
+
return;
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
forcedUnref = true;
|
|
92
|
+
if (refs > 0) {
|
|
93
|
+
unref();
|
|
94
|
+
}
|
|
95
|
+
};
|
|
96
|
+
|
|
97
|
+
const refChannel = () => {
|
|
98
|
+
if (!forcedUnref && ++refs === 1) {
|
|
99
|
+
ref();
|
|
100
|
+
}
|
|
101
|
+
};
|
|
102
|
+
|
|
103
|
+
const unrefChannel = () => {
|
|
104
|
+
if (!forcedUnref && refs > 0 && --refs === 0) {
|
|
105
|
+
unref();
|
|
106
|
+
}
|
|
107
|
+
};
|
|
108
|
+
|
|
109
|
+
send({
|
|
110
|
+
type: 'shared-worker-connect',
|
|
111
|
+
channelId,
|
|
112
|
+
filename,
|
|
113
|
+
initialData
|
|
114
|
+
});
|
|
115
|
+
|
|
116
|
+
let currentlyAvailable = false;
|
|
117
|
+
let error = null;
|
|
118
|
+
|
|
119
|
+
refChannel();
|
|
120
|
+
const ready = pEvent(channelEmitter, 'shared-worker-ready').then(() => { // eslint-disable-line promise/prefer-await-to-then
|
|
121
|
+
currentlyAvailable = error === null;
|
|
122
|
+
}).finally(unrefChannel);
|
|
123
|
+
|
|
124
|
+
const messageEmitters = new Set();
|
|
125
|
+
const handleMessage = message => {
|
|
126
|
+
// Wait for a turn of the event loop, to allow new subscriptions to be set
|
|
127
|
+
// up in response to the previous message.
|
|
128
|
+
setImmediate(() => {
|
|
129
|
+
for (const emitter of messageEmitters) {
|
|
130
|
+
emitter.emit('message', message);
|
|
131
|
+
}
|
|
132
|
+
});
|
|
133
|
+
};
|
|
134
|
+
|
|
135
|
+
channelEmitter.on('shared-worker-message', handleMessage);
|
|
136
|
+
|
|
137
|
+
pEvent(channelEmitter, 'shared-worker-error').then(() => { // eslint-disable-line promise/prefer-await-to-then
|
|
138
|
+
unsubscribe();
|
|
139
|
+
forceUnref();
|
|
140
|
+
|
|
141
|
+
error = new Error('The shared worker is no longer available');
|
|
142
|
+
currentlyAvailable = false;
|
|
143
|
+
for (const emitter of messageEmitters) {
|
|
144
|
+
emitter.emit('error', error);
|
|
145
|
+
}
|
|
146
|
+
});
|
|
147
|
+
|
|
148
|
+
return {
|
|
149
|
+
forceUnref,
|
|
150
|
+
ready,
|
|
151
|
+
channel: {
|
|
152
|
+
available: ready,
|
|
153
|
+
|
|
154
|
+
get currentlyAvailable() {
|
|
155
|
+
return currentlyAvailable;
|
|
156
|
+
},
|
|
157
|
+
|
|
158
|
+
async * receive() {
|
|
159
|
+
if (error !== null) {
|
|
160
|
+
throw error;
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
const emitter = new events.EventEmitter();
|
|
164
|
+
messageEmitters.add(emitter);
|
|
165
|
+
try {
|
|
166
|
+
refChannel();
|
|
167
|
+
for await (const [message] of events.on(emitter, 'message')) {
|
|
168
|
+
yield message;
|
|
169
|
+
}
|
|
170
|
+
} finally {
|
|
171
|
+
unrefChannel();
|
|
172
|
+
messageEmitters.delete(emitter);
|
|
173
|
+
}
|
|
174
|
+
},
|
|
175
|
+
|
|
176
|
+
post(serializedData, replyTo) {
|
|
177
|
+
if (error !== null) {
|
|
178
|
+
throw error;
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
if (!currentlyAvailable) {
|
|
182
|
+
throw new Error('Shared worker is not yet available');
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
const messageId = `${channelId}/message/${++messageCounter}`;
|
|
186
|
+
send({
|
|
187
|
+
type: 'shared-worker-message',
|
|
188
|
+
channelId,
|
|
189
|
+
messageId,
|
|
190
|
+
replyTo,
|
|
191
|
+
serializedData
|
|
192
|
+
});
|
|
193
|
+
|
|
194
|
+
return messageId;
|
|
195
|
+
}
|
|
196
|
+
}
|
|
197
|
+
};
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
exports.registerSharedWorker = registerSharedWorker;
|
|
201
|
+
|