Skip to content

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

Open
@Jimbly

Description

@Jimbly

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, presumably all non-Windows platforms.

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

Always.

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)

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions