Skip to content

Commit 7068d97

Browse files
test: add deadlock case
1 parent d9f61cb commit 7068d97

File tree

5 files changed

+98
-0
lines changed

5 files changed

+98
-0
lines changed
Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
let port;
2+
3+
export function initialize(data) {
4+
({ port } = data);
5+
}
6+
7+
export function resolve(specifier, context, nextResolve) {
8+
const id = ''+Math.random();
9+
port.postMessage({
10+
context,
11+
id,
12+
specifier,
13+
});
14+
15+
return new Promise((resolve) => {
16+
port.on('message', (message) => {
17+
if (message.id !== id) return;
18+
19+
port.off('message', onMessage);
20+
resolve({ url: message.specifier });
21+
});
22+
});
23+
}
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
import { register } from 'node:module';
2+
import { MessageChannel } from 'node:worker_threads';
3+
4+
const { port1, port2 } = new MessageChannel();
5+
6+
port1.unref();
7+
port2.unref();
8+
9+
register((new URL('./hooks.mjs', import.meta.url)).href, {
10+
data: {
11+
port: port2,
12+
},
13+
parentURL: import.meta.url,
14+
transferList: [port2],
15+
});
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
// This file will not get loaded because of a deadlock trying to resolve it.
2+
3+
export function resolve(specifier, context, nextResolve) {
4+
return nextResolve(specifier, context, nextResolve);
5+
}
6+
7+
export function load(url, context, nextLoad) {
8+
return nextLoad(url, context, nextLoad);
9+
}
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
import { register } from 'node:module';
2+
import { MessageChannel } from 'node:worker_threads';
3+
4+
const { port1, port2 } = new MessageChannel();
5+
6+
port1.unref();
7+
port2.unref();
8+
9+
register((new URL('./hooks.mjs', import.meta.url)).href);
Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
const { mustNotCall } = require('../../common/index.js');
2+
const assert = require('node:assert');
3+
const { spawn } = require('node:child_process');
4+
const path = require('node:path');
5+
const { execPath } = require('node:process');
6+
const { describe, it } = require('node:test');
7+
8+
describe('hooks deadlock', { concurrency: true }, () => {
9+
it('will deadlock when a/hooks…resolve tries to contact the main thread during b/register', async () => {
10+
let stderr = '';
11+
let stdout = '';
12+
// ! Do NOT use spawnSync here: it will deadlock.
13+
const child = spawn(execPath, [
14+
`--import=${path.resolve(__dirname, './a/register.mjs')}`,
15+
`--import=${path.resolve(__dirname, './b/register.mjs')}`,
16+
'--input-type=module',
17+
'--eval',
18+
'import.meta.url;console.log("done")',
19+
]);
20+
child.stderr.setEncoding('utf8');
21+
child.stderr.on('data', (data) => { stderr += data; });
22+
child.stdout.setEncoding('utf8');
23+
child.stdout.on('data', (data) => { stdout += data; });
24+
25+
child.on('close', () => mustNotCall('Deadlock should prevent closing'));
26+
27+
return new Promise((res, rej) => {
28+
setTimeout(() => {
29+
try {
30+
assert.strictEqual(stderr, '');
31+
assert.strictEqual(child.exitCode, null); // It hasn't ended
32+
assert.strictEqual(child.signalCode, null); // It hasn't ended
33+
34+
assert.strictEqual(child.kill('SIGKILL'), true); // Deadlocked process must be forcibily killed.
35+
res('deadlocked process terminated');
36+
} catch (e) {
37+
rej(e);
38+
}
39+
}, 5_000); // This should have finished well before 5 second.
40+
});
41+
});
42+
});

0 commit comments

Comments
 (0)