servolux 0.8.1 → 0.9.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -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
- # call-seq:
96
- # Piper.new( mode = 'r', opts = {} )
97
- #
98
- # Creates a new Piper instance with the communication pipe configured
99
- # using the provided _mode_. The default mode is read-only (from the
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
- # Mode | Parent View | Child View
104
- # -----+-------------+-----------
105
- # r read-only write-only
106
- # w write-only read-only
107
- # rw read-write read-write
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 timeout can be provided as an option. This is the
110
- # number of seconds to wait for a +puts+ or +gets+ to succeed. This timeout
111
- # will default to +nil+ such that calls through the pipe will block forever
112
- # until data is available. You can configure the +puts+ and +gets+ to be
113
- # non-blocking by setting the timeout to +0+.
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 # class Servolux::Piper
329
+ end
293
330
 
@@ -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
- @workers.each { |worker| worker.wait }
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
- @prefork = prefork
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 => @prefork.timeout)
216
- parent if @piper.parent?
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
- Thread.pass until !@thread.status
232
- kill 'HUP'
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
- def kill( signal = 'TERM' )
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
- loop {
278
- break if Thread.current[:stop]
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
- @piper.timeout = 0.1
300
- @piper.puts HALT rescue nil
301
- @piper.close
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
- @thread = Thread.current
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
- @thread.wakeup
414
+ hup if self.respond_to? :hup
320
415
  }
321
416
 
322
- before_executing if self.respond_to? :before_executing
323
- :wait until @piper.gets == START
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 #{@prefork.timeout.inspect} seconds."
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 # class Worker
349
-
350
- end # class Servolux::Prefork
457
+ end
458
+ end
351
459
 
@@ -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 # class Servolux::Server
274
+ end
269
275
 
270
- # EOF
@@ -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 # module Servolux::Threaded
258
+ end
256
259
 
257
- # EOF