Skip to content

Commit ae60865

Browse files
committed
Add blocked event loop test
1 parent 27300db commit ae60865

File tree

9 files changed

+152
-86
lines changed

9 files changed

+152
-86
lines changed

test/basic.test.mjs

Lines changed: 0 additions & 57 deletions
This file was deleted.

test/e2e.test.mjs

Lines changed: 90 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,90 @@
1+
import { spawnSync } from 'node:child_process';
2+
import { join } from 'node:path';
3+
import { describe, expect, test } from 'vitest';
4+
5+
const __dirname = import.meta.dirname || new URL('.', import.meta.url).pathname;
6+
7+
describe('e2e Tests', () => {
8+
test('Capture stack trace from multiple threads', () => {
9+
const testFile = join(__dirname, 'stack-traces.js');
10+
const result = spawnSync('node', [testFile])
11+
12+
expect(result.status).toBe(0);
13+
14+
const stacks = JSON.parse(result.stdout.toString());
15+
16+
expect(stacks['0']).toEqual(expect.arrayContaining([
17+
{
18+
function: 'pbkdf2Sync',
19+
filename: expect.any(String),
20+
lineno: expect.any(Number),
21+
colno: expect.any(Number),
22+
},
23+
{
24+
function: 'longWork',
25+
filename: expect.stringMatching(/long-work.js$/),
26+
lineno: expect.any(Number),
27+
colno: expect.any(Number),
28+
},
29+
{
30+
function: '?',
31+
filename: expect.stringMatching(/stack-traces.js$/),
32+
lineno: expect.any(Number),
33+
colno: expect.any(Number),
34+
},
35+
]));
36+
37+
expect(stacks['2']).toEqual(expect.arrayContaining([
38+
{
39+
function: 'pbkdf2Sync',
40+
filename: expect.any(String),
41+
lineno: expect.any(Number),
42+
colno: expect.any(Number),
43+
},
44+
{
45+
function: 'longWork',
46+
filename: expect.stringMatching(/long-work.js$/),
47+
lineno: expect.any(Number),
48+
colno: expect.any(Number),
49+
},
50+
{
51+
function: '?',
52+
filename: expect.stringMatching(/worker.js$/),
53+
lineno: expect.any(Number),
54+
colno: expect.any(Number),
55+
},
56+
]));
57+
});
58+
59+
test('detect stalled thread', { timeout: 10000 }, () => {
60+
const testFile = join(__dirname, 'stalled.js');
61+
const result = spawnSync('node', [testFile]);
62+
63+
expect(result.status).toBe(0);
64+
65+
const stacks = JSON.parse(result.stdout.toString());
66+
67+
expect(stacks['0']).toEqual(expect.arrayContaining([
68+
{
69+
function: 'pbkdf2Sync',
70+
filename: expect.any(String),
71+
lineno: expect.any(Number),
72+
colno: expect.any(Number),
73+
},
74+
{
75+
function: 'longWork',
76+
filename: expect.stringMatching(/long-work.js$/),
77+
lineno: expect.any(Number),
78+
colno: expect.any(Number),
79+
},
80+
{
81+
function: '?',
82+
filename: expect.stringMatching(/stalled.js$/),
83+
lineno: expect.any(Number),
84+
colno: expect.any(Number),
85+
},
86+
]));
87+
88+
expect(stacks['2'].length).toEqual(1);
89+
});
90+
});

test/long-work.js

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
const crypto = require('node:crypto');
2+
3+
function longWork() {
4+
for (let i = 0; i < 100; i++) {
5+
const salt = crypto.randomBytes(128).toString('base64');
6+
const hash = crypto.pbkdf2Sync('myPassword', salt, 10000, 512, 'sha512');
7+
console.assert(hash);
8+
}
9+
}
10+
11+
module.exports = { longWork };

test/stack-traces.js

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
const { Worker } = require('node:worker_threads');
2+
const { longWork } = require('./long-work.js');
3+
const { registerThread } = require('../lib/index.js');
4+
5+
registerThread();
6+
7+
const watchdog = new Worker('./test/watchdog.js');
8+
9+
const worker = new Worker('./test/worker.js');
10+
11+
longWork();

test/stalled-watchdog.js

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
const { captureStackTrace, getThreadsLastSeen } = require('../lib/index.js');
2+
3+
const THRESHOLD = 500; // 1 second
4+
5+
const timer = setInterval(() => {
6+
for (const [_threadId, lastSeen] of Object.entries(getThreadsLastSeen())) {
7+
if (lastSeen > THRESHOLD) {
8+
// we don't want to run more than once
9+
clearInterval(timer);
10+
const stackTraces = captureStackTrace();
11+
console.log(JSON.stringify(stackTraces));
12+
process.exit(0)
13+
}
14+
}
15+
}, 500);

test/stalled.js

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
const { Worker } = require('node:worker_threads');
2+
const { longWork } = require('./long-work.js');
3+
const { registerThread } = require('../lib/index.js');
4+
5+
setInterval(() => {
6+
registerThread();
7+
}, 200).unref();
8+
9+
const watchdog = new Worker('./test/stalled-watchdog.js');
10+
watchdog.on('exit', () => process.exit(0));
11+
12+
const worker = new Worker('./test/worker-do-nothing.js');
13+
14+
setTimeout(() => {
15+
longWork();
16+
}, 2000);
17+

test/test.js

Lines changed: 0 additions & 20 deletions
This file was deleted.

test/worker-do-nothing.js

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
const { longWork } = require('./long-work');
2+
const { registerThread } = require('../lib/index.js');
3+
4+
setInterval(() => {
5+
registerThread();
6+
}, 200);
7+

test/worker.js

Lines changed: 1 addition & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,6 @@
1-
const crypto = require('node:crypto');
2-
1+
const { longWork } = require('./long-work');
32
const { registerThread } = require('..');
43

54
registerThread();
65

7-
function longWork() {
8-
for (let i = 0; i < 100; i++) {
9-
const salt = crypto.randomBytes(128).toString('base64');
10-
const hash = crypto.pbkdf2Sync('myPassword', salt, 10000, 512, 'sha512');
11-
}
12-
}
13-
146
longWork();

0 commit comments

Comments
 (0)