更新3:此问题似乎是node-sass的模块安装的一部分 . 搁浅的进程的工作目录为 ./node_modules/node-sass ,其命令行 scripts/install.js 解析为模块内的文件 . 此外,到达控制台的最后一行输出与node-sass' scripts/install.js 的行输出匹配:

if (cachedBinary) {
    console.log('Cached binary found at', cachedBinary);
    fs.createReadStream(cachedBinary).pipe(fs.createWriteStream(binaryPath));
    return;
}

从命令行运行时,此代码没有任何问题(即,只需在命令提示符下使用空白的 node_modules 目录发出 npm install ),但是当 npm install 通过 popen3 启动时,此处的流 .pipe 调用似乎无限期地阻塞 .

这对我来说是一个令人头疼的问题......

如果I ^ C是Ruby启动这些子进程的终端,则中断会使其进入恶意进程并导致它终止 . 但是,强制关闭所有管道(或简单地终止父进程)不会导致流氓 node.exe 退出 .

我考虑了一个替代版本的 popen3 明确地等待子进程,而不是只是隐式地等待所有流都结束,但是虽然这确实允许调用端正常进行,但是流氓子进程仍然挂起,并且通过保持 ./node_modules/node-sass 目录的打开句柄来干扰后续调用 .

更新4:我用 node-sass 项目打开了这个错误报告:https://github.com/sass/node-sass/issues/2459


更新:我很确定这实际上是一个Node问题 . 我追踪了挂起的根本原因,并且通过一个复杂的子进程树, npm install 最终留下了一个 node.exe 的实例,它只是坐在那里,显然无限期地无所事事,保留了它继承的 stdoutstderr 管道打开 .

所以,这留下了新的问题:

  • 有没有办法让Node在 npm install 完成后不会留下一个落后者进程?

  • 有没有办法明确等待 popen3 的直接子进程退出,而不是等待流结束,然后可能关闭来自侦听端的流来终止抽取输出的线程?

更新2:我用这个极简主义代码重现了这个问题:

Open3::popen3 "npm install" do |stdin, stdout, stderr, thr|
  stdin.close
  stdout.each_line { |l| puts l }
end

使用此代码, npm install 完成后,流氓 node.exe 进程(命令行: scripts/install.js )会挂起 . 终止进程取消阻止 popen3 调用(通过使 stdout 结束,所以 each_line 循环终止),并且^ Cing Ruby代码(当在IRB窗口中运行时)导致流氓 node.exe 终止(在控制台输出: => #<IO:(closed)> ) .

仅当进程通过 popen3 运行时才会发生这种情况;来自CMD提示符的相同 npm install 正常退出 .


原始问题:

我在Ruby脚本中遇到了 popen3 的问题 . 它's hanging, but I'我很确定's not any of the usual candidates. I'已经用大量的注释更新了我对 popen3 的调用,这样我就可以在控制台输出中看到's going on. Here is how I' m正在拨打电话:

command_output_lines = []
  lock = Mutex.new

  exit_code = nil

  Logger.log("[MAIN] beginning popen3 block")

  Open3.popen3(command_w_params) do |stdin, stdout, stderr, thr|
    Logger.log("[MAIN] closing stdin stream")

    stdin.close

    Logger.log("[MAIN] starting [STDOUT]")

    stdout_thread = Thread.new do
      Logger.log("[STDOUT] started")

      begin
        stdout.each_line do |stdout_line|
          Logger.log("[STDOUT] got a line, acquiring lock")

          lock.synchronize do
            command_output_lines <<= stdout_line
            Logger.log(stdout_line)
          end

          Logger.log("[STDOUT] lock released")
        end
      rescue Exception => e
        Logger.log("[STDOUT] exception: #{e}")
      end

      Logger.log("[STDOUT] exiting")
    end

    Logger.log("[MAIN] starting [STDERR]")

    stderr_thread = Thread.new do
      Logger.log("[STDERR] started")

      begin
        stderr.each_line do |stderr_line|
          Logger.log("[STDERR] got a line, acquiring lock")

          lock.synchronize do
            command_output_lines <<= "[STDERR] " + stderr_line
            Logger.warn(stderr_line)
          end

          Logger.log("[STDERR] lock released")
        end
      rescue Exception => e
        Logger.log("[STDERR] exception: #{e}")
      end

      Logger.log("[STDERR] exiting")
    end

    Logger.log("[MAIN] joining to [STDOUT]")
    stdout_thread.join
    Logger.log("[MAIN] joining to [STDERR]")
    stderr_thread.join

    Logger.log("[MAIN] threads joined, reading exit status")

    exit_code = thr.value.exitstatus
  end

  Logger.log("[MAIN] popen3 block completed")

(不要在意 Logger.log 究竟是什么;只知道它将输出发送到控制台 . )

在我看到问题的地方, command_w_params 等于 npm install ,并且此代码在 bundle exec rake TaskName 的上下文中运行 .

当它到达此代码时,我看到以下控制台输出:

[MAIN] beginning popen3 block
[MAIN] closing stdin stream
[MAIN] starting [STDOUT]
[MAIN] starting [STDERR]
[MAIN] joining to [STDOUT]
[STDOUT] started
[STDERR] started
[STDOUT] got a line, acquiring lock

[STDOUT] lock released
[STDOUT] got a line, acquiring lock
> node-sass@4.9.2 install C:\Users\Jonathan Gilbert\RepositoryName\ProjectName\node_modules\node-sass
[STDOUT] lock released
[STDOUT] got a line, acquiring lock
> node scripts/install.js
[STDOUT] lock released
[STDOUT] got a line, acquiring lock

[STDOUT] lock released
[STDOUT] got a line, acquiring lock
Cached binary found at C:\Users\Jonathan Gilbert\AppData\Roaming\npm-cache\node- sass\4.9.2\win32-x64-57_binding.node
[STDOUT] lock released

......然后它就会挂起来 . 此时,我可以在Process Explorer中看到子进程已退出 . 除了 ruby.exe 之外什么都没有留下,但它只是无限期地坐在那里直到它被明确取消 . 这两个线程仍在运行,表明 stdoutstderr 流尚未发出流末尾信号 .

现在,通常当人们对 popen3 有问题时,'s because they'不会同时读取 stdoutstderr ,并且其中一个或另一个填充其管道缓冲区,而父进程只关注另一个 . 但我的代码使用单独的线程并保持管道缓冲区为空 .

我看到的另一个问题是,子进程可能会在等待 stdin 被关闭,但在这种情况下:

  • stdin 正在关闭 .

  • 子进程甚至不再存在 .

有人认出这些症状吗?当子进程退出时,为什么 stdoutstderr 流不会到达流末尾?