servolux 0.8.1 → 0.9.0

Sign up to get free protection for your applications and to get access to all the features.
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