4

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

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,并控制何时阻止执行。一般的想法是我分叉一个非阻塞进程。在这个分叉中,我产生了一个命令。我将命令 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

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

4

2 回答 2

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() 直到产生才会完成进程已关闭其标准输出。

于 2012-12-12T18:21:45.380 回答
0

事实上,您的诊断可能是正确的。您可以在等待进程结束时在管道上实现一个选择和读取循环,但很可能您可以使用 stdlib 更简单地获得您想要的东西Open3::capture2e

于 2012-12-11T23:03:03.650 回答