Class: Subexec

Inherits:
Object
  • Object
show all
Defined in:
lib/subexec.rb

Overview

Subexec

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 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:

Instance Attribute Summary collapse

Class Method Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(command, options = {}) ⇒ Subexec

Returns a new instance of Subexec.



41
42
43
44
45
# File 'lib/subexec.rb', line 41

def initialize(command, options={})
  self.command  = command
  self.timeout  = options[:timeout] || -1 # default is to never timeout
  run!
end

Instance Attribute Details

#commandObject

Returns the value of attribute command.



31
32
33
# File 'lib/subexec.rb', line 31

def command
  @command
end

#exitstatusObject

Returns the value of attribute exitstatus.



35
36
37
# File 'lib/subexec.rb', line 35

def exitstatus
  @exitstatus
end

#outputObject

Returns the value of attribute output.



34
35
36
# File 'lib/subexec.rb', line 34

def output
  @output
end

#pidObject

Returns the value of attribute pid.



30
31
32
# File 'lib/subexec.rb', line 30

def pid
  @pid
end

#timeoutObject

Returns the value of attribute timeout.



32
33
34
# File 'lib/subexec.rb', line 32

def timeout
  @timeout
end

#timerObject

Returns the value of attribute timer.



33
34
35
# File 'lib/subexec.rb', line 33

def timer
  @timer
end

Class Method Details

.run(command, options = {}) ⇒ Object



37
38
39
# File 'lib/subexec.rb', line 37

def self.run(command, options={})
  new(command, options)
end

Instance Method Details

#run!Object



47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
# File 'lib/subexec.rb', line 47

def run!
  r, w = IO.pipe
  self.pid = Process.spawn(command, STDERR=>STDOUT, STDOUT=>w)
  w.close
  
  self.timer = Time.now + timeout
  timed_out = false

  loop do
    begin
      flags = (timeout > 0 ? Process::WUNTRACED|Process::WNOHANG : 0)
      ret = Process.waitpid(pid, flags)
    rescue Errno::ECHILD
      break
    end

    break if ret == pid
    sleep 0.001
    if Time.now > timer
      timed_out = true
      break
    end
  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
    self.output = r.readlines.join("")
  end
  r.close
  
  self
end