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