From 5ef941251cec09d67487a0d15eea40e479e5ea07 Mon Sep 17 00:00:00 2001 From: Eric Mueller Date: Wed, 1 Jul 2015 16:23:21 -0400 Subject: [PATCH] implement timeout option with SIGKILL fixes #1 --- README.md | 4 +++- lib/iso_latte.rb | 21 ++++++++++++++++++++- 2 files changed, 23 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index e0c5f5b..5f40f88 100644 --- a/README.md +++ b/README.md @@ -36,7 +36,7 @@ IsoLatte.fork( ### Options -* `:stderr` - A path to write the subprocess' stderr stream into. Defaults to '/dev/null', +* `stderr` - A path to write the subprocess' stderr stream into. Defaults to '/dev/null', supplying `nil` lets the subprocess continue writing to the parent's stderr stream. * `success` - a callable to execute if the subprocess completes successfully. * `fault` - a callable to execute if the subprocess receives a SIGABRT (segfault). @@ -45,6 +45,8 @@ IsoLatte.fork( Receives the exit status value as its argument. * `finish` - a callable to execute when the subprocess terminates in any way. It receives a boolean 'success' value and an exit status as its arguments. +* `timeout` - a number of seconds to wait - if the process has not terminated by then, + the parent will kill it by issuing a SIGKILL signal (triggering the kill callback) ## Roadmap diff --git a/lib/iso_latte.rb b/lib/iso_latte.rb index e0cb294..21fc3a1 100644 --- a/lib/iso_latte.rb +++ b/lib/iso_latte.rb @@ -1,4 +1,5 @@ require "ostruct" +require "timeout" module IsoLatte NO_EXIT = 122 @@ -16,6 +17,7 @@ module IsoLatte # fault: a callable to execute if the subprocess segfaults, core dumps, etc. # exit: a callable to execute if the subprocess voluntarily exits with nonzero. # receives the exit status value as its argument. + # timeout: after this many seconds, the parent should send a SIGKILL to the child. # # It is allowable to Isolatte.fork from inside an IsoLatte.fork block (reentrant) # @@ -59,7 +61,17 @@ def self.fork(options = nil, &block) write_ex.close - pid, rc = Process.wait2(child_pid) + pid, rc = + begin + if opts.timeout + Timeout.timeout(opts.timeout) { Process.wait2(child_pid) } + else + Process.wait2(child_pid) + end + rescue Timeout::Error + kill_child(child_pid) + end + fail(Error, "Wrong child's exit received!") unless pid == child_pid if rc.exited? && rc.exitstatus == EXCEPTION_RAISED @@ -91,5 +103,12 @@ def self.fork(options = nil, &block) rc end + def self.kill_child(pid) + Process.kill("KILL", pid) + Process.wait2(pid) + rescue Errno::ESRCH + # Save us from the race condition where it exited just as we decided to kill it. + end + class Error < StandardError; end end