首页 > 解决方案 > 当 Ruby 的 popen3 启动 npm install 时,node-sass 模块安装脚本不退出

问题描述

更新 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;
}

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

这对我来说是一个令人头疼的时刻......

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

我考虑了一个替代版本,popen3它显式等待子进程,而不是仅仅隐式等待流全部结束,但是虽然这确实允许调用方正常进行,但流氓子进程仍然徘徊,并且会通过持有目录的打开句柄来干扰后续调用./node_modules/node-sass

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


更新:我很确定这实际上是一个节点问题。我找到了挂起的根本原因,它是通过一个复杂的子进程树,npm install最终留下一个实例,node.exe它只是坐在那里,显然是无限期地,什么都不做,保持它继承的stdout和管道打开。stderr

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

  1. 有没有办法让 Node在完成后不会留下一个落后的进程npm install
  2. 有没有办法显式等待直接子进程popen3退出,而不是等待流结束,然后可能从侦听端关闭流以终止泵送输出的线程?

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

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

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

这仅在进程运行时发生popen3;CMD 提示符中的相同npm install内容正常退出。


原始问题:

popen3在 Ruby 脚本中遇到问题。它挂了,但我很确定它不是任何通常的候选人。我已经popen3用大量注释更新了我的调用,以便我可以在控制台输出中看到正在发生的事情。这是我打电话的方式:

  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对. 但是我的代码使用单独的线程并保持管道缓冲区为空。stdoutstderr

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

有人认识这些症状吗?为什么子进程退出时stdoutstderr流没有达到流尾?

标签: node.jsrubynpm-installnode-sasspopen3

解决方案


推荐阅读