servolux 0.10.0 → 0.11.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -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