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.
@@ -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