Skip to content

Commit e78d65c

Browse files
committed
Don't force-exit after tests have completed
Fixes #1718. With worker threads, this seems to stop AVA from detecting that the thread has exited, causing a hang. Also remove flush logic implemented in #1722. Let's hope that current Node.js versions are better at flushing IPC before exiting.
1 parent e07179b commit e78d65c

File tree

4 files changed

+39
-45
lines changed

4 files changed

+39
-45
lines changed

lib/fork.js

Lines changed: 0 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -122,11 +122,6 @@ export default function loadFork(file, options, execArgv = process.execArgv) {
122122
break;
123123
}
124124

125-
case 'ping': {
126-
send({type: 'pong'});
127-
break;
128-
}
129-
130125
default: {
131126
emitStateChange(message.ava);
132127
}

lib/reporters/default.js

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -240,6 +240,12 @@ export default class Reporter {
240240
break;
241241
}
242242

243+
case 'exit-failed': {
244+
this.write(colors.error(`${figures.cross} Failed to exit when running ${this.relativeFile(event.testFile)}`));
245+
246+
break;
247+
}
248+
243249
case 'hook-finished': {
244250
if (event.logs.length > 0) {
245251
this.lineWriter.writeLine(` ${this.prefixTitle(event.testFile, event.title)}`);

lib/worker/base.js

Lines changed: 33 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -23,35 +23,42 @@ import {isRunningInThread, isRunningInChildProcess} from './utils.cjs';
2323
const currentlyUnhandled = setUpCurrentlyUnhandled();
2424
let runner;
2525

26+
let failedToExit = false;
27+
28+
const expectExit = code => {
29+
// Set the exit code, which is just handy.
30+
process.exitCode = code;
31+
32+
// Note that this timer is unreferenced. It should not keep the event loop
33+
// open. However if after a second the process is still running, assume
34+
// something is keeping the event loop busy. Exit forcibly but try to convey
35+
// the reason to the main process.
36+
nowAndTimers.setTimeout(() => {
37+
failedToExit = true;
38+
channel.send({type: 'exit-failed', code});
39+
process.exit(1);
40+
}, 1000).unref();
41+
};
42+
2643
// Override process.exit with an undetectable replacement
2744
// to report when it is called from a test (which it should never be).
28-
const {apply} = Reflect;
29-
const realExit = process.exit;
30-
31-
async function exit(code, forceSync = false) {
32-
const flushing = channel.flush();
33-
if (!forceSync) {
34-
await flushing;
45+
const handleProcessExit = (target, thisArg, args) => {
46+
if (!failedToExit) {
47+
const error = new Error('Unexpected process.exit()');
48+
Error.captureStackTrace(error, handleProcessExit);
49+
channel.send({type: 'process-exit', stack: error.stack});
3550
}
3651

37-
apply(realExit, process, [code]);
38-
}
39-
40-
const handleProcessExit = (fn, receiver, args) => {
41-
const error = new Error('Unexpected process.exit()');
42-
Error.captureStackTrace(error, handleProcessExit);
43-
channel.send({type: 'process-exit', stack: error.stack});
44-
4552
// Make sure to extract the code only from `args` rather than e.g. `Array.prototype`.
4653
// This level of paranoia is usually unwarranted, but we're dealing with test code
4754
// that has already colored outside the lines.
4855
const code = args.length > 0 ? args[0] : undefined;
4956

5057
// Force a synchronous exit as guaranteed by the real process.exit().
51-
exit(code, true);
58+
target.call(thisArg, code);
5259
};
5360

54-
process.exit = new Proxy(realExit, {
61+
process.exit = new Proxy(process.exit, {
5562
apply: handleProcessExit,
5663
});
5764

@@ -101,7 +108,7 @@ const run = async options => {
101108

102109
runner.on('error', error => {
103110
channel.send({type: 'internal-error', err: serializeError(error)});
104-
exit(1);
111+
expectExit(1);
105112
});
106113

107114
runner.on('finish', async () => {
@@ -112,30 +119,32 @@ const run = async options => {
112119
}
113120
} catch (error) {
114121
channel.send({type: 'internal-error', err: serializeError(error)});
115-
exit(1);
122+
expectExit(1);
116123
return;
117124
}
118125

119126
try {
120127
await Promise.all(sharedWorkerTeardowns.map(fn => fn()));
121128
} catch (error) {
122129
channel.send({type: 'uncaught-exception', err: serializeError(error)});
123-
exit(1);
130+
expectExit(1);
124131
return;
125132
}
126133

127134
nowAndTimers.setImmediate(() => {
135+
let exitCode = 0;
128136
for (const rejection of currentlyUnhandled()) {
129137
channel.send({type: 'unhandled-rejection', err: serializeError(rejection.reason, {testFile: options.file})});
138+
exitCode = 1;
130139
}
131140

132-
exit(0);
141+
expectExit(exitCode);
133142
});
134143
});
135144

136145
process.on('uncaughtException', error => {
137146
channel.send({type: 'uncaught-exception', err: serializeError(error, {testFile: options.file})});
138-
exit(1);
147+
expectExit(1);
139148
});
140149

141150
// Store value to prevent required modules from modifying it.
@@ -248,11 +257,11 @@ const run = async options => {
248257
channel.unref();
249258
} else {
250259
channel.send({type: 'missing-ava-import'});
251-
exit(1);
260+
expectExit(1);
252261
}
253262
} catch (error) {
254263
channel.send({type: 'uncaught-exception', err: serializeError(error, {testFile: options.file})});
255-
exit(1);
264+
expectExit(1);
256265
}
257266
};
258267

lib/worker/channel.cjs

Lines changed: 0 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -110,22 +110,6 @@ exports.peerFailed = selectAvaMessage(handle.channel, 'peer-failed');
110110
exports.send = handle.send.bind(handle);
111111
exports.unref = handle.unref.bind(handle);
112112

113-
let pendingPings = Promise.resolve();
114-
async function flush() {
115-
handle.ref();
116-
const promise = pendingPings.then(async () => {
117-
handle.send({type: 'ping'});
118-
await selectAvaMessage(handle.channel, 'pong');
119-
if (promise === pendingPings) {
120-
handle.unref();
121-
}
122-
});
123-
pendingPings = promise;
124-
await promise;
125-
}
126-
127-
exports.flush = flush;
128-
129113
let channelCounter = 0;
130114
let messageCounter = 0;
131115

0 commit comments

Comments
 (0)