nodejs / nodejs/node

Node process hangs during `import('process')` if a sibling process reads from `stdin` and `stdin` is piped to a parent process

Open
#56,537 11 comments 0 reactions 0 assignees View on GitHub

Nobody has claimed this yet.

Dominant language
JavaScript
Stars
122k
Forks
37.3k
Avg merge
4d 2h
Merged PRs (30d)
283

Description

Version

All versions, tested on v12 through v23.6.0

Platform

Windows 10 and 11

What steps will reproduce the bug?

Launching a process tree like the following causes grandchild2 to hang (cannot attach Node inspector, no JS code executes) after calling import('process') (which never resolves, unless its sibling grandchild1 exits):

  main.js - launches child with piped stdin
    middle.js - launches children with inherited stdin
      grandchild1.js - attaches a listener to stdin
      (1s later) grandchild2.js - imports node:process and then hangs

Example code:

main.js

require('child_process').spawn('node', ['middle.js'], { stdio: ['pipe', 'inherit', 'inherit'] });

middle.js

const { spawn } = require('child_process');

spawn('node', ['grandchild1.js'], { stdio: 'inherit' });
// Note: this also works: spawn('cmd', ['/c', 'pause'], { stdio: 'inherit' });

setTimeout(function () {
  spawn('node', ['grandchild2.js'], { stdio: 'inherit' });
}, 1000);

grandchild1.js

process.stdin.on('data', console.log);

grandchild2.js

import('process').then(function (mod) {
  console.log('import(node:process) completed'); // this line is never executed
});

console.log(process.pid, `grandchild2 started - if you don't see "not stuck" below, it's stuck`);
setInterval(function () {
  console.log(process.pid, 'not stuck');
}, 1000);

Example repo with the above code (plus a bit more logging): https://github.com/Jimbly/node-stdin-hang-bug-demo

Tested on (all fail):

  • Node v12...v22
  • Windows 10, 11

Works fine on Linux, MacOS, presumably all non-Windows platforms.

How often does it reproduce? Is there a required condition?

Always (on Windows).

Additional information

Hang does not occur if any of these happen:

  • grandchild1 is launched before grandchild2
  • middle's stdin is not piped to main
  • grandchild1's or grandchild2's stdin is not inherit
  • grandchild1 exits (causes grandchild2 to get unstuck)

Hang still occurs if:

  • grandchild1 is anything that reads from stdio (e.g. cmd /c pause)

The hung process's (native) call stack is:

NtSetInformationFile()
SetNamedPipeHandleState(pipeHandle, PIPE_READMODE_BYTE | PIPE_WAIT)
node.exe!uv__set_pipe_handle(loop=0x00007ff71d27f130, handle=0x000001c0c83367f0, pipeHandle=0x0000000000000278, fd=-1, duplex_flags=49152)
	at deps\uv\src\win\pipe.c(482)
node.exe!uv_pipe_open(pipe=0x000001c0c83367f0, file=0)
	at deps\uv\src\win\pipe.c(2480)
node.exe!node::PipeWrap::Open(args={...})
	at src\pipe_wrap.cc(209)
node.exe!Builtins_CallApiCallbackGeneric()
	at out\Release\obj\v8_snapshot\embedded.S(4265)
node.exe!Builtins_InterpreterEntryTrampoline()
	at out\Release\obj\v8_snapshot\embedded.S(3946)
node.exe!Builtins_InterpreterPushArgsThenFastConstructFunction()
	at out\Release\obj\v8_snapshot\embedded.S(4038)
node.exe!Builtins_ConstructHandler()
	at out\Release\obj\v8_snapshot\embedded.S(56237)
node.exe!Builtins_InterpreterEntryTrampoline()
	at out\Release\obj\v8_snapshot\embedded.S(3946)
node.exe!Builtins_GetPropertyWithReceiver()
	at out\Release\obj\v8_snapshot\embedded.S(24522)
node.exe!Builtins_ReflectGet()
	at out\Release\obj\v8_snapshot\embedded.S(41510)
00007ff69938aaf7()
00007ff69938acf4()
node.exe!Builtins_InterpreterEntryTrampoline()
	at out\Release\obj\v8_snapshot\embedded.S(3946)
node.exe!Builtins_JSEntryTrampoline()
	at out\Release\obj\v8_snapshot\embedded.S(3658)
node.exe!Builtins_JSEntry()
	at out\Release\obj\v8_snapshot\embedded.S(3616)
[Inline Frame] node.exe!v8::internal::GeneratedCode<unsigned __int64,unsigned __int64,unsigned __int64,unsigned __int64,unsigned __int64,__int64,unsigned __int64 * *>::Call()
	at deps\v8\src\execution\simulator.h(178)
node.exe!v8::internal::`anonymous namespace'::Invoke(isolate=0x000001c0c82c1000, params={...})
	at deps\v8\src\execution\execution.cc(420)
node.exe!v8::internal::Execution::Call(isolate=0x000001c0c82c1000, callable, receiver, argc=0, argv=0x0000000000000000)
	at deps\v8\src\execution\execution.cc(506)
node.exe!v8::Function::Call(context, recv={...}, argc=0, argv=0x0000000000000000)
	at deps\v8\src\api\api.cc(5484)
node.exe!node::loader::ModuleWrap::SyntheticModuleEvaluationStepsCallback(context={...}, module)
	at src\module_wrap.cc(948)
node.exe!v8::internal::SyntheticModule::Evaluate(isolate=0x000001c0c82c1000, module={...})
	at deps\v8\src\objects\synthetic-module.cc(108)
node.exe!v8::internal::Module::Evaluate(isolate=0x000001c0c82c1000, module={...})
	at deps\v8\src\objects\module.cc(284)
node.exe!v8::Module::Evaluate(context)
	at deps\v8\src\api\api.cc(2461)
[Inline Frame] node.exe!node::loader::ModuleWrap::Evaluate::__l2::<lambda_1>::operator()()
	at src\module_wrap.cc(562)
node.exe!node::loader::ModuleWrap::Evaluate(args={...})
	at src\module_wrap.cc(578)
node.exe!Builtins_CallApiCallbackGeneric()
	at out\Release\obj\v8_snapshot\embedded.S(4265)
node.exe!Builtins_InterpreterEntryTrampoline()
	at out\Release\obj\v8_snapshot\embedded.S(3946)
node.exe!Builtins_InterpreterEntryTrampoline()
	at out\Release\obj\v8_snapshot\embedded.S(3946)
node.exe!Builtins_InterpreterEntryTrampoline()
	at out\Release\obj\v8_snapshot\embedded.S(3946)
node.exe!Builtins_InterpreterEntryTrampoline()
	at out\Release\obj\v8_snapshot\embedded.S(3946)
node.exe!Builtins_AsyncFunctionAwaitResolveClosure()
	at out\Release\obj\v8_snapshot\embedded.S(11765)
node.exe!Builtins_PromiseFulfillReactionJob()
	at out\Release\obj\v8_snapshot\embedded.S(40771)
node.exe!Builtins_RunMicrotasks()
	at out\Release\obj\v8_snapshot\embedded.S(9703)
(truncated for size, see above repo if more stack is useful)

Contributor guide

Open the contributing guide

First steps

  1. Read the whole issue, then the project's contributing guide.
  2. Comment on the issue to say you are picking it up — it saves two people doing the same work.
  3. Fork the repository and make your change on a branch.
  4. Open a pull request that references the issue number.

Research direction

Run the linked node-stdin-hang-bug-demo reproduction on Windows first. Then trace the interaction through deps/uv/src/win/pipe.c, src/pipe_wrap.cc, and src/module_wrap.cc, using the reported native stack as a starting point. Done means the sibling stdin scenario no longer hangs and import('process') completes while the reproduction still behaves correctly.

Written by the indexing model from the issue text.

Assessment

Tech stack
javascript, node.js
Domain
operating-systems
Issue type
Bug
Difficulty
5/5
Estimated time
Over a week
Activity status
Quiet
Clarity
Clearly specified
Newbie friendliness
35/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.