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 +10 -0
- data/README.rdoc +3 -1
- data/examples/beanstalk.rb +33 -8
- data/examples/echo.rb +1 -1
- data/examples/server_beanstalk.rb +84 -0
- data/lib/servolux/child.rb +34 -15
- data/lib/servolux/daemon.rb +66 -52
- data/lib/servolux/piper.rb +71 -34
- data/lib/servolux/prefork.rb +164 -56
- data/lib/servolux/server.rb +9 -4
- data/lib/servolux/threaded.rb +12 -10
- data/lib/servolux.rb +11 -3
- data/spec/piper_spec.rb +1 -1
- data/spec/prefork_spec.rb +125 -0
- data/spec/server_spec.rb +12 -5
- data/spec/threaded_spec.rb +17 -2
- metadata +8 -8
- data/a.rb +0 -34
- data/b.rb +0 -17
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://
|
37
|
+
All the documentation is available online at http://rdoc.info/projects/TwP/servolux
|
36
38
|
|
37
39
|
=== INSTALL
|
38
40
|
|
data/examples/beanstalk.rb
CHANGED
@@ -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 =
|
40
|
-
|
41
|
-
|
42
|
-
job.
|
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
|
-
#
|
64
|
-
|
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
@@ -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
|
+
|
data/lib/servolux/child.rb
CHANGED
@@ -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
|
-
#
|
53
|
-
#
|
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
|
-
#
|
57
|
-
#
|
58
|
-
#
|
59
|
-
#
|
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
|
-
#
|
62
|
-
#
|
63
|
-
#
|
64
|
-
#
|
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
|
-
#
|
67
|
-
#
|
68
|
-
#
|
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
|
219
|
+
end
|
201
220
|
|
202
221
|
# EOF
|
data/lib/servolux/daemon.rb
CHANGED
@@ -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
|
-
#
|
92
|
-
#
|
93
|
-
#
|
94
|
-
#
|
95
|
-
#
|
96
|
-
#
|
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
|
-
#
|
100
|
-
#
|
101
|
-
#
|
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
|
-
#
|
104
|
-
#
|
105
|
-
#
|
106
|
-
#
|
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
|
-
#
|
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
|
-
#
|
112
|
-
#
|
113
|
-
#
|
114
|
-
#
|
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
|
-
#
|
117
|
-
#
|
118
|
-
#
|
119
|
-
#
|
120
|
-
#
|
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
|
-
#
|
124
|
-
#
|
125
|
-
#
|
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
|
-
#
|
131
|
-
#
|
132
|
-
#
|
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
|
-
#
|
136
|
-
#
|
137
|
-
#
|
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
|
-
#
|
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
|
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
|
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
|
448
|
+
end
|
434
449
|
# :startdoc:
|
435
450
|
|
436
|
-
end
|
451
|
+
end
|
437
452
|
|
438
|
-
# EOF
|