Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

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

Open
Jimbly opened this issue Jan 9, 2025 · 1 comment

Comments

@Jimbly
Copy link
Contributor

Jimbly commented Jan 9, 2025

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)
@Jimbly
Copy link
Contributor Author

Jimbly commented Jan 9, 2025

This has been causing me trouble in development with a process tree of:

nodemon
  our app's build script
    electron-forge child process (reads from stdin)
    imagemin child process (imports node:process)

This appears slightly similar to #10836 (which was just a single grandchild, but otherwise the same piping / inheriting tree).

Jimbly added a commit to Jimbly/glov-build that referenced this issue Jan 9, 2025
By not inheriting stdin, it will avoid a potential process hang
Reference: nodejs/node#56537
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

1 participant