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 +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
|