`process.report.getReport()` crashes the process on Windows while one Worker exchanges messages and another Worker is idle
Les mainteneurs répondent en général sous 1 jour
Personne n'a encore pris cette issue.
Évaluation
- Difficulté
- 4/5
- Temps estimé
- 3-5 jours
- Accessibilité débutants
- 38/100
- Type d'issue
- Bug
- Clarté
- Plutôt claire
- Activité
- Active
- Stack technique
- javascript, node.js
- Domaine
- backend
Piste de recherche
Start with the callback in src/node_report.cc:236 and trace how the main thread requests each Worker's report section through an interrupt. Reproduce on Windows using the supplied script, comparing runs with reports and with --one-worker; the issue says the Linux runs are clean. Done means reports no longer crash or produce invalid JSON in the reported Windows scenario, with a regression test covering it.
Rédigé par le modèle d'indexation à partir du texte de l'issue.
Description
Versions
- Crashes: Node 26.13.0, 26.12.1, 24.22.1 and 24.14.0 on Windows 11, x64, on two machines.
- Clean: Node 26.13.0 on Ubuntu 24.04.5 (GitHub's
ubuntu-24.04runner, x64, glibc 2.39), 100 runs of 100.
Machines
| Machine | OS | Node |
|---|---|---|
| Intel Core i5-12500H | Windows 11 Home 10.0.26200 | 24.22.1, 26.12.1, 26.13.0 |
| AMD Ryzen 7 9800X3D | Windows 11 Pro Education 10.0.26200 | 24.14.0 |
| GitHub runner, AMD EPYC 7763, 2 cores | Ubuntu 24.04.5 LTS, kernel 7.0.0-1017-azure | 26.13.0 |
Reproducer
One file, no dependencies. The main thread starts two Workers from data: URLs: one answers each message with one of its own after making some garbage, and is sent a message every 16 ms; the other does nothing. Once the busy Worker has answered five times, the main thread calls process.report.getReport() ten times, 50 ms apart, then ends both Workers. Run without arguments it runs that in 20 child processes and tallies their exit codes; --without-reports waits the same time but takes no report; --one-worker leaves the idle Worker out.
// Node's diagnostic report, taken on the main thread while one Worker answers a message every 16 ms and a second Worker
// sits idle, crashes the process on Windows. `node report-crash.mjs [runs]` runs that in child processes, 20 unless
// given, and tallies their exit codes; `--without-reports` waits the same time but takes no report; `--one-worker`
// leaves the idle Worker out.
import { spawnSync } from "node:child_process";
import { fileURLToPath } from "node:url";
import { Worker } from "node:worker_threads";
const busyCode = `
import { parentPort } from "node:worker_threads";
parentPort.on("message", (step) => {
const garbage = Array.from({ length: 2000 }, (_, index) => ({ index, text: "step " + step }));
parentPort.postMessage({ step, made: garbage.length });
});
`;
const idleCode = `
import { parentPort } from "node:worker_threads";
parentPort.on("message", () => process.exit(0));
`;
const dataUrl = (code) => new URL(`data:text/javascript,${encodeURIComponent(code)}`);
const flags = process.argv.slice(2);
const isReporting = !flags.includes("--without-reports");
const hasIdle = !flags.includes("--one-worker");
async function runOnce() {
const idle = hasIdle ? new Worker(dataUrl(idleCode)) : undefined;
if (idle) await new Promise((resolve) => idle.once("online", resolve));
const busy = new Worker(dataUrl(busyCode));
let answers = 0;
busy.on("message", () => { answers += 1; });
await new Promise((resolve) => busy.once("online", resolve));
let next = 0;
const stepping = setInterval(() => busy.postMessage((next += 1)), 16);
while (answers < 5) await new Promise((resolve) => setTimeout(resolve, 5));
for (let index = 0; index < 10; index += 1) {
if (isReporting) process.report.getReport();
else await new Promise((resolve) => setTimeout(resolve, 30));
await new Promise((resolve) => setTimeout(resolve, 50));
}
clearInterval(stepping);
await busy.terminate();
if (idle) { idle.postMessage("end"); await new Promise((resolve) => idle.once("exit", resolve)); }
}
if (flags.includes("--run")) {
await runOnce();
} else {
const meanings = { 0: "clean", 3221226356: "heap corruption (0xC0000374)", 3221225477: "access violation (0xC0000005)", 3221226505: "fast fail (0xC0000409)" };
const runs = Number(flags.find((flag) => /^\d+$/.test(flag))) || 20;
const passed = flags.filter((flag) => flag.startsWith("--"));
const tally = new Map();
for (let index = 0; index < runs; index += 1) {
const run = spawnSync(process.execPath, [fileURLToPath(import.meta.url), "--run", ...passed], { stdio: ["ignore", "ignore", "inherit"] });
const meaning = meanings[run.status] ?? `exit ${run.status ?? run.signal}`;
tally.set(meaning, (tally.get(meaning) ?? 0) + 1);
}
console.log(`Node ${process.version} on ${process.platform}, ${runs} runs${passed.length ? " " + passed.join(" ") : ""}:`);
for (const [meaning, count] of tally) console.log(` ${count} ${meaning}`);
}
Expected: 20 clean exits. On Windows, about half the runs end in a heap corruption (exit code 0xC0000374), a fast fail (0xC0000409, as Node's own abort() gives on Windows) or, now and then, an access violation (0xC0000005); with the larger reproducer below, a few runs in 100 end with exit code 1 instead because getReport() is handed a report that is not JSON, its strings holding bytes from elsewhere:
SyntaxError: Bad control character in string literal in JSON at position 31547 (line 1176 column 16)
at parse (<anonymous>)
at Object.getReport (node:internal/process/report:64:12)
where line 1176 of the report text, an environment variable's name in environmentVariables, read as an environment variable's name with stray bytes in it.
With --without-reports every run is clean, so the Workers alone do not crash; with --one-worker every run is clean, so a report taken with one busy Worker and no other does not crash either. A child process with pipes held by the main thread, in place of the idle Worker, does not crash either (20 of 20 clean); the second Worker is what matters, busy or idle.
Counts
The smaller reproducer above, on the i5-12500H, Node 26.13.0:
| Runs | Clean | Heap corruption | Fast fail |
|---|---|---|---|
| 100, with reports | 50 | 15 | 35 |
20, --without-reports |
20 | 0 | 0 |
20, --one-worker |
20 | 0 | 0 |
No run of these 100 gave a report that was not JSON; the larger reproducer below did, 1 to 4 times in 100.
The larger reproducer, a script in our repository: the same busy Worker, started from a file, and esbuild's synchronous API called once first, whose service on Windows is a second, idle Worker holding the esbuild child process; the ten reports taken 50 ms apart. 100 runs a row.
| Machine | Node | Clean | Heap corruption | Access violation | Fast fail | Report not JSON |
|---|---|---|---|---|---|---|
| i5-12500H | 24.22.1 | 39 | 20 | 0 | 40 | 1 |
| i5-12500H | 26.12.1 | 21 | 49 | 2 | 24 | 4 |
| i5-12500H | 26.13.0 | 48 | 15 | 0 | 34 | 3 |
| 9800X3D | 24.14.0 | 4 of 20 | 1 of 20 | 0 of 20 | 15 of 20 | 0 |
| Linux | 26.13.0 | 100 | 0 | 0 | 0 | 0 |
On Linux the script's five cases, the Worker from a file, with its globals deleted, with its old generation held to 256 MiB, from a data: URL, and the repository's own sandbox Worker, were 20 of 20 clean each, and 20 runs without reports were clean. On the 9800X3D on 24.14.0, 100 runs without reports were clean, and the five cases crashed 11 to 16 of 20 each, so none of the Worker's own differences is the cause.
The dump
A dump of the larger reproducer's crash, taken on Node 24.14.0 under cdb -o with Node's symbols (node_4b10_2026-10-23_01-04-20-062_29cc.dmp, 23 Oct 2026), shows the heap found corrupt as node::report::WriteNodeReport's callback (src/node_report.cc:236), run through Environment::RunAndClearInterrupts, frees a std::ostringstream's buffer. That is the path by which the main thread's report asks each Worker, by an interrupt, for the Worker's own section of the report. The dump can be supplied.
What we know of the conditions
- Two Workers are needed, one of them exchanging messages while the reports are taken; the other may be idle.
- The reports must be taken; the same Workers for the same time without a report never crash.
- It is Windows' alone in our runs: 100 of 100 clean on Linux with the same script and Node.
- Node 24.14.0, 24.22.1, 26.12.1 and 26.13.0 all crash; nodejs/node#65778, fixed in 24.22.1 and 26.10.0, is another path (a Worker's teardown), and this crash outlived it.
- Langage dominant
- JavaScript
- Étoiles
- 123k
- Forks
- 39.3k
- Merge moyen
- 4 j 15 h
- PR mergées (30 j)
- 238
Préparer son environnement
- Aucun Dockerfile ni fichier Docker Compose
- Propose un modèle de pull request
- Lire le guide de contribution
Par où commencer
- Lisez l'issue en entier, puis le guide de contribution du projet.
- Signalez en commentaire que vous la prenez — cela évite que deux personnes fassent le même travail.
- Forkez le dépôt et travaillez sur une branche.
- Ouvrez une pull request qui référence le numéro de l'issue.
Autres issues de nodejs/node
-
http2: content-encoding is incorrectly listed in kSingleValueHeadersPeut-être pris @inoway46 l’a pris il y a 2 jours. Ouverteconfirmed-bug http2
Difficulté 2/5 1-3 heures Accessibilité débutants 74/100
nodejs/node#67151 · 1 commentaire ·
Les mainteneurs répondent en général sous 1 jour
-
util.parseEnv and --env-file keep a leading UTF-8 BOM in the first keyPeut-être pris @helmipradita l’a pris il y a 4 jours. Ouverte
Difficulté 2/5 1-3 heures Accessibilité débutants 78/100
Les mainteneurs répondent en général sous 1 jour
-
node:internal/inspector/network_http: `TypeError: Missing dataLength` in event when response uses `setEncoding()`Peut-être à nouveau libre Une pull request pour cette issue a été fermée sans être fusionnée. Ouverte
Difficulté 2/5 1-3 heures Accessibilité débutants 78/100
nodejs/node#66551 · 1 commentaire ·
Les mainteneurs répondent en général sous 1 jour
-
[Docs] `process.loadEnvFile()` does not document behaviour when variables already existPeut-être pris @Sepandard l’a pris il y a 37 jours. Ouvertedoc
Difficulté 1/5 Moins d'une heure Accessibilité débutants 90/100
Les mainteneurs répondent en général sous 1 jour
-
Stream.prototype.forEach will block in first promise in queue before read more chunkPeut-être pris @mmustafasenoglu l’a pris il y a 38 jours. Ouvertedoc
Difficulté 2/5 1-3 heures Accessibilité débutants 65/100
Les mainteneurs répondent en général sous 1 jour
Toutes les issues de nodejs/node
Issues similaires
-
bug confirmed js v6
Difficulté 2/5 1-3 heures Accessibilité débutants 85/100
Les mainteneurs répondent en général sous 1 jour
-
Difficulté 2/5 1-3 heures Accessibilité débutants 72/100
Les mainteneurs répondent en général sous 1 jour
-
BUG
Difficulté 1/5 1-3 heures Accessibilité débutants 90/100
linuxmint/cinnamon-spices-applets#9223 ·
Les mainteneurs répondent en général sous 1 jour
-
Difficulté 2/5 1-3 heures Accessibilité débutants 70/100
Les mainteneurs répondent en général sous 1 jour
-
Difficulté 2/5 1-3 heures Accessibilité débutants 78/100
fastify/fastify-swagger-ui#299 ·