Class: Spawner
- Inherits:
-
Object
- Object
- Spawner
- Defined in:
- lib/spawner.rb
Overview
Synopsis
A class for spawning child processes and ensuring those children continue running.
Details
When a spawner is created it is given the command to run in a child process. This child process has stdin
, stdout
, and stderr
redirected to /dev/null
(this works even on Windows). When the child dies for any reason, the spawner will restart a new child process in the exact same manner as the original.
Defined Under Namespace
Classes: Main
Constant Summary collapse
- VERSION =
'1.0.2'
Class Attribute Summary collapse
-
.dev_null ⇒ Object
readonly
Returns the value of attribute dev_null.
-
.ruby ⇒ Object
readonly
Returns the value of attribute ruby.
Instance Attribute Summary collapse
-
#pause ⇒ Object
Returns the value of attribute pause.
-
#spawn ⇒ Object
def initialize.
Class Method Summary collapse
- .finalizer(cids) ⇒ Object
-
.version ⇒ Object
finalizer.
Instance Method Summary collapse
-
#initialize(*args) ⇒ Spawner
constructor
call-seq: Spawner.new( command, *args, opts = {} ).
-
#join(limit = nil) ⇒ Object
call-seq: join( timeout = nil ) => spawner or nil.
-
#restart(timeout = 5) ⇒ Object
call-seq: restart( timeout = 5 ).
-
#running? ⇒ Boolean
call-seq: running?.
-
#start ⇒ Object
call-seq: start => self.
-
#stop(timeout = 5) ⇒ Object
call-seq: stop( timeout = 5 ) => self.
Constructor Details
#initialize(*args) ⇒ Spawner
call-seq:
Spawner.new( command, *args, opts = {} )
Creates a new spawner that will execute the given external command in a sub-process. The calling semantics of Kernel::exec
are used to execute the command. Any number of optional args can be passed to the command.
Available options:
:spawn => the number of child processes to spawn
:pause => wait time (in seconds) before respawning after termination
:ruby => the Ruby interpreter to use when spawning children
:env => a hash for the child process environment
:cwd => the current working directory to use for the child process
:stdin => stdin child processes will read from
:stdout => stdout child processes will write to
:stderr => stderr child processes will write to
The :env
option is used to add environemnt variables to child processes when they are spawned.
Note: all spawned child processes will use the same stdin, stdout, and stderr if they are given in the options. Otherwise they all default to /dev/null
on *NIX and NUL:
on Windows.
76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 |
# File 'lib/spawner.rb', line 76 def initialize( *args ) config = { :ruby => self.class.ruby, :spawn => 1, :pause => 0, :stdin => self.class.dev_null, :stdout => self.class.dev_null, :stderr => self.class.dev_null } config.merge! args.pop if Hash === args.last config[:argv] = args raise ArgumentError, 'wrong number of arguments' if args.empty? @stop = true @cids = [] @group = ThreadGroup.new @spawn = config.delete(:spawn) @pause = config.delete(:pause) @ruby = config.delete(:ruby) @tmp = child_program(config) class << @cids # call-seq: # sync {block} # # Executes the given block in a synchronized fashion -- i.e. only a # single thread can execute at a time. Uses Mutex under the hood. # def sync(&b) @mutex ||= Mutex.new @mutex.synchronize(&b) end # call-seq: # kill( signal, num ) => number killed # kill( signal, :all ) => number killed # # Send the _signal_ to a given _num_ of child processes or all child # processes if <code>:all</code> is given instead of a number. Returns # the number of child processes killed. # def kill( signal, arg ) return if empty? ary = sync do case arg when :all; self.dup when Integer; self.slice(0,arg) else raise ArgumentError end end ary.each do |cid| begin Process.kill(signal, cid) rescue SystemCallError sync {delete cid} end end ary.length end # def kill end # class << @cids end |
Class Attribute Details
.dev_null ⇒ Object (readonly)
Returns the value of attribute dev_null.
35 36 37 |
# File 'lib/spawner.rb', line 35 def dev_null @dev_null end |
.ruby ⇒ Object (readonly)
Returns the value of attribute ruby.
34 35 36 |
# File 'lib/spawner.rb', line 34 def ruby @ruby end |
Instance Attribute Details
#pause ⇒ Object
Returns the value of attribute pause.
144 145 146 |
# File 'lib/spawner.rb', line 144 def pause @pause end |
#spawn ⇒ Object
def initialize
143 144 145 |
# File 'lib/spawner.rb', line 143 def spawn @spawn end |
Class Method Details
.finalizer(cids) ⇒ Object
37 38 39 40 41 42 43 |
# File 'lib/spawner.rb', line 37 def finalizer( cids ) pid = $$ lambda do break unless pid == $$ cids.kill 'TERM', :all end # lambda end |
Instance Method Details
#join(limit = nil) ⇒ Object
call-seq:
join( timeout = nil ) => spawner or nil
The calling thread will suspend execution until all child processes have been stopped. Does not return until all spawner threads have exited (the child processes have been stopped) or until _timeout seconds have passed. If the timeout expires nil
will be returned; otherwise the spawner is returned.
239 240 241 242 243 244 245 246 |
# File 'lib/spawner.rb', line 239 def join( limit = nil ) loop do t = @group.list.first break if t.nil? return nil unless t.join(limit) end self end |
#restart(timeout = 5) ⇒ Object
call-seq:
restart( timeout = 5 )
215 216 217 218 |
# File 'lib/spawner.rb', line 215 def restart( timeout = 5 ) stop( timeout ) start end |
#running? ⇒ Boolean
call-seq:
running?
Returns true
if the spawner is currently running; returns false
otherwise.
226 227 228 |
# File 'lib/spawner.rb', line 226 def running? !@stop end |
#start ⇒ Object
call-seq:
start => self
Spawn the sub-processes.
169 170 171 172 173 174 175 176 177 178 |
# File 'lib/spawner.rb', line 169 def start return self if running? @stop = false @cleanup = Spawner.finalizer(@cids) ObjectSpace.define_finalizer(self, @cleanup) @spawn.times {_spawn} self end |
#stop(timeout = 5) ⇒ Object
call-seq:
stop( timeout = 5 ) => self
Stop any spawned sub-processes.
185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 |
# File 'lib/spawner.rb', line 185 def stop( timeout = 5 ) return self unless running? @stop = true @cleanup.call ObjectSpace.undefine_finalizer(self) # the cleanup call sends SIGTERM to all the child processes # however, some might still be hanging around, so we are going to wait # for a timeout interval and then send a SIGKILL to any remaining child # processes nap_time = 0.05 * timeout # sleep for 5% of the timeout interval timeout = Time.now + timeout until @cids.empty? sleep nap_time unless Time.now < timeout @cids.kill 'KILL', :all @cids.clear @group.list.each {|t| t.kill} break end end self end |