2

我正在使用open4gem 将系统调用包装到可能长时间运行的第三方命令行工具。该工具有时可能会失败,使两个进程保持忙碌,并部分阻塞管道,因为父进程是工作脚本池的一部分(服务于 Beanstalk 队列)。从系统外部,我可以根据正在处理的数据模型以编程方式识别卡住的工作脚本及其进程 ID。在Open4.open4块内,我可以识别子进程 ID。

我想设置Open4块,以便当我向父工作进程发送 SIGTERM 时,它会在 SIGTERM 上转发给子进程。另外,如果子进程在短暂等待后仍然未能退出,我想向子进程发送 SIGKILL。在这两种情况下,我都希望父进程正常响应它发送的 SIGTERM。

这一切都已完成,因此我可以在客户服务应用程序中显示“停止”按钮,这样非技术团队成员就有了一个工具来管理他们摆脱队列阻塞的情况。

我在 SO 中发现了一些相关问题 - 例如如何在父进程退出后使子进程死亡?- 但从 Ruby 应用程序代码中,答案对我来说并不是真正有用的。

这是我在 Mac 上测试过的 Ruby 中的当前实现:

测试不会总是响应 SIGTERM 的“坏”进程的替身:

# Writing to a log file shows whether or not a detached process continues
# once the parent has closed IO to it.
$f = open( 'log.txt', 'w' );

def say m
  begin
    $f.puts m
    $f.flush
    $stderr.puts m
  rescue Exception => e
    # When the parent process closes, we get
    # #<Errno::EPIPE: Broken pipe - <STDERR>> in this
    # test, but with a stuck child process, this is not 
    # guaranteed to happen or cause the child to exit.
    $f.puts e.inspect
    $f.flush
  end
end

Signal.trap( "TERM" ) { say "Received and ignored TERM" }

# Messages get logged, and sleep allows test of manual interrupts
say "Hello"
sleep 3
say "Foo Bar Baz"
sleep 3
say "Doo Be Doo"
sleep 3
say "Goodbye" 
$f.close

测试 Open4 块(“worker”测试脚本的一部分):

Open4.open4(@command) do | pid, stdin, stdout, stderr |
  begin
    stderr.each { |l|
      puts "[#{pid}] STDERR: #{l}" if l
    }
  rescue SignalException => e
    puts "[#{$$}] Received signal (#{e.signo} #{e.signm}) in Open4 block"

    # Forward a SIGTERM to child, upgrade to SIGKILL if it doesn't work
    if e.signo == 15
      begin
        puts "[#{$$}] Sending TERM to child process"
        Process.kill( 'TERM', pid )
        timeout(3.0) { Process.waitpid( pid ) }
      rescue Timeout::Error
        puts "[#{$$}] Sending KILL to child process"
        Process.kill( 'KILL', pid )
      end
    end

    raise e
  end
end

如果我启动它并运行例如典型输出kill -15 16854

[16855] STDERR: Hello
[16854] Received signal (15 SIGTERM) in Open4 block
[16854] Sending TERM to child process
[16854] Sending KILL to child process

同一测试的日志文件内容:

Hello
Received and ignored TERM
Foo Bar Baz

该代码在 IMO 有点笨拙,尽管它似乎可以按我的意愿工作。我的问题:

  1. 上述尝试是否可行,或者在我需要它的用例中存在致命缺陷?
  2. 我是否错过了使用现有Open4和核心 Ruby 方法做同样事情的更简洁的方法?
4

0 回答 0