servolux 0.10.0 → 0.11.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.
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: c744b0af533fabea800e187f1008917cf2c7aacf
4
+ data.tar.gz: e46bfba16ebbcfac37aa4e4d0fa44bb6e090e403
5
+ SHA512:
6
+ metadata.gz: 1f8e41c57443b49a4a4ba6766daa95933e7e6a9827e759aee2284828589e29c13536041ba70e196ac945fa5cc1ee96c61124cfd0726881990634c4b4f9b32372
7
+ data.tar.gz: 555e8ad6764063743afca6635515f445e8e8e04b509a4143fbda4026cc9074b98d9310f36da0f37c59083f127cba8a10df44738dda4b1590d52ea9a76d83af9c
data/.gitignore CHANGED
@@ -16,3 +16,5 @@ coverage
16
16
  doc
17
17
  pkg
18
18
  .yardoc
19
+ .rvmrc
20
+ vendor
@@ -6,6 +6,6 @@ install: "rake gem:install_dependencies"
6
6
  script: "rake"
7
7
 
8
8
  rvm:
9
- - 1.8.7
10
- - 1.9.2
11
-
9
+ - 1.9.3
10
+ - 2.1.5
11
+ - 2.2.2
@@ -0,0 +1,74 @@
1
+ ## Serv-O-Lux
2
+ by Tim Pease [![](https://secure.travis-ci.org/TwP/servolux.png)](http://travis-ci.org/TwP/servolux)
3
+
4
+ * [Homepage](http://rubygems.org/gems/servolux)
5
+ * [Github Project](http://github.com/TwP/servolux)
6
+
7
+ ### Description
8
+
9
+ Serv-O-Lux is a collection of Ruby classes that are useful for daemon and
10
+ process management, and for writing your own Ruby services. The code is well
11
+ documented and tested. It works with Ruby and JRuby supporting both 1.8 and 1.9
12
+ interpreters.
13
+
14
+ ### Features
15
+
16
+ [Servolux::Threaded](http://www.rubydoc.info/github/TwP/servolux/Servolux/Threaded)
17
+ -- when included into your own class, it gives you an activity thread that will
18
+ run some code at a regular interval. Provides methods to start and stop the
19
+ thread, report on the running state, and join the thread to wait for it to
20
+ complete.
21
+
22
+ [Servolux::Server](http://www.rubydoc.info/github/TwP/servolux/Servolux/Server)
23
+ -- a template server class that handles the mundane work of creating / deleting
24
+ a PID file, reporting running state, logging errors, starting the service, and
25
+ gracefully shutting down the service.
26
+
27
+ [Servolux::Piper](http://www.rubydoc.info/github/TwP/servolux/Servolux/Piper)
28
+ -- an extension of the standard Ruby fork method that opens a pipe for
29
+ communication between parent and child processes. Ruby objects are passed
30
+ between parent and child allowing, for example, exceptions in the child process
31
+ to be passed to the parent and raised there.
32
+
33
+ [Servolux::Daemon](http://www.rubydoc.info/github/TwP/servolux/Servolux/Daemon)
34
+ -- a robust class for starting and stopping daemon processes.
35
+
36
+ [Servolux::Child](http://www.rubydoc.info/github/TwP/servolux/Servolux/Child)
37
+ -- adds some much needed functionality to child processes created via Ruby's
38
+ IO#popen method. Specifically, a timeout thread is used to signal the child
39
+ process to die if it does not exit in a given amount of time.
40
+
41
+ [Servolux::Prefork](http://www.rubydoc.info/github/TwP/servolux/Servolux/Prefork)
42
+ -- provides a pre-forking worker pool for executing tasks in parallel using
43
+ multiple processes.
44
+
45
+ All the documentation is available online at http://rdoc.info/projects/TwP/servolux
46
+
47
+ ### Install
48
+
49
+ gem install servolux
50
+
51
+ ### License
52
+
53
+ The MIT License
54
+
55
+ Copyright (c) 2015 Tim Pease
56
+
57
+ Permission is hereby granted, free of charge, to any person obtaining
58
+ a copy of this software and associated documentation files (the
59
+ 'Software'), to deal in the Software without restriction, including
60
+ without limitation the rights to use, copy, modify, merge, publish,
61
+ distribute, sublicense, and/or sell copies of the Software, and to
62
+ permit persons to whom the Software is furnished to do so, subject to
63
+ the following conditions:
64
+
65
+ The above copyright notice and this permission notice shall be
66
+ included in all copies or substantial portions of the Software.
67
+
68
+ THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND,
69
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
70
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
71
+ IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
72
+ CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
73
+ TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
74
+ SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/Rakefile CHANGED
@@ -5,20 +5,24 @@ rescue LoadError
5
5
  abort '### please install the "bones" gem ###'
6
6
  end
7
7
 
8
+ lib = File.expand_path('../lib', __FILE__)
9
+ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
10
+ require 'servolux/version'
11
+
8
12
  task :default => 'spec:run'
9
13
  task 'gem:release' => 'spec:run'
10
14
 
11
15
  Bones {
12
16
  name 'servolux'
17
+ summary 'A collection of tools for working with processes'
13
18
  authors 'Tim Pease'
14
19
  email 'tim.pease@gmail.com'
15
- url 'http://gemcutter.org/gems/servolux'
16
- readme_file 'README.rdoc'
17
- spec.opts << '--color' << '--format documentation'
20
+ url 'http://rubygems.org/gems/servolux'
21
+ version Servolux::VERSION
18
22
 
19
- use_gmail
23
+ spec.opts << '--color' << '--format documentation'
20
24
 
21
- depend_on 'bones-rspec', :development => true
22
- depend_on 'bones-git', :development => true
23
- depend_on 'logging', :development => true
25
+ depend_on 'bones-rspec', '~> 2.0', :development => true
26
+ depend_on 'bones-git', '~> 1.3', :development => true
27
+ depend_on 'logging', '~> 2.0', :development => true
24
28
  }
@@ -25,7 +25,9 @@ module JobProcessor
25
25
  # Open a connection to our beanstalk queue. This method is called once just
26
26
  # before entering the child run loop.
27
27
  def before_executing
28
- @beanstalk = Beanstalk::Pool.new(['localhost:11300'])
28
+ host = config[:host]
29
+ port = config[:port]
30
+ @beanstalk = Beanstalk::Pool.new(["#{host}:#{port}"])
29
31
  end
30
32
 
31
33
  # Close the connection to our beanstalk queue. This method is called once
@@ -65,7 +67,13 @@ module JobProcessor
65
67
  end
66
68
 
67
69
  # Create our preforking worker pool. Each worker will run the code found in
68
- # the JobProcessor module. We set a timeout of 10 minutes. The child process
70
+ # the JobProcessor module.
71
+ #
72
+ # The `:config` Hash is passed to each worker when it is created. The values
73
+ # here are available to the JobProcessor module. We use this config hash to pass
74
+ # the `:host` and `:port` where the beanstalkd server can be found.
75
+ #
76
+ # We set a timeout of 10 minutes for the worker pool. The child process
69
77
  # must send a "heartbeat" message to the parent within this timeout period;
70
78
  # otherwise, the parent will halt the child process.
71
79
  #
@@ -75,7 +83,10 @@ end
75
83
  #
76
84
  # This also means that if any job processed by a worker takes longer than 10
77
85
  # minutes to run, that child worker will be killed.
78
- pool = Servolux::Prefork.new(:timeout => 600, :module => JobProcessor)
86
+ pool = Servolux::Prefork.new \
87
+ :timeout => 600,
88
+ :module => JobProcessor,
89
+ :config => {:host => '127.0.0.1', :port => 11300}
79
90
 
80
91
  # Start up 7 child processes to handle jobs
81
92
  pool.start 7
@@ -32,7 +32,9 @@ module BeanstalkWorkerPool
32
32
  # Before we start the server run loop, allocate our pool of child workers
33
33
  # and prefork seven JobProcessors to pull work from the beanstalk queue.
34
34
  def before_starting
35
- @pool = Servolux::Prefork.new(:module => JobProcessor)
35
+ @pool = Servolux::Prefork.new \
36
+ :module => JobProcessor,
37
+ :config => {:host => '127.0.0.1', :port => 11300}
36
38
  @pool.start 7
37
39
  end
38
40
 
@@ -57,7 +59,9 @@ end
57
59
  # See the beanstalk.rb example for an explanation of the JobProcessor
58
60
  module JobProcessor
59
61
  def before_executing
60
- @beanstalk = Beanstalk::Pool.new(['localhost:11300'])
62
+ host = config[:host]
63
+ port = config[:port]
64
+ @beanstalk = Beanstalk::Pool.new(["#{host}:#{port}"])
61
65
  end
62
66
 
63
67
  def after_executing
@@ -1,4 +1,3 @@
1
-
2
1
  module Servolux
3
2
 
4
3
  # :stopdoc:
@@ -9,14 +8,6 @@ module Servolux
9
8
  # Generic Servolux Error class.
10
9
  Error = Class.new(StandardError)
11
10
 
12
- # Returns the version string for the library.
13
- #
14
- # @return [String]
15
- #
16
- def self.version
17
- @version ||= File.read(path('version.txt')).strip
18
- end
19
-
20
11
  # Returns the library path for the module. If any arguments are given,
21
12
  # they will be joined to the end of the library path using
22
13
  # <tt>File.join</tt>.
@@ -47,7 +38,7 @@ module Servolux
47
38
 
48
39
  end
49
40
 
50
- %w[threaded server piper daemon child prefork].each do |lib|
41
+ %w[version threaded server piper daemon child prefork].each do |lib|
51
42
  require Servolux.libpath('servolux', lib)
52
43
  end
53
44
 
@@ -121,7 +121,7 @@ class Servolux::Child
121
121
  end
122
122
 
123
123
  kill if alive?
124
- @io.close rescue nil
124
+ @io.close unless @io.nil? || @io.closed?
125
125
  @io = nil
126
126
  self
127
127
  end
@@ -126,7 +126,7 @@ class Servolux::Daemon
126
126
  # still being used by the parent process. This prevents zombie processes.
127
127
  #
128
128
  # @option opts [Numeric, String, Array<String>, Proc, Method, Servolux::Server] :shutdown_command (nil)
129
- # Assign the startup command. Different calling semantics are used for
129
+ # Assign the shutdown command. Different calling semantics are used for
130
130
  # each type of command.
131
131
  #
132
132
  # @option opts [String] :log_file (nil)
@@ -363,9 +363,9 @@ private
363
363
 
364
364
  skip = [STDIN, STDOUT, STDERR]
365
365
  skip << @piper.socket if @piper
366
- ObjectSpace.each_object(IO) { |obj|
367
- next if skip.include? obj
368
- obj.close rescue nil
366
+ ObjectSpace.each_object(IO) { |io|
367
+ next if skip.include? io
368
+ io.close unless io.closed?
369
369
  }
370
370
 
371
371
  before_exec.call if before_exec.respond_to? :call
@@ -1,4 +1,3 @@
1
-
2
1
  require 'socket'
3
2
 
4
3
  # == Synopsis
@@ -55,7 +54,7 @@ class Servolux::Piper
55
54
  # Creates a new Piper with the child process configured as a daemon. The
56
55
  # +pid+ method of the piper returns the PID of the daemon process.
57
56
  #
58
- # Be default a daemon process will release its current working directory
57
+ # By default a daemon process will release its current working directory
59
58
  # and the stdout/stderr/stdin file descriptors. This allows the parent
60
59
  # process to exit cleanly. This behavior can be overridden by setting the
61
60
  # _nochdir_ and _noclose_ flags to true. The first will keep the current
@@ -154,7 +153,7 @@ class Servolux::Piper
154
153
  # @return [Piper] self
155
154
  #
156
155
  def close
157
- @socket.close rescue nil
156
+ @socket.close unless @socket.closed?
158
157
  self
159
158
  end
160
159
 
@@ -173,7 +172,7 @@ class Servolux::Piper
173
172
  #
174
173
  def readable?
175
174
  return false if @socket.closed?
176
- r,w,e = Kernel.select([@socket], nil, nil, @timeout) rescue nil
175
+ r,_,_ = Kernel.select([@socket], nil, nil, @timeout) rescue nil
177
176
  return !(r.nil? or r.empty?)
178
177
  end
179
178
 
@@ -184,7 +183,7 @@ class Servolux::Piper
184
183
  #
185
184
  def writeable?
186
185
  return false if @socket.closed?
187
- r,w,e = Kernel.select(nil, [@socket], nil, @timeout) rescue nil
186
+ _,w,_ = Kernel.select(nil, [@socket], nil, @timeout) rescue nil
188
187
  return !(w.nil? or w.empty?)
189
188
  end
190
189
 
@@ -141,6 +141,7 @@ class Servolux::Prefork
141
141
  attr_accessor :timeout # Communication timeout in seconds.
142
142
  attr_accessor :min_workers # Minimum number of workers
143
143
  attr_accessor :max_workers # Maximum number of workers
144
+ attr_accessor :config # Worker configuration options (a Hash)
144
145
 
145
146
  # call-seq:
146
147
  # Prefork.new { block }
@@ -171,6 +172,7 @@ class Servolux::Prefork
171
172
  @module = opts.fetch(:module, nil)
172
173
  @max_workers = opts.fetch(:max_workers, nil)
173
174
  @min_workers = opts.fetch(:min_workers, nil)
175
+ @config = opts.fetch(:config, {})
174
176
  @module = Module.new { define_method :execute, &block } if block
175
177
  @workers = []
176
178
 
@@ -245,7 +247,7 @@ class Servolux::Prefork
245
247
  def add_workers( number = 1 )
246
248
  number.times do
247
249
  break if at_max_workers?
248
- worker = Worker.new( self )
250
+ worker = Worker.new(self, @config)
249
251
  worker.extend @module
250
252
  worker.start
251
253
  @workers << worker
@@ -360,12 +362,16 @@ private
360
362
 
361
363
  attr_reader :error
362
364
 
365
+ attr_reader :config
366
+
363
367
  # Create a new worker that belongs to the _prefork_ pool.
364
368
  #
365
369
  # @param [Prefork] prefork The prefork pool that created this worker.
370
+ # @param [Hash] config The worker configuration options.
366
371
  #
367
- def initialize( prefork )
372
+ def initialize( prefork, config )
368
373
  @timeout = prefork.timeout
374
+ @config = config
369
375
  @thread = nil
370
376
  @piper = nil
371
377
  @error = nil
@@ -190,9 +190,9 @@ class Servolux::Server
190
190
  }
191
191
 
192
192
  begin
193
+ trap_signals
193
194
  create_pid_file
194
195
  start
195
- trap_signals
196
196
  join
197
197
  wait_for_shutdown if wait
198
198
  ensure
@@ -266,10 +266,19 @@ class Servolux::Server
266
266
 
267
267
  def trap_signals
268
268
  SIGNALS.each do |sig|
269
- m = sig.downcase.to_sym
270
- Signal.trap(sig) { self.send(m) rescue nil } if self.respond_to? m
269
+ method = sig.downcase.to_sym
270
+ if self.respond_to? method
271
+ Signal.trap(sig) do
272
+ Thread.new do
273
+ begin
274
+ self.send(method)
275
+ rescue StandardError => err
276
+ logger.error "Exception in signal handler: #{method}"
277
+ logger.error err
278
+ end
279
+ end
280
+ end
281
+ end
271
282
  end
272
283
  end
273
-
274
284
  end
275
-
@@ -152,6 +152,27 @@ module Servolux::Threaded
152
152
  _activity_thread.interval
153
153
  end
154
154
 
155
+ # Signals the activity thread to treat the sleep interval with strict
156
+ # semantics. The time it takes for the 'run' method to execute will be
157
+ # subtracted from the sleep interval.
158
+ #
159
+ # If the sleep interval is 60 seconds and the 'run' method takes 2.2 seconds
160
+ # to execute, then the activity thread will sleep for 57.2 seconds. The
161
+ # subsequent invocation of the 'run' will occur as close as possible to 60
162
+ # seconds after the previous invocation.
163
+ #
164
+ def use_strict_interval=( value )
165
+ _activity_thread.use_strict_interval = (value ? true : false)
166
+ end
167
+
168
+ # When true, the activity thread will treat the sleep interval with strict
169
+ # semantics. See the setter method for an in depth explanation.
170
+ #
171
+ def use_strict_interval
172
+ _activity_thread.use_strict_interval
173
+ end
174
+ alias :use_strict_interval? :use_strict_interval
175
+
155
176
  # Sets the maximum number of invocations of the threaded object's
156
177
  # 'run' method
157
178
  #
@@ -199,11 +220,11 @@ module Servolux::Threaded
199
220
 
200
221
  # :stopdoc:
201
222
  def _activity_thread
202
- @_activity_thread ||= ::Servolux::Threaded::ThreadContainer.new(60, 0, nil, false);
223
+ @_activity_thread ||= ::Servolux::Threaded::ThreadContainer.new(60, false, 0, nil, false);
203
224
  end # @private
204
225
 
205
226
  # @private
206
- ThreadContainer = Struct.new( :interval, :iterations, :maximum_iterations, :continue_on_error, :thread, :running ) {
227
+ ThreadContainer = Struct.new( :interval, :use_strict_interval, :iterations, :maximum_iterations, :continue_on_error, :thread, :running ) {
207
228
  def start( threaded )
208
229
  self.running = true
209
230
  self.iterations = 0
@@ -219,6 +240,7 @@ module Servolux::Threaded
219
240
  def run( threaded )
220
241
  loop do
221
242
  begin
243
+ mark_time
222
244
  break unless running?
223
245
  threaded.run
224
246
 
@@ -230,7 +252,7 @@ module Servolux::Threaded
230
252
  end
231
253
  end
232
254
 
233
- sleep interval if running?
255
+ sleep if running?
234
256
  rescue SystemExit; raise
235
257
  rescue Exception => err
236
258
  if continue_on_error
@@ -259,6 +281,33 @@ module Servolux::Threaded
259
281
  end # @private
260
282
 
261
283
  alias :running? :running
284
+
285
+ # Mark the start time of the run loop.
286
+ #
287
+ def mark_time
288
+ @mark = Time.now if use_strict_interval
289
+ end # @private
290
+
291
+ # Sleep for "interval" seconds adjusting for the run time of the "run"
292
+ # method if the "use_strict_interval" flag is set. If the run time of the
293
+ # "run" method exceeds our sleep "interval", then log a warning and just
294
+ # use the interval as normal for this sleep period.
295
+ #
296
+ def sleep
297
+ time_to_sleep = interval
298
+
299
+ if use_strict_interval and interval > 0
300
+ diff = Time.now - @mark
301
+ time_to_sleep = interval - diff
302
+
303
+ if time_to_sleep < 0
304
+ time_to_sleep = interval
305
+ logger.warn "Run time [#{diff} s] exceeded strict interval [#{interval} s]"
306
+ end
307
+ end
308
+
309
+ ::Kernel.sleep time_to_sleep
310
+ end # @private
262
311
  }
263
312
  # :startdoc:
264
313