Skip to content

Commit b99b703

Browse files
matz3novemberborn
andauthored
Keep test worker alive until explicitly freed
This change ensures that all messages from the worker are received by the main process before the worker exits. It solves issues on Windows that are occurring with Node.js v20 and later when `workerThreads: false` is set in the AVA configuration. Fixes: #3390 * Update comment and rename message Whether the worker exits depends on whether user code is keeping the event loop busy. 'Freed' is a better term. --------- Co-authored-by: Mark Wubben <[email protected]>
1 parent 41a684f commit b99b703

File tree

6 files changed

+35
-0
lines changed

6 files changed

+35
-0
lines changed

lib/fork.js

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -104,6 +104,11 @@ export default function loadFork(file, options, execArgv = process.execArgv) {
104104
}
105105

106106
switch (message.ava.type) {
107+
case 'worker-finished': {
108+
send({type: 'free-worker'});
109+
break;
110+
}
111+
107112
case 'ready-for-options': {
108113
send({type: 'options', options});
109114
break;

lib/worker/base.js

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -120,6 +120,15 @@ const run = async options => {
120120
return;
121121
}
122122

123+
channel.send({type: 'worker-finished'});
124+
125+
// Reference the channel until the worker is freed. This should prevent Node.js from terminating the child process
126+
// prematurely, which has been witnessed on Windows. See discussion at
127+
// <https://github.com/avajs/ava/issues/3390#issuecomment-3056119361>.
128+
channel.ref();
129+
await channel.workerFreed;
130+
channel.unref();
131+
123132
nowAndTimers.setImmediate(() => {
124133
const unhandled = currentlyUnhandled();
125134
if (unhandled.length === 0) {

lib/worker/channel.cjs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -107,7 +107,9 @@ handle.ref();
107107

108108
exports.options = selectAvaMessage(handle.channel, 'options').then(message => message.ava.options);
109109
exports.peerFailed = selectAvaMessage(handle.channel, 'peer-failed');
110+
exports.workerFreed = selectAvaMessage(handle.channel, 'free-worker');
110111
exports.send = handle.send.bind(handle);
112+
exports.ref = handle.ref.bind(handle);
111113
exports.unref = handle.unref.bind(handle);
112114

113115
let channelCounter = 0;
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
{
2+
"type": "module"
3+
}

test/child-process/fixtures/test.js

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
import test from 'ava';
2+
3+
for (let i = 0; i < 50; i++) {
4+
test('Test ' + (i + 1), t => {
5+
t.true(true);
6+
});
7+
}

test/child-process/test.js

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
import test from '@ava/test';
2+
3+
import {fixture} from '../helpers/exec.js';
4+
5+
// Regression test for https://github.com/avajs/ava/issues/3390
6+
test('running 50 tests in a child process works as expected', async t => {
7+
const result = await fixture(['--no-worker-threads']);
8+
t.is(result.stats.passed.length, 50);
9+
});

0 commit comments

Comments
 (0)