servolux 0.8.1 → 0.9.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
data/History.txt CHANGED
@@ -1,3 +1,13 @@
1
+ == 0.9.0 / 2009-11-30
2
+
3
+ * Minor Enhancements
4
+ * Moving towards yard style documentation
5
+ * Adding tests for the Prefork class
6
+ * Bug Fixes
7
+ * Fixes for Ruby 1.9
8
+ * Ensuring the examples run and shutdown properly
9
+ * Other numerous bug fixes
10
+
1
11
  == 0.8.1 / 2009-11-12
2
12
 
3
13
  * 3 Bug Fixes
data/README.rdoc CHANGED
@@ -31,8 +31,10 @@ Servolux::Child -- adds some much needed funtionality to child processes
31
31
  created via Ruby's IO#popen method. Specifically, a timeout thread is used to
32
32
  signal the child process to die if it does not exit in a given amount of time.
33
33
 
34
+ Servolux::Prefork -- provides a pre-forking worker pool for executing tasks in
35
+ parallel using multiple processes.
34
36
 
35
- All the documentation is available online at http://codeforpeople.rubyforge.org/servolux
37
+ All the documentation is available online at http://rdoc.info/projects/TwP/servolux
36
38
 
37
39
  === INSTALL
38
40
 
@@ -22,25 +22,45 @@ require 'servolux'
22
22
  require 'beanstalk-client'
23
23
 
24
24
  module JobProcessor
25
- # Open a connection to our beanstalk queue
25
+ # Open a connection to our beanstalk queue. This method is called once just
26
+ # before entering the child run loop.
26
27
  def before_executing
27
28
  @beanstalk = Beanstalk::Pool.new(['localhost:11300'])
28
29
  end
29
30
 
30
- # Close the connection to our beanstalk queue
31
+ # Close the connection to our beanstalk queue. This method is called once
32
+ # just after the child run loop stops and just before the child exits.
31
33
  def after_executing
32
34
  @beanstalk.close
33
35
  end
34
36
 
37
+ # Close the beanstalk socket when we receive SIGHUP. This allows the execute
38
+ # thread to return processing back to the child run loop; the child run loop
39
+ # will gracefully shutdown the process.
40
+ def hup
41
+ @beanstalk.close if @job.nil?
42
+ @thread.wakeup
43
+ end
44
+
45
+ # We want to do the same thing when we receive SIGTERM.
46
+ alias :term :hup
47
+
35
48
  # Reserve a job from the beanstalk queue, and processes jobs as we receive
36
49
  # them. We have a timeout set for 2 minutes so that we can send a heartbeat
37
50
  # back to the parent process even if the beanstalk queue is empty.
51
+ #
52
+ # This method is called repeatedly by the child run loop until the child is
53
+ # killed via SIGHUP or SIGTERM or halted by the parent.
38
54
  def execute
39
- job = @beanstalk.reserve(120)
40
- if job
41
- # process job here ...
42
- job.delete
55
+ @job = nil
56
+ @job = @beanstalk.reserve(120) rescue nil
57
+ if @job
58
+ $stdout.puts "[C] #{Process.pid} processing job #{@job.inspect}"
59
+ # ... do more processing here
43
60
  end
61
+ rescue Beanstalk::TimedOut
62
+ ensure
63
+ @job.delete rescue nil if @job
44
64
  end
45
65
  end
46
66
 
@@ -60,6 +80,11 @@ pool = Servolux::Prefork.new(:timeout => 600, :module => JobProcessor)
60
80
  # Start up 7 child processes to handle jobs
61
81
  pool.start 7
62
82
 
63
- # Stop when SIGINT is received.
64
- trap('INT') { pool.stop }
83
+ # When SIGINT is received, kill all child process and then reap the child PIDs
84
+ # from the proc table.
85
+ trap('INT') {
86
+ pool.signal 'KILL'
87
+ pool.reap
88
+ }
65
89
  Process.waitall
90
+
data/examples/echo.rb CHANGED
@@ -48,5 +48,5 @@ pool = Servolux::Prefork.new {
48
48
  pool.start 3
49
49
 
50
50
  # Stop the child processes when SIGINT is received.
51
- trap('INT') { pool.stop }
51
+ trap('INT') { pool.signal 'KILL' }
52
52
  Process.waitall
@@ -0,0 +1,84 @@
1
+
2
+ require 'rubygems'
3
+ require 'servolux'
4
+ require 'beanstalk-client'
5
+ require 'logger'
6
+
7
+ # The END block is executed at the *end* of the script. It is here only
8
+ # because this is the meat of the running code, and it makes the example more
9
+ # "exemplary".
10
+ END {
11
+
12
+ # Create a new Servolux::Server and augment it with our BeanstalkWorkerPool
13
+ # methods. The run loop will be executed every 30 seconds by this server.
14
+ server = Servolux::Server.new('BeanstalkWorkerPool', :logger => Logger.new($stdout), :interval => 30)
15
+ server.extend BeanstalkWorkerPool
16
+
17
+ # Startup the server. The "before_starting" method will be called and the run
18
+ # loop will begin executing. This method will not return until a SIGINT or
19
+ # SIGTERM is sent to the server process.
20
+ server.startup
21
+
22
+ }
23
+
24
+ # The worker pool is managed as a Servolux::Server instance. This allows the
25
+ # pool to be gracefully stopped and to be monitored by the server thread. This
26
+ # monitoring involves reaping child processes that have died and reporting on
27
+ # errors raised by children. It is also possible to respawn dead child
28
+ # workers, but this should be thuroughly thought through (ha, unintentional
29
+ # alliteration) before doing so [if the CPU is thrashing, then respawning dead
30
+ # child workers will only contribute to the thrash].
31
+ module BeanstalkWorkerPool
32
+ # Before we start the server run loop, allocate our pool of child workers
33
+ # and prefork seven JobProcessors to pull work from the beanstalk queue.
34
+ def before_starting
35
+ @pool = Servolux::Prefork.new(:module => JobProcessor)
36
+ @pool.start 7
37
+ end
38
+
39
+ # This run loop will be called at a fixed interval by the server thread. If
40
+ # the pool has any child processes that have died or restarted, then the
41
+ # expired PIDs are reaed from the proc table. If any workers in the pool
42
+ # have reported an error, then display those errors on STDOUT; these are
43
+ # errors raised from the child process that caused the child to terminate.
44
+ def run
45
+ @pool.reap
46
+ @pool.each_worker { |worker|
47
+ $stdout.puts "[P] #{Process.pid} child error: #{worker.error.inspect}" if worker.error
48
+ }
49
+ end
50
+
51
+ # After the server run loop exits, stop all children in the pool of workers.
52
+ def after_stopping
53
+ @pool.stop
54
+ end
55
+ end
56
+
57
+ # See the beanstalk.rb example for an explanation of the JobProcessor
58
+ module JobProcessor
59
+ def before_executing
60
+ @beanstalk = Beanstalk::Pool.new(['localhost:11300'])
61
+ end
62
+
63
+ def after_executing
64
+ @beanstalk.close
65
+ end
66
+
67
+ def hup
68
+ @beanstalk.close if @job.nil?
69
+ @thread.wakeup
70
+ end
71
+ alias :term :hup
72
+
73
+ def execute
74
+ @job = nil
75
+ @job = @beanstalk.reserve(120) rescue nil
76
+ if @job
77
+ $stdout.puts "[C] #{Process.pid} processing job #{@job.inspect}"
78
+ end
79
+ rescue Beanstalk::TimedOut
80
+ ensure
81
+ @job.delete rescue nil if @job
82
+ end
83
+ end
84
+
@@ -49,23 +49,22 @@ class Servolux::Child
49
49
  # Create a new Child that will execute and manage the +command+ string as
50
50
  # a child process.
51
51
  #
52
- # ==== Options
53
- # * command <String>
54
- # The command that will be executed via IO#popen.
52
+ # @option opts [String] :command
53
+ # The command that will be executed via IO#popen.
55
54
  #
56
- # * timeout <Numeric>
57
- # The number of seconds to wait before terminating the child process.
58
- # No action is taken if the child process exits normally before the
59
- # timeout expires.
55
+ # @option opts [Numeric] :timeout (nil)
56
+ # The number of seconds to wait before terminating the child process.
57
+ # No action is taken if the child process exits normally before the
58
+ # timeout expires.
60
59
  #
61
- # * signals <Array>
62
- # A list of signals that will be sent to the child process when the
63
- # timeout expires. The signals increase in severity with SIGKILL being
64
- # the signal of last resort.
60
+ # @option opts [Array<String, Integer>] :signals (['TERM', 'QUIT', 'KILL'])
61
+ # A list of signals that will be sent to the child process when the
62
+ # timeout expires. The signals increase in severity with SIGKILL being
63
+ # the signal of last resort.
65
64
  #
66
- # * suspend <Numeric>
67
- # The number of seconds to wait for the child process to respond to
68
- # a signal before trying the next one in the list.
65
+ # @option opts [Numeric] :suspend (4)
66
+ # The number of seconds to wait for the child process to respond to a
67
+ # signal before trying the next one in the list.
69
68
  #
70
69
  def initialize( opts = {} )
71
70
  @command = opts[:command]
@@ -85,6 +84,14 @@ class Servolux::Child
85
84
  # Ruby with a pipe. Ruby’s end of the pipe will be passed as a parameter
86
85
  # to the block. In this case the value of the block is returned.
87
86
  #
87
+ # @param [String] mode The mode flag used to open the child process via
88
+ # IO#popen.
89
+ # @yield [IO] Execute the block of call passing in the communication pipe
90
+ # with the child process.
91
+ # @yieldreturn Returns the result of the block.
92
+ # @return [IO] The communication pipe with the child process or the return
93
+ # value from the block if one was given.
94
+ #
88
95
  def start( mode = 'r', &block )
89
96
  start_timeout_thread if @timeout
90
97
 
@@ -104,6 +111,8 @@ class Servolux::Child
104
111
  # the stored child PID is set to +nil+. The +start+ method can be safely
105
112
  # called again.
106
113
  #
114
+ # @return self
115
+ #
107
116
  def stop
108
117
  unless @thread.nil?
109
118
  t, @thread = @thread, nil
@@ -121,6 +130,12 @@ class Servolux::Child
121
130
  # global variable $? is set to a Process::Status object containing
122
131
  # information on the child process.
123
132
  #
133
+ # @param [Integer] flags Bit flags that will be passed to the system level
134
+ # wait call. See the Ruby core documentation for Process#wait for more
135
+ # information on these flags.
136
+ # @return [Integer, nil] The exit status of the child process or +nil+ if
137
+ # the child process is not running.
138
+ #
124
139
  def wait( flags = 0 )
125
140
  return if @io.nil?
126
141
  Process.wait(@pid, flags)
@@ -131,6 +146,8 @@ class Servolux::Child
131
146
  # Returns +true+ if the child process is alive. Returns +nil+ if the child
132
147
  # process has not been started.
133
148
  #
149
+ # @return [Boolean]
150
+ #
134
151
  def alive?
135
152
  return if @io.nil?
136
153
  Process.kill(0, @pid)
@@ -141,6 +158,8 @@ class Servolux::Child
141
158
 
142
159
  # Returns +true+ if the child process was killed by the timeout thread.
143
160
  #
161
+ # @return [Boolean]
162
+ #
144
163
  def timed_out?
145
164
  @timed_out
146
165
  end
@@ -197,6 +216,6 @@ class Servolux::Child
197
216
  }
198
217
  end
199
218
 
200
- end # class Servolux::Child
219
+ end
201
220
 
202
221
  # EOF
@@ -1,4 +1,3 @@
1
-
2
1
  # == Synopsis
3
2
  # The Daemon takes care of the work of creating and managing daemon
4
3
  # processes from Ruby.
@@ -88,60 +87,56 @@ class Servolux::Daemon
88
87
  # Create a new Daemon that will manage the +startup_command+ as a deamon
89
88
  # process.
90
89
  #
91
- # ==== Required
92
- # * name <String>
93
- # The name of the daemon process. This name will appear in log
94
- # messages.
95
- #
96
- # * logger <Logger>
97
- # The Logger instance used to output messages.
90
+ # @option opts [String] :name
91
+ # The name of the daemon process. This name will appear in log messages.
92
+ # [required]
93
+ #
94
+ # @option opts [Logger] :logger
95
+ # The Logger instance used to output messages. [required]
98
96
  #
99
- # * pid_file <String>
100
- # Location of the PID file. This is used to determine if the daemon
101
- # process is running, and to send signals to the daemon process.
97
+ # @option opts [String] :pid_file
98
+ # Location of the PID file. This is used to determine if the daemon
99
+ # process is running, and to send signals to the daemon process.
100
+ # [required]
102
101
  #
103
- # * startup_command
104
- # Assign the startup command. This can be either a String, an Array of
105
- # strings, a Proc, a bound Method, or a Servolux::Server instance.
106
- # Different calling semantics are used for each type of command. See
107
- # the setter method for more details.
102
+ # @option opts [String, Array<String>, Proc, Method, Servolux::Server] :startup_command
103
+ # Assign the startup command. Different calling semantics are used for
104
+ # each type of command. See the {Daemon#startup_command= startup_command}
105
+ # method for more details. [required]
108
106
  #
109
- # ==== Options
107
+ # @option opts [Numeric] :timeout (30)
108
+ # The time (in seconds) to wait for the daemon process to either startup
109
+ # or shutdown. An error is raised when this timeout is exceeded.
110
110
  #
111
- # * timeout <Numeric>
112
- # The time (in seconds) to wait for the daemon process to either
113
- # startup or shutdown. An error is raised when this timeout is
114
- # exceeded. The default is 30 seconds.
111
+ # @option opts [Boolen] :nochdir (false)
112
+ # When set to true this flag directs the daemon process to keep the
113
+ # current working directory. By default, the process of daemonizing will
114
+ # cause the current working directory to be changed to the root folder
115
+ # (thus preventing the daemon process from holding onto the directory
116
+ # inode).
115
117
  #
116
- # * nochdir <Boolean>
117
- # When set to true this flag directs the daemon process to keep the
118
- # current working directory. By default, the process of daemonizing
119
- # will cause the current working directory to be changed to the root
120
- # folder (thus preventing the daemon process from holding onto the
121
- # directory inode). The default is false.
118
+ # @option opts [Boolen] :noclose (false)
119
+ # When set to true this flag keeps the standard input/output streams from
120
+ # being reopend to /dev/null when the deamon process is created. Reopening
121
+ # the standard input/output streams frees the file descriptors which are
122
+ # still being used by the parent process. This prevents zombie processes.
122
123
  #
123
- # * noclose <Boolean>
124
- # When set to true this flag keeps the standard input/output streams
125
- # from being reopend to /dev/null when the deamon process is created.
126
- # Reopening the standard input/output streams frees the file
127
- # descriptors which are still being used by the parent process. This
128
- # prevents zombie processes. The default is false.
124
+ # @option opts [String, Array<String>, Proc, Method, Servolux::Server] :shutdown_command (nil)
125
+ # Assign the startup command. Different calling semantics are used for
126
+ # each type of command.
129
127
  #
130
- # * shutdown_command
131
- # Assign the startup command. This can be either a String, an Array of
132
- # strings, a Proc, a bound Method, or a Servolux::Server instance.
133
- # Different calling semantics are used for each type of command.
128
+ # @option opts [String] :log_file (nil)
129
+ # This log file will be monitored to determine if the daemon process has
130
+ # sucessfully started.
134
131
  #
135
- # * log_file <String>
136
- # This log file will be monitored to determine if the daemon process
137
- # has sucessfully started.
132
+ # @option opts [String, Regexp] :look_for (nil)
133
+ # This can be either a String or a Regexp. It defines a phrase to search
134
+ # for in the log_file. When the daemon process is started, the parent
135
+ # process will not return until this phrase is found in the log file. This
136
+ # is a useful check for determining if the daemon process is fully
137
+ # started.
138
138
  #
139
- # * look_for
140
- # This can be either a String or a Regexp. It defines a phrase to
141
- # search for in the log_file. When the daemon process is started, the
142
- # parent process will not return until this phrase is found in the log
143
- # file. This is a useful check for determining if the daemon process
144
- # is fully started. The default is nil.
139
+ # @yield [self] Block used to configure the daemon instance
145
140
  #
146
141
  def initialize( opts = {} )
147
142
  self.server = opts[:server] || opts[:startup_command]
@@ -173,16 +168,19 @@ class Servolux::Daemon
173
168
  #
174
169
  # If the startup command is a String or an Array of strings, then
175
170
  # Kernel#exec is used to run the command. Therefore, the string (or array)
176
- # should be system level command that is either fully qualified or can be
171
+ # should be a system command that is either fully qualified or can be
177
172
  # found on the current environment path.
178
173
  #
179
174
  # If the startup command is a Proc or a bound Method then it is invoked
180
175
  # using the +call+ method on the object. No arguments are passed to the
181
176
  # +call+ invocoation.
182
177
  #
183
- # Lastly, if the startup command is a Servolux::Server then it's +startup+
178
+ # Lastly, if the startup command is a Servolux::Server then its +startup+
184
179
  # method is called.
185
180
  #
181
+ # @param [String, Array<String>, Proc, Method, Servolux::Server] val The startup
182
+ # command to invoke when daemonizing.
183
+ #
186
184
  def startup_command=( val )
187
185
  @startup_command = val
188
186
  return unless val.is_a?(::Servolux::Server)
@@ -198,6 +196,8 @@ class Servolux::Daemon
198
196
  # Assign the log file name. This log file will be monitored to determine
199
197
  # if the daemon process is running.
200
198
  #
199
+ # @param [String] filename The name of the log file to monitor
200
+ #
201
201
  def log_file=( filename )
202
202
  return if filename.nil?
203
203
  @logfile_reader ||= LogfileReader.new
@@ -212,6 +212,8 @@ class Servolux::Daemon
212
212
  # If no phrase is given to look for, then the log file will simply be
213
213
  # watched for a change in size and a modified timestamp.
214
214
  #
215
+ # @param [String, Regexp] val The phrase in the log file to search for
216
+ #
215
217
  def look_for=( val )
216
218
  return if val.nil?
217
219
  @logfile_reader ||= LogfileReader.new
@@ -220,6 +222,8 @@ class Servolux::Daemon
220
222
 
221
223
  # Start the daemon process.
222
224
  #
225
+ # @return [Daemon] self
226
+ #
223
227
  def startup
224
228
  raise Error, "Fork is not supported in this Ruby environment." unless ::Servolux.fork?
225
229
  return if alive?
@@ -234,12 +238,15 @@ class Servolux::Daemon
234
238
  }
235
239
 
236
240
  @piper.child { run_startup_command }
241
+ self
237
242
  end
238
243
 
239
244
  # Stop the daemon process. If a shutdown command has been defined, it will
240
245
  # be called to stop the daemon process. Otherwise, SIGINT will be sent to
241
246
  # the daemon process to terminate it.
242
247
  #
248
+ # @return [Daemon] self
249
+ #
243
250
  def shutdown
244
251
  return unless alive?
245
252
 
@@ -260,6 +267,8 @@ class Servolux::Daemon
260
267
  # +false+ if this is not the case. The status of the process is determined
261
268
  # by sending a signal to the process identified by the +pid_file+.
262
269
  #
270
+ # @return [Boolean]
271
+ #
263
272
  def alive?
264
273
  pid = retrieve_pid
265
274
  Process.kill(0, pid)
@@ -276,11 +285,16 @@ class Servolux::Daemon
276
285
  # default signal to send is 'INT' (2). The signal can be given either as a
277
286
  # string or a signal number.
278
287
  #
288
+ # @param [String, Integer] signal The kill signal to send to the daemon
289
+ # process
290
+ # @return [Daemon] self
291
+ #
279
292
  def kill( signal = 'INT' )
280
293
  signal = Signal.list.invert[signal] if signal.is_a?(Integer)
281
294
  pid = retrieve_pid
282
295
  logger.info "Killing PID #{pid} with #{signal}"
283
296
  Process.kill(signal, pid)
297
+ self
284
298
  rescue Errno::EINVAL
285
299
  logger.error "Failed to kill PID #{pid} with #{signal}: " \
286
300
  "'#{signal}' is an invalid or unsupported signal number."
@@ -368,7 +382,7 @@ class Servolux::Daemon
368
382
 
369
383
  def wait_for_shutdown
370
384
  logger.debug "Waiting for #{name.inspect} to shutdown."
371
- return if wait_for { !alive? }
385
+ return self if wait_for { !alive? }
372
386
  raise Timeout, "#{name.inspect} failed to shutdown in a timely fashion. " \
373
387
  "The timeout is set at #{timeout} seconds."
374
388
  end
@@ -390,6 +404,7 @@ class Servolux::Daemon
390
404
  end
391
405
 
392
406
  # :stopdoc:
407
+ # @private
393
408
  class LogfileReader
394
409
  attr_accessor :filename
395
410
  attr_reader :look_for
@@ -430,9 +445,8 @@ class Servolux::Daemon
430
445
  ensure
431
446
  @stat = s
432
447
  end
433
- end # class LogfileReader
448
+ end
434
449
  # :startdoc:
435
450
 
436
- end # class Servolux::Daemon
451
+ end
437
452
 
438
- # EOF