ruby 超时和系统命令
Posted
技术标签:
【中文标题】ruby 超时和系统命令【英文标题】:ruby timeouts and system commands 【发布时间】:2012-01-07 16:29:26 【问题描述】:我有一个 ruby 超时,它调用这样的系统(bash)命令..
Timeout::timeout(10)
`my_bash_command -c12 -o text.txt`
但我认为即使ruby线程被中断,实际命令仍然在后台运行..正常吗?怎样才能杀死它?
【问题讨论】:
这不是真的。当父 ruby 进程终止时,运行命令的子 shell 应该终止。请举一个更具体的例子。 @BenLee: 超时后父进程不会终止。 @MladenJablanović,在一个快速的实验中它确实如此。我创建了一个 ruby 文件,除了:require 'timeout'; Timeout::timeout(100)
sleep 500` . While running it, I do
ps aux | grep sleep` 并查看睡眠过程。然后我将 SIGKILL 发送到 ruby 进程,并再次运行 ps aux | grep sleep
并且不再看到子进程。
@BenLee:请重新阅读我上面的评论。谢谢。
其实Mladen是对的,到了timeout时父进程并没有终止(除非运行命令后无事可做)。发生的事情是引发了 Timeout::Error 异常,仅此而已。子外壳仍在运行。即使父(ruby)进程在抛出异常后终止,启动的shell命令仍在运行,它只是从ruby进程重新分配给PID为1的进程作为它的子进程。至少这是在 Mac OSX 上发生的事情。
【参考方案1】:
我认为你必须手动kill
:
require 'timeout'
puts 'starting process'
pid = Process.spawn('sleep 20')
begin
Timeout.timeout(5) do
puts 'waiting for the process to end'
Process.wait(pid)
puts 'process finished in time'
end
rescue Timeout::Error
puts 'process not finished in time, killing it'
Process.kill('TERM', pid)
end
【讨论】:
这与示例不同,因为你使用的是`Process.spawn. With that command, the child process *doesn't* terminate when the main process does. It also doesn't halt application execution while waiting for the subprocess to return; it runs it in parallel. But when using backticks (or
exec`),主进程等待子进程返回,如果主进程终止则杀死子进程.
呃.. OP 根本没有终止主进程。问题是timeout
引发的异常是否会终止子进程(它不会)。
你说得对,我误解了 OP 的问题。将我的投票从反对票改为赞成票。 (最初它不会让我改变它说“你的投票现在被锁定,除非这个答案被编辑”所以我做了一个快速编辑,只添加了一个空白字符,不改变任何内容)。
哈哈,然后它让我撤消我的编辑而不恢复我的投票。从某种意义上说,游戏系统的有趣方式——如果你总能像那样绕过它,“锁”就不是真实的。
谢谢。好吧,我想涵盖这种边缘投票案例会不必要地复杂。 :) 不知道他们为什么把锁放在开头......【参考方案2】:
为了正确停止生成的进程树(不仅仅是父进程),应该考虑这样的事情:
def exec_with_timeout(cmd, timeout)
pid = Process.spawn(cmd, [:err,:out] => :close, :pgroup => true)
begin
Timeout.timeout(timeout) do
Process.waitpid(pid, 0)
$?.exitstatus == 0
end
rescue Timeout::Error
Process.kill(15, -Process.getpgid(pid))
false
end
end
这还允许您跟踪进程状态
【讨论】:
杀死树很重要,执行它的代码通常应该是这个问题的答案。关于您的解决方案的两点: Process::kill 文档说 signal 应该是否定的以杀死进程组(您的代码的进程组 ID 为负数)。此外, Process::spawn 似乎不采用代码块,这使得它不太方便。不过,我认为你的方向是正确的。 我认为 Process.kill(15, -Process.getpgid(pid)) == Process.kill(-15, pid),我不记得我在哪里读到它(可能是当然错了)。这里重要的是:pgroup => true【参考方案3】:也许这将有助于其他人希望实现类似的超时功能,但需要收集 shell 命令的输出。
我已经调整了@shurikk 的方法以使用 Ruby 2.0 和来自Fork child process with timeout and capture output 的一些代码来收集输出。
def exec_with_timeout(cmd, timeout)
begin
# stdout, stderr pipes
rout, wout = IO.pipe
rerr, werr = IO.pipe
stdout, stderr = nil
pid = Process.spawn(cmd, pgroup: true, :out => wout, :err => werr)
Timeout.timeout(timeout) do
Process.waitpid(pid)
# close write ends so we can read from them
wout.close
werr.close
stdout = rout.readlines.join
stderr = rerr.readlines.join
end
rescue Timeout::Error
Process.kill(-9, pid)
Process.detach(pid)
ensure
wout.close unless wout.closed?
werr.close unless werr.closed?
# dispose the read ends of the pipes
rout.close
rerr.close
end
stdout
end
【讨论】:
我认为使用Open3.capture*
变体会更容易。我认为它会在超时后自行清理。【参考方案4】:
处理进程、信号和定时器并不容易。这就是您可能考虑委派此任务的原因:在新版本的 Linux 上使用命令 timeout
:
timeout --kill-after=5s 10s my_bash_command -c12 -o text.txt
【讨论】:
在我的系统上,语法是timeout <duration> <command>
:例如timeout 10s my_bash_command
。 --kill-after
选项特定于发送术语信号后等待的时间量。使用此选项,您仍然需要指定原始持续时间:timeout --kill-after=5s 10s my_bash_command
。
@yves: 在我的系统上,语法是 timeout timeout
不接受您系统上的任何选项?以上是关于ruby 超时和系统命令的主要内容,如果未能解决你的问题,请参考以下文章