class Subexec
# Subexec
-
by Peter Kieltyka
## Description
Subexec
is a simple library that spawns an external command with an optional timeout parameter. It relies on Ruby 1.9's Process.spawn method. Also, it works with synchronous and asynchronous code.
Useful for libraries that are Ruby wrappers for CLI's. For example, resizing images with ImageMagick's mogrify command sometimes stalls and never returns control back to the original process. Subexec
executes mogrify and preempts if it gets lost.
## Usage
# Print hello sub = Subexec.run
“echo 'hello' && sleep 3”, :timeout => 5 puts sub.output # returns: hello puts sub.exitstatus # returns: 0
# Timeout process after a second sub = Subexec.run
“echo 'hello' && sleep 3”, :timeout => 1 puts sub.output # returns: puts sub.exitstatus # returns:
Constants
- VERSION
Attributes
Public Class Methods
# File lib/subexec.rb, line 45 def initialize(command, options={}) self.command = command self.lang = options[:lang] || "C" self.timeout = options[:timeout] || -1 # default is to never timeout self.log_file = options[:log_file] self.exitstatus = 0 end
# File lib/subexec.rb, line 39 def self.run(command, options={}) sub = new(command, options) sub.run! sub end
Public Instance Methods
# File lib/subexec.rb, line 53 def run! if RUBY_VERSION >= '1.9' && RUBY_ENGINE != 'jruby' spawn else exec end end
Private Instance Methods
# File lib/subexec.rb, line 116 def exec if !(RUBY_PLATFORM =~ /win32|mswin|mingw/).nil? self.output = `set LANG=#{lang} && #{command} 2>&1` else self.output = `LANG=#{lang} && export LANG && #{command} 2>&1` end self.exitstatus = $?.exitstatus end
# File lib/subexec.rb, line 64 def spawn # TODO: weak implementation for log_file support. # Ideally, the data would be piped through to both descriptors r, w = IO.pipe log_to_file = !log_file.nil? log_opts = log_to_file ? {[:out, :err] => [log_file, 'a']} : {STDERR=>w, STDOUT=>w} self.pid = Process.spawn({'LANG' => self.lang}, command, log_opts) w.close @timer = Time.now + timeout timed_out = false self.output = '' append_to_output = Proc.new do self.output << r.readlines.join('') unless log_to_file end loop do ret = begin Process.waitpid(pid, Process::WUNTRACED|Process::WNOHANG) rescue Errno::ECHILD break end break if ret == pid append_to_output.call if timeout > 0 && Time.now > @timer timed_out = true break end sleep 0.01 end if timed_out # The subprocess timed out -- kill it Process.kill(9, pid) rescue Errno::ESRCH self.exitstatus = nil else # The subprocess exited on its own self.exitstatus = $?.exitstatus append_to_output.call end r.close self end