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 超时和系统命令的主要内容,如果未能解决你的问题,请参考以下文章

ruby 证明在ruby进程中执行的系统命令将此进程作为父进程并具有其所有环境变量

强制macos系统降级ruby系统

如何用fir.im 命令行工具 打包上传

ruby安装卸载

在 Java 中实现“系统”命令

Mac 系统软件包管理工具Brew