ruby Process.spawn stdout =>管道缓冲区大小限制

时间:2012-12-11 22:51:21

标签: ruby process buffer pipe spawn

在Ruby中,我使用Process.spawn在新进程中运行命令。我已经打开了一个双向管道来从生成的进程中捕获stdout和stderr。这很有效,直到写入管道的字节(来自命令的stdout)超过64Kb,此时命令永远不会完成。我认为管道缓冲区大小已被命中,并且管道的写入现在被阻止,导致进程永远不会完成。在我的实际应用程序中,我正在运行一个具有大量stdout的长命令,我需要捕获并在进程完成时保存。有没有办法提高缓冲区大小,或者更好的是刷新缓冲区以便不会达到限制?

cmd = "for i in {1..6600}; do echo '123456789'; done"  #works fine at 6500 iterations.

pipe_cmd_in, pipe_cmd_out = IO.pipe
cmd_pid = Process.spawn(cmd, :out => pipe_cmd_out, :err => pipe_cmd_out)

Process.wait(cmd_pid)
pipe_cmd_out.close
out = pipe_cmd_in.read
puts "child: cmd out length = #{out.length}"

更新 Open3 :: capture2e似乎适用于我展示的简单示例。不幸的是,对于我的实际应用程序,我需要能够获得生成进程的pid,并控制何时阻止执行。一般的想法是我分叉一个非阻塞过程。在这个fork中,我生成了一个命令。我将命令pid发送回父进程,然后我等待命令完成以获得退出状态。命令完成后,退出状态将发送回父级。在父级中,循环每1秒迭代一次,检查数据库是否有暂停和恢复等控制操作。如果它获得控制动作,它会将相应的信号发送到命令pid以停止,继续。当命令最终完成时,父命中命中救援块并读取退出状态管道,并保存到DB。这是我的实际流程:

#pipes for communicating the command pid, and exit status from child to parent
pipe_parent_in, pipe_child_out = IO.pipe
pipe_exitstatus_read, pipe_exitstatus_write = IO.pipe

child_pid = fork do
    pipe_cmd_in, pipe_cmd_out = IO.pipe
    cmd_pid = Process.spawn(cmd, :out => pipe_cmd_out, :err => pipe_cmd_out)
    pipe_child_out.write cmd_pid  #send command pid to parent
    pipe_child_out.close
    Process.wait(cmd_pid)
    exitstatus = $?.exitstatus
    pipe_exitstatus_write.write exitstatus  #send exitstatus to parent
    pipe_exitstatus_write.close
    pipe_cmd_out.close
    out = pipe_cmd_in.read
    #save out to DB
end

#blocking read to get the command pid from the child
pipe_child_out.close
cmd_pid = pipe_parent_in.read.to_i

loop do
    begin
        Process.getpgid(cmd_pid)  #when command is done, this will except
        @job.reload #refresh from DB

        #based on status in the DB, pause / resume command
        if @job.status == 'pausing'
            Process.kill('SIGSTOP', cmd_pid)
        elsif @job.status == 'resuming'
            Process.kill('SIGCONT', cmd_pid)
        end
    rescue
        #command is no longer running
        pipe_exitstatus_write.close
        exitstatus = pipe_exitstatus_read.read
        #save exit status to DB
        break
    end
    sleep 1
end

注意:我不能让父轮询命令输出管道,因为父进程将被阻塞,等待管道关闭。它无法通过控制循环暂停和恢复命令。

2 个答案:

答案 0 :(得分:1)

此代码似乎可以执行您想要的操作,并且可能是说明性的。

cmd = "for i in {1..6600}; do echo '123456789'; done"

pipe_cmd_in, pipe_cmd_out = IO.pipe
cmd_pid = Process.spawn(cmd, :out => pipe_cmd_out, :err => pipe_cmd_out)

@exitstatus = :not_done
Thread.new do
  Process.wait(cmd_pid); 
  @exitstatus = $?.exitstatus
end

pipe_cmd_out.close
out = pipe_cmd_in.read;
sleep(0.1) while @exitstatus == :not_done
puts "child: cmd out length = #{out.length}; Exit status: #{@exitstatus}"

通常,在线程之间共享数据(@exitstatus)需要更多的关注,但它可以工作 这里因为它只是在初始化后由线程写入一次。 (事实证明 $ ?. exitstatus可以返回nil,这就是我将其初始化为其他内容的原因。)调用 sleep()不太可能执行一次,因为它上面的read()将无法完成 直到产生的进程关闭它的标准输出。

答案 1 :(得分:0)

确实,您的诊断可能是正确的。您可以在等待进程结束时在管道上实现选择和读取循环,但是您可以使用stdlib Open3::capture2e更简单地获得所需内容。