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/lib/servolux/piper.rb
CHANGED
@@ -49,12 +49,9 @@ require 'socket'
|
|
49
49
|
class Servolux::Piper
|
50
50
|
|
51
51
|
# :stopdoc:
|
52
|
-
SIZEOF_INT = [42].pack('I').size
|
52
|
+
SIZEOF_INT = [42].pack('I').size # @private
|
53
53
|
# :startdoc:
|
54
54
|
|
55
|
-
# call-seq:
|
56
|
-
# Piper.daemon( nochdir = false, noclose = false )
|
57
|
-
#
|
58
55
|
# Creates a new Piper with the child process configured as a daemon. The
|
59
56
|
# +pid+ method of the piper returns the PID of the daemon process.
|
60
57
|
#
|
@@ -64,6 +61,10 @@ class Servolux::Piper
|
|
64
61
|
# _nochdir_ and _noclose_ flags to true. The first will keep the current
|
65
62
|
# working directory; the second will keep stdout/stderr/stdin open.
|
66
63
|
#
|
64
|
+
# @param [Boolean] nochdir Do not change working directories
|
65
|
+
# @param [Boolean] noclose Do not close stdin, stdout, and stderr
|
66
|
+
# @return [Piper]
|
67
|
+
#
|
67
68
|
def self.daemon( nochdir = false, noclose = false )
|
68
69
|
piper = self.new(:timeout => 1)
|
69
70
|
piper.parent {
|
@@ -92,25 +93,25 @@ class Servolux::Piper
|
|
92
93
|
# The timeout in seconds to wait for puts / gets commands.
|
93
94
|
attr_accessor :timeout
|
94
95
|
|
95
|
-
#
|
96
|
-
#
|
97
|
-
#
|
98
|
-
#
|
99
|
-
#
|
100
|
-
# parent, and write-only from the child). The supported modes are as
|
101
|
-
# follows:
|
96
|
+
# @overload Piper.new( mode = 'r', opts = {} )
|
97
|
+
# Creates a new Piper instance with the communication pipe configured
|
98
|
+
# using the provided _mode_. The default mode is read-only (from the
|
99
|
+
# parent, and write-only from the child). The supported modes are as
|
100
|
+
# follows:
|
102
101
|
#
|
103
|
-
#
|
104
|
-
#
|
105
|
-
#
|
106
|
-
#
|
107
|
-
#
|
102
|
+
# Mode | Parent View | Child View
|
103
|
+
# -------------------------------
|
104
|
+
# r read-only write-only
|
105
|
+
# w write-only read-only
|
106
|
+
# rw read-write read-write
|
108
107
|
#
|
109
|
-
# The communication
|
110
|
-
#
|
111
|
-
#
|
112
|
-
#
|
113
|
-
#
|
108
|
+
# @param [String] mode The communication mode of the pipe.
|
109
|
+
# @option opts [Numeric] :timeout (nil)
|
110
|
+
# The number of seconds to wait for a +puts+ or +gets+ to succeed. If not
|
111
|
+
# specified, calls through the pipe will block forever until data is
|
112
|
+
# available. You can configure the +puts+ and +gets+ to be non-blocking
|
113
|
+
# by setting the timeout to +0+.
|
114
|
+
# @return [Piper]
|
114
115
|
#
|
115
116
|
def initialize( *args )
|
116
117
|
opts = args.last.is_a?(Hash) ? args.pop : {}
|
@@ -146,34 +147,51 @@ class Servolux::Piper
|
|
146
147
|
# Close both the communications socket. This only affects the process from
|
147
148
|
# which it was called -- the parent or the child.
|
148
149
|
#
|
150
|
+
# @return [Piper] self
|
151
|
+
#
|
149
152
|
def close
|
150
153
|
@socket.close rescue nil
|
154
|
+
self
|
155
|
+
end
|
156
|
+
|
157
|
+
# Returns +true+ if the piper has been closed. Returns +false+ otherwise.
|
158
|
+
#
|
159
|
+
# @return [Boolean]
|
160
|
+
#
|
161
|
+
def closed?
|
162
|
+
@socket.closed?
|
151
163
|
end
|
152
164
|
|
153
165
|
# Returns +true+ if the communications pipe is readable from the process
|
154
166
|
# and there is data waiting to be read.
|
155
167
|
#
|
168
|
+
# @return [Boolean]
|
169
|
+
#
|
156
170
|
def readable?
|
157
171
|
return false if @socket.closed?
|
158
|
-
r,w,e = Kernel.select([@socket], nil, nil, @timeout)
|
172
|
+
r,w,e = Kernel.select([@socket], nil, nil, @timeout) rescue nil
|
159
173
|
return !(r.nil? or r.empty?)
|
160
174
|
end
|
161
175
|
|
162
176
|
# Returns +true+ if the communications pipe is writeable from the process
|
163
177
|
# and the write buffer can accept more data.
|
164
178
|
#
|
179
|
+
# @return [Boolean]
|
180
|
+
#
|
165
181
|
def writeable?
|
166
182
|
return false if @socket.closed?
|
167
|
-
r,w,e = Kernel.select(nil, [@socket], nil, @timeout)
|
183
|
+
r,w,e = Kernel.select(nil, [@socket], nil, @timeout) rescue nil
|
168
184
|
return !(w.nil? or w.empty?)
|
169
185
|
end
|
170
186
|
|
171
|
-
# call-seq:
|
172
|
-
# child { block }
|
173
|
-
# child {|piper| block }
|
174
|
-
#
|
175
187
|
# Execute the _block_ only in the child process. This method returns
|
176
|
-
# immediately when called from the parent process.
|
188
|
+
# immediately when called from the parent process. The piper instance is
|
189
|
+
# passed to the block if the arity is non-zero.
|
190
|
+
#
|
191
|
+
# @yield [self] Execute the block in the child process
|
192
|
+
# @yieldparam [Piper] self The piper instance (optional)
|
193
|
+
# @return The return value from the block or +nil+ when called from the
|
194
|
+
# parent.
|
177
195
|
#
|
178
196
|
def child( &block )
|
179
197
|
return unless child?
|
@@ -188,16 +206,20 @@ class Servolux::Piper
|
|
188
206
|
|
189
207
|
# Returns +true+ if this is the child prcoess and +false+ otherwise.
|
190
208
|
#
|
209
|
+
# @return [Boolean]
|
210
|
+
#
|
191
211
|
def child?
|
192
212
|
@child_pid.nil?
|
193
213
|
end
|
194
214
|
|
195
|
-
# call-seq:
|
196
|
-
# parent { block }
|
197
|
-
# parent {|piper| block }
|
198
|
-
#
|
199
215
|
# Execute the _block_ only in the parent process. This method returns
|
200
|
-
# immediately when called from the child process.
|
216
|
+
# immediately when called from the child process. The piper instance is
|
217
|
+
# passed to the block if the arity is non-zero.
|
218
|
+
#
|
219
|
+
# @yield [self] Execute the block in the parent process
|
220
|
+
# @yieldparam [Piper] self The piper instance (optional)
|
221
|
+
# @return The return value from the block or +nil+ when called from the
|
222
|
+
# child.
|
201
223
|
#
|
202
224
|
def parent( &block )
|
203
225
|
return unless parent?
|
@@ -212,6 +234,8 @@ class Servolux::Piper
|
|
212
234
|
|
213
235
|
# Returns +true+ if this is the parent prcoess and +false+ otherwise.
|
214
236
|
#
|
237
|
+
# @return [Boolean]
|
238
|
+
#
|
215
239
|
def parent?
|
216
240
|
!@child_pid.nil?
|
217
241
|
end
|
@@ -219,6 +243,8 @@ class Servolux::Piper
|
|
219
243
|
# Returns the PID of the child process when called from the parent.
|
220
244
|
# Returns +nil+ when called from the child.
|
221
245
|
#
|
246
|
+
# @return [Integer, nil] The PID of the child process or +nil+
|
247
|
+
#
|
222
248
|
def pid
|
223
249
|
@child_pid
|
224
250
|
end
|
@@ -255,6 +281,11 @@ class Servolux::Piper
|
|
255
281
|
# is returned. If this number is zero it means that the _obj_ was
|
256
282
|
# unsuccessfully communicated (sorry).
|
257
283
|
#
|
284
|
+
# @param [Object] obj The data to send to the "other" process. The object
|
285
|
+
# must be marshallable by Ruby (no Proc objects or lambdas).
|
286
|
+
# @return [Integer, nil] The number of bytes written to the pipe or +nil+ if
|
287
|
+
# there was an error or the pipe is not writeable.
|
288
|
+
#
|
258
289
|
def puts( obj )
|
259
290
|
return unless writeable?
|
260
291
|
|
@@ -270,6 +301,10 @@ class Servolux::Piper
|
|
270
301
|
#
|
271
302
|
# This method does nothing when called from the child process.
|
272
303
|
#
|
304
|
+
# @param [String, Integer] sig The signal to send to the child process.
|
305
|
+
# @return [Integer, nil] The result of Process#kill or +nil+ if called from
|
306
|
+
# the child process.
|
307
|
+
#
|
273
308
|
def signal( sig )
|
274
309
|
return if @child_pid.nil?
|
275
310
|
sig = Signal.list.invert[sig] if sig.is_a?(Integer)
|
@@ -281,6 +316,8 @@ class Servolux::Piper
|
|
281
316
|
#
|
282
317
|
# Always returns +nil+ when called from the child process.
|
283
318
|
#
|
319
|
+
# @return [Boolean, nil]
|
320
|
+
#
|
284
321
|
def alive?
|
285
322
|
return if @child_pid.nil?
|
286
323
|
Process.kill(0, @child_pid)
|
@@ -289,5 +326,5 @@ class Servolux::Piper
|
|
289
326
|
false
|
290
327
|
end
|
291
328
|
|
292
|
-
end
|
329
|
+
end
|
293
330
|
|
data/lib/servolux/prefork.rb
CHANGED
@@ -32,7 +32,9 @@
|
|
32
32
|
# Sending a SIGHUP to a child process will cause that child to stop and
|
33
33
|
# restart. The child will send a signal to the parent asking to be shutdown.
|
34
34
|
# The parent will gracefully halt the child and then start a new child process
|
35
|
-
# to replace it.
|
35
|
+
# to replace it. If you define a "hup" method in your worker module, it will
|
36
|
+
# be executed when SIGHUP is received by the child. Your "hup" method will be
|
37
|
+
# the last method executed in the signal handler.
|
36
38
|
#
|
37
39
|
# This has the advantage of calling your before/after_executing methods again
|
38
40
|
# and reloading any code or resources your worker code will use. The SIGHUP
|
@@ -99,6 +101,30 @@
|
|
99
101
|
# Use the heartbeat with caution -- allow margins for timing issues and
|
100
102
|
# processor load spikes.
|
101
103
|
#
|
104
|
+
# ==== Signals
|
105
|
+
# Forked child processes are configured to respond to two signals: SIGHUP and
|
106
|
+
# SIGTERM. The SIGHUP signal when sent to a child process is used to restart
|
107
|
+
# just that one child. The SIGTERM signal when sent to a child process is used
|
108
|
+
# to forcibly kill the child; it will not be restarted. The parent process
|
109
|
+
# uses SIGTERM to halt all the children when it is stopping.
|
110
|
+
#
|
111
|
+
# SIGHUP
|
112
|
+
# Child processes are restarted by sending a SIGHUP signal to the child. This
|
113
|
+
# will shutdown the child worker and then start up a new one to replace it.
|
114
|
+
# For the child to shutdown gracefully, it needs to return from the "execute"
|
115
|
+
# method when it receives the signal. Define a "hup" method that will wake the
|
116
|
+
# execute thread from any pending operations -- listening on a socket, reading
|
117
|
+
# a file, polling a queue, etc. When the execute method returns, the child
|
118
|
+
# will exit.
|
119
|
+
#
|
120
|
+
# SIGTERM
|
121
|
+
# Child processes are stopped by the prefork parent by sending a SIGTERM
|
122
|
+
# signal to the child. For the child to shutdown gracefully, it needs to
|
123
|
+
# return from the "execute" method when it receives the signal. Define a
|
124
|
+
# "term" method that will wake the execute thread from any pending operations
|
125
|
+
# -- listening on a socket, reading a file, polling a queue, etc. When the
|
126
|
+
# execute method returns, the child will exit.
|
127
|
+
#
|
102
128
|
class Servolux::Prefork
|
103
129
|
|
104
130
|
Timeout = Class.new(::Servolux::Error)
|
@@ -106,13 +132,14 @@ class Servolux::Prefork
|
|
106
132
|
UnknownResponse = Class.new(::Servolux::Error)
|
107
133
|
|
108
134
|
# :stopdoc:
|
109
|
-
START = "\000START".freeze
|
110
|
-
HALT = "\000HALT".freeze
|
111
|
-
ERROR = "\000SHIT".freeze
|
112
|
-
HEARTBEAT = "\000<3".freeze
|
135
|
+
START = "\000START".freeze # @private
|
136
|
+
HALT = "\000HALT".freeze # @private
|
137
|
+
ERROR = "\000SHIT".freeze # @private
|
138
|
+
HEARTBEAT = "\000<3".freeze # @private
|
113
139
|
# :startdoc:
|
114
140
|
|
115
141
|
attr_accessor :timeout # Communication timeout in seconds.
|
142
|
+
attr_reader :harvest # List of child PIDs that need to be reaped
|
116
143
|
|
117
144
|
# call-seq:
|
118
145
|
# Prefork.new { block }
|
@@ -137,6 +164,7 @@ class Servolux::Prefork
|
|
137
164
|
@module = opts[:module]
|
138
165
|
@module = Module.new { define_method :execute, &block } if block
|
139
166
|
@workers = []
|
167
|
+
@harvest = Queue.new
|
140
168
|
|
141
169
|
raise ArgumentError, 'No code was given to execute by the workers.' unless @module
|
142
170
|
end
|
@@ -144,6 +172,9 @@ class Servolux::Prefork
|
|
144
172
|
# Start up the given _number_ of workers. Each worker will create a child
|
145
173
|
# process and run the user supplied code in that child process.
|
146
174
|
#
|
175
|
+
# @param [Integer] number The number of workers to prefork
|
176
|
+
# @return [Prefork] self
|
177
|
+
#
|
147
178
|
def start( number )
|
148
179
|
@workers.clear
|
149
180
|
|
@@ -151,7 +182,7 @@ class Servolux::Prefork
|
|
151
182
|
@workers << Worker.new(self)
|
152
183
|
@workers.last.extend @module
|
153
184
|
}
|
154
|
-
@workers.each { |worker| worker.start }
|
185
|
+
@workers.each { |worker| worker.start; pause }
|
155
186
|
self
|
156
187
|
end
|
157
188
|
|
@@ -161,10 +192,38 @@ class Servolux::Prefork
|
|
161
192
|
# +errors+ methods will still function correctly after stopping the workers.
|
162
193
|
#
|
163
194
|
def stop
|
164
|
-
@workers.each { |worker| worker.stop }
|
165
|
-
|
195
|
+
@workers.each { |worker| worker.stop; pause }
|
196
|
+
reap
|
197
|
+
self
|
198
|
+
end
|
199
|
+
|
200
|
+
# This method should be called periodically in order to clear the return
|
201
|
+
# status from child processes that have either died or been restarted (via a
|
202
|
+
# HUP signal). This will remove zombie children from the process table.
|
203
|
+
#
|
204
|
+
# @return [Prefork] self
|
205
|
+
#
|
206
|
+
def reap
|
207
|
+
while !@harvest.empty?
|
208
|
+
pid = @harvest.pop
|
209
|
+
Process.wait pid rescue nil
|
210
|
+
end
|
211
|
+
self
|
212
|
+
end
|
213
|
+
|
214
|
+
# Send this given _signal_ to all child process. The default signal is
|
215
|
+
# 'TERM'. The method waits for a short period of time after the signal is
|
216
|
+
# sent to each child; this is done to alleviate a flood of signals being
|
217
|
+
# sent simultaneously and overwhemlming the CPU.
|
218
|
+
#
|
219
|
+
# @param [String, Integer] signal The signal to send to child processes.
|
220
|
+
# @return [Prefork] self
|
221
|
+
#
|
222
|
+
def signal( signal = 'TERM' )
|
223
|
+
@workers.each { |worker| worker.signal(signal); pause }
|
166
224
|
self
|
167
225
|
end
|
226
|
+
alias :kill :signal
|
168
227
|
|
169
228
|
# call-seq:
|
170
229
|
# each_worker { |worker| block }
|
@@ -188,6 +247,18 @@ class Servolux::Prefork
|
|
188
247
|
self
|
189
248
|
end
|
190
249
|
|
250
|
+
|
251
|
+
private
|
252
|
+
|
253
|
+
# Pause script execution for a random time interval between 0.1 and 0.4
|
254
|
+
# seconds. This method is used to slow down the starting and stopping of
|
255
|
+
# child processes in order to avoiad the "thundering herd" problem.
|
256
|
+
# http://en.wikipedia.org/wiki/Thundering_herd_problem
|
257
|
+
#
|
258
|
+
def pause
|
259
|
+
sleep(0.1 + 0.3*rand)
|
260
|
+
end
|
261
|
+
|
191
262
|
# The worker encapsulates the forking of the child process and communication
|
192
263
|
# between the parent and the child. Each worker instance is extended with
|
193
264
|
# the block or module supplied to the pre-forking pool that created the
|
@@ -195,13 +266,15 @@ class Servolux::Prefork
|
|
195
266
|
#
|
196
267
|
class Worker
|
197
268
|
|
198
|
-
attr_reader :prefork
|
199
269
|
attr_reader :error
|
200
270
|
|
201
271
|
# Create a new worker that belongs to the _prefork_ pool.
|
202
272
|
#
|
273
|
+
# @param [Prefork] prefork The prefork pool that created this worker.
|
274
|
+
#
|
203
275
|
def initialize( prefork )
|
204
|
-
@
|
276
|
+
@timeout = prefork.timeout
|
277
|
+
@harvest = prefork.harvest
|
205
278
|
@thread = nil
|
206
279
|
@piper = nil
|
207
280
|
@error = nil
|
@@ -210,11 +283,12 @@ class Servolux::Prefork
|
|
210
283
|
# Start this worker. A new process will be forked, and the code supplied
|
211
284
|
# by the user to the prefork pool will be executed in the child process.
|
212
285
|
#
|
286
|
+
# @return [Worker] self
|
287
|
+
#
|
213
288
|
def start
|
214
289
|
@error = nil
|
215
|
-
@piper = ::Servolux::Piper.new('rw', :timeout => @
|
216
|
-
|
217
|
-
child if @piper.child?
|
290
|
+
@piper = ::Servolux::Piper.new('rw', :timeout => @timeout)
|
291
|
+
@piper.parent? ? parent : child
|
218
292
|
self
|
219
293
|
end
|
220
294
|
|
@@ -223,13 +297,16 @@ class Servolux::Prefork
|
|
223
297
|
# without waiting for the child process to exit. Use the +wait+ method
|
224
298
|
# after calling +stop+ if your code needs to know when the child exits.
|
225
299
|
#
|
300
|
+
# @return [Worker, nil] self
|
301
|
+
#
|
226
302
|
def stop
|
227
303
|
return if @thread.nil? or @piper.nil? or @piper.child?
|
228
304
|
|
229
305
|
@thread[:stop] = true
|
230
306
|
@thread.wakeup if @thread.status
|
231
|
-
|
232
|
-
|
307
|
+
close_parent
|
308
|
+
signal 'TERM'
|
309
|
+
@thread.join(0.5) rescue nil
|
233
310
|
@thread = nil
|
234
311
|
self
|
235
312
|
end
|
@@ -246,18 +323,25 @@ class Servolux::Prefork
|
|
246
323
|
# Send this given _signal_ to the child process. The default signal is
|
247
324
|
# 'TERM'. This method will return immediately.
|
248
325
|
#
|
249
|
-
|
326
|
+
# @param [String, Integer] signal The signal to send to the child process.
|
327
|
+
# @return [Integer, nil] The result of Process#kill or +nil+ if called from
|
328
|
+
# the child process.
|
329
|
+
#
|
330
|
+
def signal( signal = 'TERM' )
|
250
331
|
return if @piper.nil?
|
251
332
|
@piper.signal signal
|
252
333
|
rescue Errno::ESRCH, Errno::ENOENT
|
253
334
|
return nil
|
254
335
|
end
|
336
|
+
alias :kill :signal
|
255
337
|
|
256
338
|
# Returns +true+ if the child process is alive. Returns +nil+ if the child
|
257
339
|
# process has not been started.
|
258
340
|
#
|
259
341
|
# Always returns +nil+ when called from the child process.
|
260
342
|
#
|
343
|
+
# @return [Boolean, nil]
|
344
|
+
#
|
261
345
|
def alive?
|
262
346
|
return if @piper.nil?
|
263
347
|
@piper.alive?
|
@@ -266,62 +350,93 @@ class Servolux::Prefork
|
|
266
350
|
|
267
351
|
private
|
268
352
|
|
353
|
+
def close_parent
|
354
|
+
@piper.timeout = 0.5
|
355
|
+
@piper.puts HALT rescue nil
|
356
|
+
@piper.close
|
357
|
+
end
|
358
|
+
|
269
359
|
# This code should only be executed in the parent process.
|
270
360
|
#
|
271
361
|
def parent
|
272
362
|
@thread = Thread.new {
|
273
|
-
response = nil
|
274
363
|
begin
|
275
364
|
@piper.puts START
|
276
365
|
Thread.current[:stop] = false
|
277
|
-
|
278
|
-
|
279
|
-
@piper.puts HEARTBEAT
|
280
|
-
response = @piper.gets(ERROR)
|
281
|
-
break if Thread.current[:stop]
|
282
|
-
|
283
|
-
case response
|
284
|
-
when HEARTBEAT; next
|
285
|
-
when START; break
|
286
|
-
when ERROR
|
287
|
-
raise Timeout,
|
288
|
-
"Child did not respond in a timely fashion. Timeout is set to #{@prefork.timeout.inspect} seconds."
|
289
|
-
when Exception
|
290
|
-
raise response
|
291
|
-
else
|
292
|
-
raise UnknownResponse,
|
293
|
-
"Child returned unknown response: #{response.inspect}"
|
294
|
-
end
|
295
|
-
}
|
296
|
-
rescue Exception => err
|
297
|
-
@error = err
|
366
|
+
response = parent_loop
|
367
|
+
# TODO: put a logger here to catch and log all exceptions
|
298
368
|
ensure
|
299
|
-
@
|
300
|
-
|
301
|
-
|
302
|
-
self.start if START == response and !Thread.current[:stop]
|
369
|
+
@harvest << @piper.pid
|
370
|
+
close_parent
|
371
|
+
start if START == response and !Thread.current[:stop]
|
303
372
|
end
|
304
373
|
}
|
305
374
|
Thread.pass until @thread[:stop] == false
|
306
375
|
end
|
307
376
|
|
377
|
+
def parent_loop
|
378
|
+
response = nil
|
379
|
+
loop {
|
380
|
+
break if Thread.current[:stop]
|
381
|
+
@piper.puts HEARTBEAT
|
382
|
+
response = @piper.gets(ERROR)
|
383
|
+
break if Thread.current[:stop]
|
384
|
+
|
385
|
+
case response
|
386
|
+
when HEARTBEAT; next
|
387
|
+
when START; break
|
388
|
+
when ERROR
|
389
|
+
raise Timeout,
|
390
|
+
"Child did not respond in a timely fashion. Timeout is set to #{@timeout.inspect} seconds."
|
391
|
+
when Exception
|
392
|
+
@error = response
|
393
|
+
break
|
394
|
+
else
|
395
|
+
raise UnknownResponse,
|
396
|
+
"Child returned unknown response: #{response.inspect}"
|
397
|
+
end
|
398
|
+
}
|
399
|
+
return response
|
400
|
+
end
|
401
|
+
|
308
402
|
# This code should only be executed in the child process. It wraps the
|
309
403
|
# user supplied "execute" code in a loop, and takes care of handling
|
310
404
|
# signals and communication with the parent.
|
311
405
|
#
|
312
406
|
def child
|
313
|
-
@
|
407
|
+
@harvest = nil
|
314
408
|
|
315
409
|
# if we get a HUP signal, then tell the parent process to stop this
|
316
410
|
# child process and start a new one to replace it
|
317
411
|
Signal.trap('HUP') {
|
412
|
+
@piper.timeout = 0.5
|
318
413
|
@piper.puts START rescue nil
|
319
|
-
|
414
|
+
hup if self.respond_to? :hup
|
320
415
|
}
|
321
416
|
|
322
|
-
|
323
|
-
|
417
|
+
Signal.trap('TERM') {
|
418
|
+
@piper.close
|
419
|
+
term if self.respond_to? :term
|
420
|
+
}
|
421
|
+
|
422
|
+
@thread = Thread.new {
|
423
|
+
begin
|
424
|
+
:wait until @piper.gets == START
|
425
|
+
before_executing rescue nil if self.respond_to? :before_executing
|
426
|
+
child_loop
|
427
|
+
rescue Exception => err
|
428
|
+
@piper.puts err rescue nil
|
429
|
+
ensure
|
430
|
+
after_executing rescue nil if self.respond_to? :after_executing
|
431
|
+
@piper.close
|
432
|
+
end
|
433
|
+
}
|
434
|
+
@thread.join
|
435
|
+
ensure
|
436
|
+
exit!
|
437
|
+
end
|
324
438
|
|
439
|
+
def child_loop
|
325
440
|
loop {
|
326
441
|
signal = @piper.gets(ERROR)
|
327
442
|
case signal
|
@@ -332,20 +447,13 @@ class Servolux::Prefork
|
|
332
447
|
break
|
333
448
|
when ERROR
|
334
449
|
raise Timeout,
|
335
|
-
"Parent did not respond in a timely fashion. Timeout is set to #{@
|
450
|
+
"Parent did not respond in a timely fashion. Timeout is set to #{@timeout.inspect} seconds."
|
336
451
|
else
|
337
452
|
raise UnknownSignal,
|
338
453
|
"Child received unknown signal: #{signal.inspect}"
|
339
454
|
end
|
340
455
|
}
|
341
|
-
after_executing if self.respond_to? :after_executing
|
342
|
-
rescue Exception => err
|
343
|
-
@piper.puts err rescue nil
|
344
|
-
ensure
|
345
|
-
@piper.close
|
346
|
-
exit!
|
347
456
|
end
|
348
|
-
end
|
349
|
-
|
350
|
-
end # class Servolux::Prefork
|
457
|
+
end
|
458
|
+
end
|
351
459
|
|
data/lib/servolux/server.rb
CHANGED
@@ -181,6 +181,8 @@ class Servolux::Server
|
|
181
181
|
# flag is used to ensure that the server has shutdown completely when
|
182
182
|
# shutdown by a signal.
|
183
183
|
#
|
184
|
+
# @return [Server] self
|
185
|
+
#
|
184
186
|
def startup( wait = false )
|
185
187
|
return self if running?
|
186
188
|
@mutex.synchronize {
|
@@ -189,8 +191,8 @@ class Servolux::Server
|
|
189
191
|
|
190
192
|
begin
|
191
193
|
create_pid_file
|
192
|
-
trap_signals
|
193
194
|
start
|
195
|
+
trap_signals
|
194
196
|
join
|
195
197
|
wait_for_shutdown if wait
|
196
198
|
ensure
|
@@ -211,10 +213,11 @@ class Servolux::Server
|
|
211
213
|
# the server is completely stopped, use the +wait_for_shutdown+ method to
|
212
214
|
# be notified when the this +shutdown+ method is finished executing.
|
213
215
|
#
|
216
|
+
# @return [Server] self
|
217
|
+
#
|
214
218
|
def shutdown
|
215
219
|
return self unless running?
|
216
220
|
stop
|
217
|
-
|
218
221
|
@mutex.synchronize {
|
219
222
|
@shutdown.signal
|
220
223
|
@shutdown = nil
|
@@ -253,6 +256,9 @@ class Servolux::Server
|
|
253
256
|
|
254
257
|
def delete_pid_file
|
255
258
|
if test(?f, pid_file)
|
259
|
+
pid = Integer(File.read(pid_file).strip)
|
260
|
+
return unless pid == Process.pid
|
261
|
+
|
256
262
|
logger.debug "Server #{name.inspect} removing pid file #{pid_file.inspect}"
|
257
263
|
File.delete(pid_file)
|
258
264
|
end
|
@@ -265,6 +271,5 @@ class Servolux::Server
|
|
265
271
|
end
|
266
272
|
end
|
267
273
|
|
268
|
-
end
|
274
|
+
end
|
269
275
|
|
270
|
-
# EOF
|
data/lib/servolux/threaded.rb
CHANGED
@@ -1,3 +1,4 @@
|
|
1
|
+
|
1
2
|
# == Synopsis
|
2
3
|
# The Threaded module is used to peform some activity at a specified
|
3
4
|
# interval.
|
@@ -74,7 +75,6 @@ module Servolux::Threaded
|
|
74
75
|
|
75
76
|
before_stopping if self.respond_to?(:before_stopping)
|
76
77
|
@_activity_thread.stop
|
77
|
-
after_stopping if self.respond_to?(:after_stopping)
|
78
78
|
self
|
79
79
|
end
|
80
80
|
|
@@ -198,23 +198,23 @@ module Servolux::Threaded
|
|
198
198
|
end
|
199
199
|
|
200
200
|
# :stopdoc:
|
201
|
-
|
202
201
|
def _activity_thread
|
203
202
|
@_activity_thread ||= ::Servolux::Threaded::ThreadContainer.new(60, 0, nil, false);
|
204
|
-
end
|
203
|
+
end # @private
|
205
204
|
|
205
|
+
# @private
|
206
206
|
ThreadContainer = Struct.new( :interval, :iterations, :maximum_iterations, :continue_on_error, :thread, :running ) {
|
207
207
|
def start( threaded )
|
208
208
|
self.running = true
|
209
209
|
self.iterations = 0
|
210
210
|
self.thread = Thread.new { run threaded }
|
211
211
|
Thread.pass
|
212
|
-
end
|
212
|
+
end # @private
|
213
213
|
|
214
214
|
def stop
|
215
215
|
self.running = false
|
216
216
|
thread.wakeup
|
217
|
-
end
|
217
|
+
end # @private
|
218
218
|
|
219
219
|
def run( threaded )
|
220
220
|
loop {
|
@@ -235,23 +235,25 @@ module Servolux::Threaded
|
|
235
235
|
end
|
236
236
|
}
|
237
237
|
ensure
|
238
|
+
if threaded.respond_to?(:after_stopping) and !self.running
|
239
|
+
threaded.after_stopping rescue nil
|
240
|
+
end
|
238
241
|
self.running = false
|
239
|
-
end
|
242
|
+
end # @private
|
240
243
|
|
241
244
|
def join( limit = nil )
|
242
245
|
return if thread.nil?
|
243
246
|
limit ? thread.join(limit) : thread.join
|
244
|
-
end
|
247
|
+
end # @private
|
245
248
|
|
246
249
|
def finished_iterations?
|
247
250
|
return true if maximum_iterations and (iterations >= maximum_iterations)
|
248
251
|
return false
|
249
|
-
end
|
252
|
+
end # @private
|
250
253
|
|
251
254
|
alias :running? :running
|
252
255
|
}
|
253
256
|
# :startdoc:
|
254
257
|
|
255
|
-
end
|
258
|
+
end
|
256
259
|
|
257
|
-
# EOF
|