servolux 0.9.7 → 0.10.0
Sign up to get free protection for your applications and to get access to all the features.
- data/History.txt +9 -0
- data/README.rdoc +2 -2
- data/Rakefile +1 -1
- data/examples/beanstalk.rb +2 -2
- data/examples/preforking_server.rb +151 -0
- data/examples/server_beanstalk.rb +2 -2
- data/lib/servolux.rb +1 -1
- data/lib/servolux/child.rb +14 -2
- data/lib/servolux/daemon.rb +6 -6
- data/lib/servolux/piper.rb +50 -7
- data/lib/servolux/prefork.rb +127 -27
- data/lib/servolux/server.rb +2 -2
- data/lib/servolux/threaded.rb +5 -5
- data/spec/prefork_spec.rb +76 -2
- data/spec/server_spec.rb +41 -25
- data/version.txt +1 -1
- metadata +14 -13
data/History.txt
CHANGED
@@ -1,3 +1,12 @@
|
|
1
|
+
== 0.10.0 / 2012-02-18
|
2
|
+
|
3
|
+
* Minor Enhancements
|
4
|
+
* Add in the ability to vary the Prefork worker pool size [issue #8]
|
5
|
+
* Pass original child exception backtrace up the exception chain [issue #7]
|
6
|
+
* Improved child process wellness checks in Piper and Child classes
|
7
|
+
* Bug Fixes
|
8
|
+
* Typo and documentation fixes [issue #6]
|
9
|
+
|
1
10
|
== 0.9.7 / 2012-01-19
|
2
11
|
|
3
12
|
* Minor Enhancements
|
data/README.rdoc
CHANGED
@@ -8,7 +8,7 @@ by Tim Pease {<img src="https://secure.travis-ci.org/TwP/servolux.png">}[http://
|
|
8
8
|
|
9
9
|
Serv-O-Lux is a collection of Ruby classes that are useful for daemon and
|
10
10
|
process management, and for writing your own Ruby services. The code is well
|
11
|
-
documented and tested. It works with Ruby and JRuby
|
11
|
+
documented and tested. It works with Ruby and JRuby supporting both 1.8 and 1.9
|
12
12
|
interpreters.
|
13
13
|
|
14
14
|
=== FEATURES
|
@@ -29,7 +29,7 @@ process to be passed to the parent and raised there.
|
|
29
29
|
|
30
30
|
Servolux::Daemon -- a robust class for starting and stopping daemon processes.
|
31
31
|
|
32
|
-
Servolux::Child -- adds some much needed
|
32
|
+
Servolux::Child -- adds some much needed functionality to child processes
|
33
33
|
created via Ruby's IO#popen method. Specifically, a timeout thread is used to
|
34
34
|
signal the child process to die if it does not exit in a given amount of time.
|
35
35
|
|
data/Rakefile
CHANGED
data/examples/beanstalk.rb
CHANGED
@@ -2,12 +2,12 @@
|
|
2
2
|
#
|
3
3
|
# In this example, we prefork 7 processes each of which connect to our
|
4
4
|
# Beanstalkd queue and then wait for jobs to process. We are using a module so
|
5
|
-
# that we can connect to the beanstalk queue before
|
5
|
+
# that we can connect to the beanstalk queue before executing and then
|
6
6
|
# disconnect from the beanstalk queue after exiting. These methods are called
|
7
7
|
# exactly once per child process.
|
8
8
|
#
|
9
9
|
# A variation on this is to load source code in the before_executing method
|
10
|
-
# and initialize an object that will process jobs. This is
|
10
|
+
# and initialize an object that will process jobs. This is advantageous because
|
11
11
|
# now you can send SIGHUP to a child process and it will restart, loading your
|
12
12
|
# Ruby libraries before executing. Now you can do a rolling deploy of new
|
13
13
|
# code.
|
@@ -0,0 +1,151 @@
|
|
1
|
+
require 'rubygems'
|
2
|
+
require 'servolux'
|
3
|
+
require 'logger'
|
4
|
+
|
5
|
+
###############################################################################
|
6
|
+
# This is an example script that creates uses both Prefork and Server
|
7
|
+
#
|
8
|
+
# The Server will manage the Prefork pool and respond to signals to add new
|
9
|
+
# workers to the pool
|
10
|
+
###############################################################################
|
11
|
+
|
12
|
+
# The child script that will be executed, this is just a shell script that
|
13
|
+
# will be execed by each worker pool member.
|
14
|
+
module ExecChild
|
15
|
+
def self.the_script
|
16
|
+
<<__sh__
|
17
|
+
#!/bin/sh
|
18
|
+
#
|
19
|
+
trap "echo I am process $$" SIGUSR1
|
20
|
+
trap "exit 0" SIGHUP
|
21
|
+
trap "exit 1" SIGTERM
|
22
|
+
|
23
|
+
echo "[$$] I am a child program"
|
24
|
+
while true
|
25
|
+
do
|
26
|
+
sleep 1
|
27
|
+
done
|
28
|
+
__sh__
|
29
|
+
end
|
30
|
+
|
31
|
+
def self.script_name
|
32
|
+
"/tmp/exec-child-worker.sh"
|
33
|
+
end
|
34
|
+
|
35
|
+
def self.write_script
|
36
|
+
File.open( script_name, "w+", 0750 ) do |f|
|
37
|
+
f.write( the_script )
|
38
|
+
end
|
39
|
+
end
|
40
|
+
|
41
|
+
def self.remove_script
|
42
|
+
File.unlink( script_name )
|
43
|
+
end
|
44
|
+
|
45
|
+
def execute
|
46
|
+
exec ExecChild.script_name
|
47
|
+
end
|
48
|
+
end
|
49
|
+
|
50
|
+
class PreforkingServerExample < ::Servolux::Server
|
51
|
+
|
52
|
+
# Create a preforking server that has the given minimum and maximum boundaries
|
53
|
+
#
|
54
|
+
def initialize( min_workers = 2, max_workers = 10 )
|
55
|
+
@logger = ::Logger.new( $stderr )
|
56
|
+
super( self.class.name, :interval => 2, :logger => @logger )
|
57
|
+
@pool = Servolux::Prefork.new( :module => ExecChild, :timeout => nil,
|
58
|
+
:min_workers => min_workers, :max_workers => max_workers )
|
59
|
+
end
|
60
|
+
|
61
|
+
def log( msg )
|
62
|
+
logger.info msg
|
63
|
+
end
|
64
|
+
|
65
|
+
def log_pool_status
|
66
|
+
log "Pool status : #{@pool.worker_counts.inspect} living pids #{live_worker_pids.join(' ')}"
|
67
|
+
end
|
68
|
+
|
69
|
+
def live_worker_pids
|
70
|
+
pids = []
|
71
|
+
@pool.each_worker { |w| pids << w.pid if w.alive? }
|
72
|
+
return pids
|
73
|
+
end
|
74
|
+
|
75
|
+
def shutdown_workers
|
76
|
+
log "Shutting down all workers"
|
77
|
+
@pool.stop
|
78
|
+
loop do
|
79
|
+
log_pool_status
|
80
|
+
break if @pool.live_worker_count <= 0
|
81
|
+
sleep 0.25
|
82
|
+
end
|
83
|
+
end
|
84
|
+
|
85
|
+
def log_worker_status( worker )
|
86
|
+
if not worker.alive? then
|
87
|
+
worker.wait
|
88
|
+
if worker.exited? then
|
89
|
+
log "Worker #{worker.pid} exited with status #{worker.exitstatus}"
|
90
|
+
elsif worker.signaled? then
|
91
|
+
log "Worker #{worker.pid} signaled by #{worker.termsig}"
|
92
|
+
elsif worker.stopped? then
|
93
|
+
log "Worker #{worker.pid} stopped by #{worker.stopsig}"
|
94
|
+
else
|
95
|
+
log "I have no clue #{worker.inspect}"
|
96
|
+
end
|
97
|
+
end
|
98
|
+
end
|
99
|
+
|
100
|
+
|
101
|
+
#############################################################################
|
102
|
+
# Implementations of parts of the Servolux::Server API
|
103
|
+
#############################################################################
|
104
|
+
|
105
|
+
# this is run once before the Server's run loop
|
106
|
+
def before_starting
|
107
|
+
ExecChild.write_script
|
108
|
+
log "Starting up the Pool"
|
109
|
+
@pool.start( 1 )
|
110
|
+
log "Send a USR1 to add a worker (kill -usr1 #{Process.pid})"
|
111
|
+
log "Send a USR2 to kill all the workers (kill -usr2 #{Process.pid})"
|
112
|
+
log "Send a INT (Ctrl-C) or TERM to shutdown the server (kill -term #{Process.pid})"
|
113
|
+
end
|
114
|
+
|
115
|
+
# Add a worker to the pool when USR1 is received
|
116
|
+
def usr1
|
117
|
+
log "Adding a worker"
|
118
|
+
@pool.add_workers
|
119
|
+
end
|
120
|
+
|
121
|
+
# kill all the current workers with a usr2, the run loop will respawn up to
|
122
|
+
# the min_worker count
|
123
|
+
#
|
124
|
+
def usr2
|
125
|
+
shutdown_workers
|
126
|
+
end
|
127
|
+
|
128
|
+
# By default, Servolux::Server will capture the TERM signal and call its
|
129
|
+
# +shutdown+ method. After that +shutdown+ method is called it will call
|
130
|
+
# +after_shutdown+ we're going to hook into that so that all the workers get
|
131
|
+
# cleanly shutdown before the parent process exits
|
132
|
+
def after_stopping
|
133
|
+
shutdown_workers
|
134
|
+
ExecChild.remove_script
|
135
|
+
end
|
136
|
+
|
137
|
+
# This is the method that is executed during the run loop
|
138
|
+
#
|
139
|
+
def run
|
140
|
+
log_pool_status
|
141
|
+
@pool.each_worker do |worker|
|
142
|
+
log_worker_status( worker )
|
143
|
+
end
|
144
|
+
@pool.ensure_worker_pool_size
|
145
|
+
end
|
146
|
+
end
|
147
|
+
|
148
|
+
if $0 == __FILE__ then
|
149
|
+
pse = PreforkingServerExample.new
|
150
|
+
pse.startup
|
151
|
+
end
|
@@ -25,7 +25,7 @@ server.startup
|
|
25
25
|
# pool to be gracefully stopped and to be monitored by the server thread. This
|
26
26
|
# monitoring involves reaping child processes that have died and reporting on
|
27
27
|
# errors raised by children. It is also possible to respawn dead child
|
28
|
-
# workers, but this should be
|
28
|
+
# workers, but this should be thoroughly thought through (ha, unintentional
|
29
29
|
# alliteration) before doing so [if the CPU is thrashing, then respawning dead
|
30
30
|
# child workers will only contribute to the thrash].
|
31
31
|
module BeanstalkWorkerPool
|
@@ -38,7 +38,7 @@ module BeanstalkWorkerPool
|
|
38
38
|
|
39
39
|
# This run loop will be called at a fixed interval by the server thread. If
|
40
40
|
# the pool has any child processes that have died or restarted, then the
|
41
|
-
# expired PIDs are
|
41
|
+
# expired PIDs are read from the proc table. If any workers in the pool
|
42
42
|
# have reported an error, then display those errors on STDOUT; these are
|
43
43
|
# errors raised from the child process that caused the child to terminate.
|
44
44
|
def run
|
data/lib/servolux.rb
CHANGED
@@ -18,7 +18,7 @@ module Servolux
|
|
18
18
|
end
|
19
19
|
|
20
20
|
# Returns the library path for the module. If any arguments are given,
|
21
|
-
# they will be joined to the end of the
|
21
|
+
# they will be joined to the end of the library path using
|
22
22
|
# <tt>File.join</tt>.
|
23
23
|
#
|
24
24
|
# @return [String] absolute servolux 'lib' path
|
data/lib/servolux/child.rb
CHANGED
@@ -130,6 +130,18 @@ class Servolux::Child
|
|
130
130
|
# global variable $? is set to a Process::Status object containing
|
131
131
|
# information on the child process.
|
132
132
|
#
|
133
|
+
# You can get more information about how the child status exited by calling
|
134
|
+
# the following methods on the piper instance:
|
135
|
+
#
|
136
|
+
# * coredump?
|
137
|
+
# * exited?
|
138
|
+
# * signaled?
|
139
|
+
# * stopped?
|
140
|
+
# * success?
|
141
|
+
# * exitstatus
|
142
|
+
# * stopsig
|
143
|
+
# * termsig
|
144
|
+
#
|
133
145
|
# @param [Integer] flags Bit flags that will be passed to the system level
|
134
146
|
# wait call. See the Ruby core documentation for Process#wait for more
|
135
147
|
# information on these flags.
|
@@ -138,8 +150,7 @@ class Servolux::Child
|
|
138
150
|
#
|
139
151
|
def wait( flags = 0 )
|
140
152
|
return if @io.nil?
|
141
|
-
Process.
|
142
|
-
@status = $?
|
153
|
+
_, @status = Process.wait2(@pid, flags) unless @status
|
143
154
|
exitstatus
|
144
155
|
end
|
145
156
|
|
@@ -150,6 +161,7 @@ class Servolux::Child
|
|
150
161
|
#
|
151
162
|
def alive?
|
152
163
|
return if @io.nil?
|
164
|
+
wait(Process::WNOHANG|Process::WUNTRACED)
|
153
165
|
Process.kill(0, @pid)
|
154
166
|
true
|
155
167
|
rescue Errno::ESRCH, Errno::ENOENT
|
data/lib/servolux/daemon.rb
CHANGED
@@ -112,16 +112,16 @@ class Servolux::Daemon
|
|
112
112
|
# The time (in seconds) to wait for the daemon process to either startup
|
113
113
|
# or shutdown. An error is raised when this timeout is exceeded.
|
114
114
|
#
|
115
|
-
# @option opts [
|
115
|
+
# @option opts [Boolean] :nochdir (false)
|
116
116
|
# When set to true this flag directs the daemon process to keep the
|
117
117
|
# current working directory. By default, the process of daemonizing will
|
118
118
|
# cause the current working directory to be changed to the root folder
|
119
119
|
# (thus preventing the daemon process from holding onto the directory
|
120
120
|
# inode).
|
121
121
|
#
|
122
|
-
# @option opts [
|
122
|
+
# @option opts [Boolean] :noclose (false)
|
123
123
|
# When set to true this flag keeps the standard input/output streams from
|
124
|
-
# being
|
124
|
+
# being reopened to /dev/null when the daemon process is created. Reopening
|
125
125
|
# the standard input/output streams frees the file descriptors which are
|
126
126
|
# still being used by the parent process. This prevents zombie processes.
|
127
127
|
#
|
@@ -131,7 +131,7 @@ class Servolux::Daemon
|
|
131
131
|
#
|
132
132
|
# @option opts [String] :log_file (nil)
|
133
133
|
# This log file will be monitored to determine if the daemon process has
|
134
|
-
#
|
134
|
+
# successfully started.
|
135
135
|
#
|
136
136
|
# @option opts [String, Regexp] :look_for (nil)
|
137
137
|
# This can be either a String or a Regexp. It defines a phrase to search
|
@@ -186,7 +186,7 @@ class Servolux::Daemon
|
|
186
186
|
#
|
187
187
|
# If the startup command is a Proc or a bound Method then it is invoked
|
188
188
|
# using the +call+ method on the object. No arguments are passed to the
|
189
|
-
# +call+
|
189
|
+
# +call+ invocation.
|
190
190
|
#
|
191
191
|
# Lastly, if the startup command is a Servolux::Server then its +startup+
|
192
192
|
# method is called.
|
@@ -392,7 +392,7 @@ private
|
|
392
392
|
started = wait_for {
|
393
393
|
rv = started?
|
394
394
|
err = @piper.gets
|
395
|
-
raise StartupError, "Child raised error: #{err.inspect}" unless err.nil?
|
395
|
+
raise StartupError, "Child raised error: #{err.inspect}", err.backtrace unless err.nil?
|
396
396
|
rv
|
397
397
|
}
|
398
398
|
|
data/lib/servolux/piper.rb
CHANGED
@@ -2,13 +2,13 @@
|
|
2
2
|
require 'socket'
|
3
3
|
|
4
4
|
# == Synopsis
|
5
|
-
# A Piper is used to fork a child
|
5
|
+
# A Piper is used to fork a child process and then establish a communication
|
6
6
|
# pipe between the parent and child. This communication pipe is used to pass
|
7
7
|
# Ruby objects between the two.
|
8
8
|
#
|
9
9
|
# == Details
|
10
10
|
# When a new piper instance is created, the Ruby process is forked into two
|
11
|
-
#
|
11
|
+
# processes - the parent and the child. Each continues execution from the
|
12
12
|
# point of the fork. The piper establishes a pipe for communication between
|
13
13
|
# the parent and the child. This communication pipe can be opened as read /
|
14
14
|
# write / read-write (from the perspective of the parent).
|
@@ -124,6 +124,7 @@ class Servolux::Piper
|
|
124
124
|
raise ArgumentError, "Unsupported mode #{mode.inspect}"
|
125
125
|
end
|
126
126
|
|
127
|
+
@status = nil
|
127
128
|
@timeout = opts.fetch(:timeout, nil)
|
128
129
|
socket_pair = Socket.pair(Socket::AF_UNIX, Socket::SOCK_STREAM, 0)
|
129
130
|
@child_pid = Kernel.fork
|
@@ -207,7 +208,7 @@ class Servolux::Piper
|
|
207
208
|
end
|
208
209
|
end
|
209
210
|
|
210
|
-
# Returns +true+ if this is the child
|
211
|
+
# Returns +true+ if this is the child process and +false+ otherwise.
|
211
212
|
#
|
212
213
|
# @return [Boolean]
|
213
214
|
#
|
@@ -235,7 +236,7 @@ class Servolux::Piper
|
|
235
236
|
end
|
236
237
|
end
|
237
238
|
|
238
|
-
# Returns +true+ if this is the parent
|
239
|
+
# Returns +true+ if this is the parent process and +false+ otherwise.
|
239
240
|
#
|
240
241
|
# @return [Boolean]
|
241
242
|
#
|
@@ -309,11 +310,43 @@ class Servolux::Piper
|
|
309
310
|
# the child process.
|
310
311
|
#
|
311
312
|
def signal( sig )
|
312
|
-
return if
|
313
|
-
|
313
|
+
return if child?
|
314
|
+
return unless alive?
|
314
315
|
Process.kill(sig, @child_pid)
|
315
316
|
end
|
316
317
|
|
318
|
+
# Waits for the child process to exit and returns its exit status. The
|
319
|
+
# global variable $? is set to a Process::Status object containing
|
320
|
+
# information on the child process.
|
321
|
+
#
|
322
|
+
# Always returns +nil+ when called from the child process.
|
323
|
+
#
|
324
|
+
# You can get more information about how the child status exited by calling
|
325
|
+
# the following methods on the piper instance:
|
326
|
+
#
|
327
|
+
# * coredump?
|
328
|
+
# * exited?
|
329
|
+
# * signaled?
|
330
|
+
# * stopped?
|
331
|
+
# * success?
|
332
|
+
# * exitstatus
|
333
|
+
# * stopsig
|
334
|
+
# * termsig
|
335
|
+
#
|
336
|
+
# @param [Integer] flags Bit flags that will be passed to the system level
|
337
|
+
# wait call. See the Ruby core documentation for Process#wait for more
|
338
|
+
# information on these flags.
|
339
|
+
# @return [Integer, nil] The exit status of the child process or +nil+ if
|
340
|
+
# the child process is not running.
|
341
|
+
#
|
342
|
+
def wait( flags = 0 )
|
343
|
+
return if child?
|
344
|
+
_, @status = Process.wait2(@child_pid, flags) unless @status
|
345
|
+
exitstatus
|
346
|
+
rescue Errno::ECHILD
|
347
|
+
nil
|
348
|
+
end
|
349
|
+
|
317
350
|
# Returns +true+ if the child process is alive. Returns +nil+ if the child
|
318
351
|
# process has not been started.
|
319
352
|
#
|
@@ -322,12 +355,22 @@ class Servolux::Piper
|
|
322
355
|
# @return [Boolean, nil]
|
323
356
|
#
|
324
357
|
def alive?
|
325
|
-
return if
|
358
|
+
return if child?
|
359
|
+
wait(Process::WNOHANG|Process::WUNTRACED)
|
326
360
|
Process.kill(0, @child_pid)
|
327
361
|
true
|
328
362
|
rescue Errno::ESRCH, Errno::ENOENT
|
329
363
|
false
|
330
364
|
end
|
331
365
|
|
366
|
+
%w[coredump? exited? signaled? stopped? success? exitstatus stopsig termsig].
|
367
|
+
each { |method|
|
368
|
+
self.class_eval <<-CODE
|
369
|
+
def #{method}
|
370
|
+
return if @status.nil?
|
371
|
+
@status.#{method}
|
372
|
+
end
|
373
|
+
CODE
|
374
|
+
}
|
332
375
|
end
|
333
376
|
|
data/lib/servolux/prefork.rb
CHANGED
@@ -127,7 +127,7 @@
|
|
127
127
|
#
|
128
128
|
class Servolux::Prefork
|
129
129
|
|
130
|
-
|
130
|
+
CommunicationError = Class.new(::Servolux::Error)
|
131
131
|
UnknownSignal = Class.new(::Servolux::Error)
|
132
132
|
UnknownResponse = Class.new(::Servolux::Error)
|
133
133
|
|
@@ -138,8 +138,9 @@ class Servolux::Prefork
|
|
138
138
|
HEARTBEAT = "\000<3".freeze # @private
|
139
139
|
# :startdoc:
|
140
140
|
|
141
|
-
attr_accessor :timeout
|
142
|
-
|
141
|
+
attr_accessor :timeout # Communication timeout in seconds.
|
142
|
+
attr_accessor :min_workers # Minimum number of workers
|
143
|
+
attr_accessor :max_workers # Maximum number of workers
|
143
144
|
|
144
145
|
# call-seq:
|
145
146
|
# Prefork.new { block }
|
@@ -156,15 +157,22 @@ class Servolux::Prefork
|
|
156
157
|
# If you do not want to use the heartbeat then leave the :timeout unset or
|
157
158
|
# manually set it to +nil+.
|
158
159
|
#
|
160
|
+
# Additionally, :min_workers and :max_workers options are avilable. If
|
161
|
+
# :min_workers is given, the method +ensure_worker_pool_size+ will guarantee
|
162
|
+
# that at least :min_workers are up and running. If :max_workers is given,
|
163
|
+
# then +add_workers+ will NOT allow ou to spawn more workers than
|
164
|
+
# :max_workers.
|
165
|
+
#
|
159
166
|
# The pre-forking worker pool makes no effort to restart dead workers. It is
|
160
167
|
# left to the user to implement this functionality.
|
161
168
|
#
|
162
169
|
def initialize( opts = {}, &block )
|
163
170
|
@timeout = opts.fetch(:timeout, nil)
|
164
171
|
@module = opts.fetch(:module, nil)
|
172
|
+
@max_workers = opts.fetch(:max_workers, nil)
|
173
|
+
@min_workers = opts.fetch(:min_workers, nil)
|
165
174
|
@module = Module.new { define_method :execute, &block } if block
|
166
175
|
@workers = []
|
167
|
-
@harvest = Queue.new
|
168
176
|
|
169
177
|
raise ArgumentError, 'No code was given to execute by the workers.' unless @module
|
170
178
|
end
|
@@ -177,13 +185,8 @@ class Servolux::Prefork
|
|
177
185
|
#
|
178
186
|
def start( number )
|
179
187
|
@workers.clear
|
180
|
-
|
181
|
-
|
182
|
-
@workers << Worker.new(self)
|
183
|
-
@workers.last.extend @module
|
184
|
-
}
|
185
|
-
@workers.each { |worker| worker.start; pause }
|
186
|
-
self
|
188
|
+
add_workers( number )
|
189
|
+
self
|
187
190
|
end
|
188
191
|
|
189
192
|
# Stop all workers. The current process will wait for each child process to
|
@@ -204,17 +207,14 @@ class Servolux::Prefork
|
|
204
207
|
# @return [Prefork] self
|
205
208
|
#
|
206
209
|
def reap
|
207
|
-
|
208
|
-
pid = @harvest.pop
|
209
|
-
Process.wait pid rescue nil
|
210
|
-
end
|
210
|
+
@workers.each { |worker| worker.alive? }
|
211
211
|
self
|
212
212
|
end
|
213
213
|
|
214
214
|
# Send this given _signal_ to all child process. The default signal is
|
215
215
|
# 'TERM'. The method waits for a short period of time after the signal is
|
216
216
|
# sent to each child; this is done to alleviate a flood of signals being
|
217
|
-
# sent simultaneously and
|
217
|
+
# sent simultaneously and overwhelming the CPU.
|
218
218
|
#
|
219
219
|
# @param [String, Integer] signal The signal to send to child processes.
|
220
220
|
# @return [Prefork] self
|
@@ -228,7 +228,7 @@ class Servolux::Prefork
|
|
228
228
|
# call-seq:
|
229
229
|
# each_worker { |worker| block }
|
230
230
|
#
|
231
|
-
# Iterates over all the
|
231
|
+
# Iterates over all the workers and yields each, in turn, to the given
|
232
232
|
# _block_.
|
233
233
|
#
|
234
234
|
def each_worker( &block )
|
@@ -236,6 +236,70 @@ class Servolux::Prefork
|
|
236
236
|
self
|
237
237
|
end
|
238
238
|
|
239
|
+
# call-seq:
|
240
|
+
# add_workers( number = 1 )
|
241
|
+
#
|
242
|
+
# Adds additional workers to the pool. It will not add more workers than
|
243
|
+
# the number set in :max_workers
|
244
|
+
#
|
245
|
+
def add_workers( number = 1 )
|
246
|
+
number.times do
|
247
|
+
break if at_max_workers?
|
248
|
+
worker = Worker.new( self )
|
249
|
+
worker.extend @module
|
250
|
+
worker.start
|
251
|
+
@workers << worker
|
252
|
+
pause
|
253
|
+
end
|
254
|
+
end
|
255
|
+
|
256
|
+
# call-seq:
|
257
|
+
# prune_workers()
|
258
|
+
#
|
259
|
+
# Remove workers that are no longer alive from the worker pool
|
260
|
+
#
|
261
|
+
def prune_workers
|
262
|
+
new_workers = @workers.find_all { |w| w.alive? }
|
263
|
+
@workers = new_workers
|
264
|
+
end
|
265
|
+
|
266
|
+
# call-seq:
|
267
|
+
# ensure_worker_pool_size()
|
268
|
+
#
|
269
|
+
# Make sure that the worker pool has >= the minimum number of workers and less
|
270
|
+
# than the maximum number of workers.
|
271
|
+
#
|
272
|
+
# Generally, this means prune the number of workers and then spawn workers up
|
273
|
+
# to the min_worker level. If min is not set, then we only prune
|
274
|
+
#
|
275
|
+
def ensure_worker_pool_size
|
276
|
+
prune_workers
|
277
|
+
while below_minimum_workers? do
|
278
|
+
add_workers
|
279
|
+
end
|
280
|
+
end
|
281
|
+
|
282
|
+
# call-seq:
|
283
|
+
# below_minimum_workers?
|
284
|
+
#
|
285
|
+
# Report if the number of workers is below the minimum threshold
|
286
|
+
#
|
287
|
+
def below_minimum_workers?
|
288
|
+
return false unless @min_workers
|
289
|
+
return @workers.size < @min_workers
|
290
|
+
end
|
291
|
+
|
292
|
+
# call-seq:
|
293
|
+
# at_max_workers?
|
294
|
+
#
|
295
|
+
# Return true or false if we are currently at or above the maximum number of
|
296
|
+
# workers allowed.
|
297
|
+
#
|
298
|
+
def at_max_workers?
|
299
|
+
return false unless @max_workers
|
300
|
+
return @workers.size >= @max_workers
|
301
|
+
end
|
302
|
+
|
239
303
|
# call-seq:
|
240
304
|
# errors { |worker| block }
|
241
305
|
#
|
@@ -247,12 +311,40 @@ class Servolux::Prefork
|
|
247
311
|
self
|
248
312
|
end
|
249
313
|
|
314
|
+
# call-seq:
|
315
|
+
# worker_counts -> { :alive => 2, :dead => 1 }
|
316
|
+
#
|
317
|
+
# Returns a hash containing the counts of alive and dead workers
|
318
|
+
def worker_counts
|
319
|
+
counts = { :alive => 0, :dead => 0 }
|
320
|
+
each_worker do |worker|
|
321
|
+
state = worker.alive? ? :alive : :dead
|
322
|
+
counts[state] += 1
|
323
|
+
end
|
324
|
+
return counts
|
325
|
+
end
|
326
|
+
|
327
|
+
# call-seq:
|
328
|
+
# live_worker_count -> Integer
|
329
|
+
#
|
330
|
+
# Returns the number of live workers in the pool
|
331
|
+
def live_worker_count
|
332
|
+
worker_counts[:alive]
|
333
|
+
end
|
334
|
+
|
335
|
+
# call-seq:
|
336
|
+
# dead_worker_count -> Integer
|
337
|
+
#
|
338
|
+
# Returns the number of dead workers in the pool
|
339
|
+
def dead_worker_count
|
340
|
+
worker_counts[:dead]
|
341
|
+
end
|
250
342
|
|
251
343
|
private
|
252
344
|
|
253
345
|
# Pause script execution for a random time interval between 0.1 and 0.4
|
254
346
|
# seconds. This method is used to slow down the starting and stopping of
|
255
|
-
# child processes in order to
|
347
|
+
# child processes in order to avoid the "thundering herd" problem.
|
256
348
|
# http://en.wikipedia.org/wiki/Thundering_herd_problem
|
257
349
|
#
|
258
350
|
def pause
|
@@ -274,7 +366,6 @@ private
|
|
274
366
|
#
|
275
367
|
def initialize( prefork )
|
276
368
|
@timeout = prefork.timeout
|
277
|
-
@harvest = prefork.harvest
|
278
369
|
@thread = nil
|
279
370
|
@piper = nil
|
280
371
|
@error = nil
|
@@ -317,7 +408,7 @@ private
|
|
317
408
|
#
|
318
409
|
def wait
|
319
410
|
return if @piper.nil? or @piper.child?
|
320
|
-
|
411
|
+
@piper.wait(Process::WNOHANG|Process::WUNTRACED)
|
321
412
|
end
|
322
413
|
|
323
414
|
# Send this given _signal_ to the child process. The default signal is
|
@@ -356,9 +447,19 @@ private
|
|
356
447
|
#
|
357
448
|
def timed_out?
|
358
449
|
return if @piper.nil? or @piper.child?
|
359
|
-
|
450
|
+
CommunicationError === @error
|
360
451
|
end
|
361
452
|
|
453
|
+
%w[pid coredump? exited? signaled? stopped? success? exitstatus stopsig termsig].
|
454
|
+
each { |method|
|
455
|
+
self.class_eval <<-CODE
|
456
|
+
def #{method}
|
457
|
+
return if @piper.nil?
|
458
|
+
@piper.#{method}
|
459
|
+
end
|
460
|
+
CODE
|
461
|
+
}
|
462
|
+
|
362
463
|
private
|
363
464
|
|
364
465
|
def close_parent
|
@@ -378,7 +479,6 @@ private
|
|
378
479
|
rescue StandardError => err
|
379
480
|
@error = err
|
380
481
|
ensure
|
381
|
-
@harvest << @piper.pid
|
382
482
|
close_parent
|
383
483
|
start if START == response and !Thread.current[:stop]
|
384
484
|
end
|
@@ -390,6 +490,7 @@ private
|
|
390
490
|
response = nil
|
391
491
|
loop {
|
392
492
|
break if Thread.current[:stop]
|
493
|
+
break unless @piper.alive?
|
393
494
|
@piper.puts HEARTBEAT
|
394
495
|
response = @piper.gets(ERROR)
|
395
496
|
break if Thread.current[:stop]
|
@@ -398,8 +499,8 @@ private
|
|
398
499
|
when HEARTBEAT; next
|
399
500
|
when START; break
|
400
501
|
when ERROR
|
401
|
-
raise
|
402
|
-
"
|
502
|
+
raise CommunicationError,
|
503
|
+
"Unable to read data from Child process. Possible timeout, closing of pipe and/or child death."
|
403
504
|
when Exception
|
404
505
|
@error = response
|
405
506
|
break
|
@@ -416,7 +517,6 @@ private
|
|
416
517
|
# signals and communication with the parent.
|
417
518
|
#
|
418
519
|
def child
|
419
|
-
@harvest = nil
|
420
520
|
|
421
521
|
# if we get a HUP signal, then tell the parent process to stop this
|
422
522
|
# child process and start a new one to replace it
|
@@ -458,8 +558,8 @@ private
|
|
458
558
|
when HALT
|
459
559
|
break
|
460
560
|
when ERROR
|
461
|
-
raise
|
462
|
-
"
|
561
|
+
raise CommunicationError,
|
562
|
+
"Unable to read data from Parent process. Possible timeout, closing of pipe and/or parent death."
|
463
563
|
else
|
464
564
|
raise UnknownSignal,
|
465
565
|
"Child received unknown signal: #{signal.inspect}"
|
data/lib/servolux/server.rb
CHANGED
@@ -16,7 +16,7 @@ require 'thread'
|
|
16
16
|
# SIGINT and SIGTERM are handled by default. These signals will gracefully
|
17
17
|
# shutdown the server by calling the +shutdown+ method (provided by default,
|
18
18
|
# too). A few other signals can be handled by defining a few methods on your
|
19
|
-
# server instance. For example, SIGINT is
|
19
|
+
# server instance. For example, SIGINT is handled by the +int+ method (an
|
20
20
|
# alias for +shutdown+). Likewise, SIGTERM is handled by the +term+ method
|
21
21
|
# (another alias for +shutdown+). The following signal methods are
|
22
22
|
# recognized by the Server class:
|
@@ -43,7 +43,7 @@ require 'thread'
|
|
43
43
|
# shutdown: +before_stopping+ and +after_stopping+. The first is called just
|
44
44
|
# before the run loop thread is signaled for shutdown. The second is called
|
45
45
|
# just after the run loop thread has died; the +after_stopping+ method is
|
46
|
-
#
|
46
|
+
# guaranteed to NOT be called till after the run loop thread is well and
|
47
47
|
# truly dead.
|
48
48
|
#
|
49
49
|
# == Usage
|
data/lib/servolux/threaded.rb
CHANGED
@@ -1,6 +1,6 @@
|
|
1
1
|
|
2
2
|
# == Synopsis
|
3
|
-
# The Threaded module is used to
|
3
|
+
# The Threaded module is used to perform some activity at a specified
|
4
4
|
# interval.
|
5
5
|
#
|
6
6
|
# == Details
|
@@ -14,7 +14,7 @@
|
|
14
14
|
# Just before the thread is created the +before_starting+ method will be
|
15
15
|
# called (if it is defined by the threaded object). Likewise, after the
|
16
16
|
# thread is created the +after_starting+ method will be called (if it is
|
17
|
-
#
|
17
|
+
# defined by the threaded object).
|
18
18
|
#
|
19
19
|
# The threaded object is stopped by calling the +stop+ method. This sets an
|
20
20
|
# internal flag and then wakes up the thread. The thread gracefully exits
|
@@ -22,7 +22,7 @@
|
|
22
22
|
# are defined for stopping as well. Just before the thread is stopped the
|
23
23
|
# +before_stopping+ method will be called (if it is defined by the threaded
|
24
24
|
# object). Likewise, after the thread has died the +after_stopping+ method
|
25
|
-
# will be called (if it is
|
25
|
+
# will be called (if it is defined by the threaded object).
|
26
26
|
#
|
27
27
|
# Calling the +join+ method on a threaded object will cause the calling
|
28
28
|
# thread to wait until the threaded object has stopped. An optional timeout
|
@@ -35,7 +35,7 @@
|
|
35
35
|
module Servolux::Threaded
|
36
36
|
|
37
37
|
# This method will be called by the activity thread at the desired
|
38
|
-
# interval. Implementing classes are
|
38
|
+
# interval. Implementing classes are expect to provide this
|
39
39
|
# functionality.
|
40
40
|
#
|
41
41
|
def run
|
@@ -189,7 +189,7 @@ module Servolux::Threaded
|
|
189
189
|
_activity_thread.continue_on_error = (value ? true : false)
|
190
190
|
end
|
191
191
|
|
192
|
-
# Returns +true+ if the
|
192
|
+
# Returns +true+ if the threaded object should continue running even if an
|
193
193
|
# error is raised by the run method. The default is to return +false+. The
|
194
194
|
# threaded object will stop running when an error is raised.
|
195
195
|
#
|
data/spec/prefork_spec.rb
CHANGED
@@ -9,7 +9,7 @@ if Servolux.fork?
|
|
9
9
|
describe Servolux::Prefork do
|
10
10
|
|
11
11
|
def pids
|
12
|
-
workers.map! { |w| w.
|
12
|
+
workers.map! { |w| w.pid }
|
13
13
|
end
|
14
14
|
|
15
15
|
def workers
|
@@ -24,9 +24,11 @@ describe Servolux::Prefork do
|
|
24
24
|
end
|
25
25
|
|
26
26
|
def alive?( pid )
|
27
|
+
_, cstatus = Process.wait2( pid, Process::WNOHANG )
|
28
|
+
return false if cstatus
|
27
29
|
Process.kill(0, pid)
|
28
30
|
true
|
29
|
-
rescue Errno::ESRCH, Errno::ENOENT
|
31
|
+
rescue Errno::ESRCH, Errno::ENOENT, Errno::ECHILD
|
30
32
|
false
|
31
33
|
end
|
32
34
|
|
@@ -121,6 +123,78 @@ describe Servolux::Prefork do
|
|
121
123
|
pid.should_not == pids.last
|
122
124
|
end
|
123
125
|
|
126
|
+
it "starts up a stopped worker" do
|
127
|
+
@prefork = Servolux::Prefork.new :module => @worker
|
128
|
+
@prefork.start 2
|
129
|
+
ary = workers
|
130
|
+
sleep 0.250 until ary.all? { |w| w.alive? }
|
131
|
+
sleep 0.250 until worker_count >= 2
|
132
|
+
|
133
|
+
pid = pids.last
|
134
|
+
ary.last.signal 'TERM'
|
135
|
+
|
136
|
+
@prefork.reap until !alive? pid
|
137
|
+
@prefork.each_worker do |worker|
|
138
|
+
worker.start unless worker.alive?
|
139
|
+
end
|
140
|
+
sleep 0.250 until ary.all? { |w| w.alive? }
|
141
|
+
pid.should_not == pids.last
|
142
|
+
end
|
143
|
+
|
144
|
+
it "adds a new worker to the worker pool" do
|
145
|
+
@prefork = Servolux::Prefork.new :module => @worker
|
146
|
+
@prefork.start 2
|
147
|
+
ary = workers
|
148
|
+
sleep 0.250 until ary.all? { |w| w.alive? }
|
149
|
+
sleep 0.250 until worker_count >= 2
|
150
|
+
|
151
|
+
|
152
|
+
@prefork.add_workers( 2 )
|
153
|
+
sleep 0.250 until worker_count >= 4
|
154
|
+
workers.size.should == 4
|
155
|
+
end
|
156
|
+
|
157
|
+
it "only adds workers up to the max_workers value" do
|
158
|
+
@prefork = Servolux::Prefork.new :module => @worker, :max_workers => 3
|
159
|
+
@prefork.start 2
|
160
|
+
ary = workers
|
161
|
+
sleep 0.250 until ary.all? { |w| w.alive? }
|
162
|
+
sleep 0.250 until worker_count >= 2
|
163
|
+
|
164
|
+
@prefork.add_workers( 2 )
|
165
|
+
sleep 0.250 until worker_count >= 3
|
166
|
+
workers.size.should == 3
|
167
|
+
end
|
168
|
+
|
169
|
+
it "prunes workers that are no longer running" do
|
170
|
+
@prefork = Servolux::Prefork.new :module => @worker
|
171
|
+
@prefork.start 2
|
172
|
+
ary = workers
|
173
|
+
sleep 0.250 until ary.all? { |w| w.alive? }
|
174
|
+
sleep 0.250 until worker_count >= 2
|
175
|
+
|
176
|
+
@prefork.add_workers( 2 )
|
177
|
+
sleep 0.250 until worker_count >= 3
|
178
|
+
workers.size.should be == 4
|
179
|
+
|
180
|
+
workers[0].stop
|
181
|
+
sleep 0.250 while workers[0].alive?
|
182
|
+
|
183
|
+
@prefork.prune_workers
|
184
|
+
workers.size.should be == 3
|
185
|
+
end
|
186
|
+
|
187
|
+
it "ensures that there are minimum number of workers" do
|
188
|
+
@prefork = Servolux::Prefork.new :module => @worker, :min_workers => 3
|
189
|
+
@prefork.start 1
|
190
|
+
ary = workers
|
191
|
+
sleep 0.250 until ary.all? { |w| w.alive? }
|
192
|
+
sleep 0.250 until worker_count >= 1
|
193
|
+
|
194
|
+
@prefork.ensure_worker_pool_size
|
195
|
+
sleep 0.250 until worker_count >= 3
|
196
|
+
workers.size.should be == 3
|
197
|
+
end
|
124
198
|
end
|
125
199
|
end # Servolux.fork?
|
126
200
|
|
data/spec/server_spec.rb
CHANGED
@@ -2,6 +2,12 @@
|
|
2
2
|
require File.expand_path('../spec_helper', __FILE__)
|
3
3
|
|
4
4
|
describe Servolux::Server do
|
5
|
+
|
6
|
+
def wait_until( seconds = 5 )
|
7
|
+
start = Time.now
|
8
|
+
sleep 0.250 until (Time.now - start) > seconds or yield
|
9
|
+
end
|
10
|
+
|
5
11
|
base = Class.new(Servolux::Server) do
|
6
12
|
def initialize( &block )
|
7
13
|
super('Test Server', :logger => Logging.logger['Servolux'], &block)
|
@@ -23,17 +29,17 @@ describe Servolux::Server do
|
|
23
29
|
test(?e, @server.pid_file).should be_false
|
24
30
|
|
25
31
|
t = Thread.new {@server.startup}
|
26
|
-
|
32
|
+
wait_until { @server.running? and t.status == 'sleep' }
|
27
33
|
test(?e, @server.pid_file).should be_true
|
28
34
|
|
29
35
|
@server.shutdown
|
30
|
-
|
36
|
+
wait_until { t.status == false }
|
31
37
|
test(?e, @server.pid_file).should be_false
|
32
38
|
end
|
33
39
|
|
34
40
|
it 'generates a PID file with mode rw-r----- by default' do
|
35
41
|
t = Thread.new {@server.startup}
|
36
|
-
|
42
|
+
wait_until { @server.running? and t.status == 'sleep' }
|
37
43
|
test(?e, @server.pid_file).should be_true
|
38
44
|
|
39
45
|
@log_output.readline.chomp.should be == %q(DEBUG Servolux : Server "Test Server" creating pid file "test_server.pid")
|
@@ -41,14 +47,14 @@ describe Servolux::Server do
|
|
41
47
|
(File.stat(@server.pid_file).mode & 0777).should be == 0640
|
42
48
|
|
43
49
|
@server.shutdown
|
44
|
-
|
50
|
+
wait_until { t.status == false }
|
45
51
|
test(?e, @server.pid_file).should be_false
|
46
52
|
end
|
47
53
|
|
48
54
|
it 'generates PID file with the specified permissions' do
|
49
55
|
@server.pid_file_mode = 0400
|
50
56
|
t = Thread.new {@server.startup}
|
51
|
-
|
57
|
+
wait_until { @server.running? and t.status == 'sleep' }
|
52
58
|
test(?e, @server.pid_file).should be_true
|
53
59
|
|
54
60
|
@log_output.readline.chomp.should be == %q(DEBUG Servolux : Server "Test Server" creating pid file "test_server.pid")
|
@@ -56,18 +62,18 @@ describe Servolux::Server do
|
|
56
62
|
(File.stat(@server.pid_file).mode & 0777).should be == 0400
|
57
63
|
|
58
64
|
@server.shutdown
|
59
|
-
|
65
|
+
wait_until { t.status == false }
|
60
66
|
test(?e, @server.pid_file).should be_false
|
61
67
|
end
|
62
68
|
|
63
69
|
it 'shuts down gracefully when signaled' do
|
64
70
|
t = Thread.new {@server.startup}
|
65
|
-
|
66
|
-
@server.
|
71
|
+
wait_until { @server.running? and t.status == 'sleep' }
|
72
|
+
@server.should be_running
|
67
73
|
|
68
|
-
|
69
|
-
|
70
|
-
@server.
|
74
|
+
`kill -SIGINT #{$$}`
|
75
|
+
wait_until { t.status == false }
|
76
|
+
@server.should_not be_running
|
71
77
|
end
|
72
78
|
|
73
79
|
it 'responds to signals that have defined handlers' do
|
@@ -78,21 +84,31 @@ describe Servolux::Server do
|
|
78
84
|
end
|
79
85
|
|
80
86
|
t = Thread.new {@server.startup}
|
81
|
-
|
87
|
+
wait_until { @server.running? and t.status == 'sleep' }
|
82
88
|
@log_output.readline
|
83
|
-
|
84
|
-
|
85
|
-
|
86
|
-
|
87
|
-
|
88
|
-
|
89
|
-
|
90
|
-
|
91
|
-
|
92
|
-
|
93
|
-
|
94
|
-
|
95
|
-
|
89
|
+
@log_output.readline.strip.should be == 'DEBUG Servolux : Starting'
|
90
|
+
|
91
|
+
line = nil
|
92
|
+
Process.kill 'SIGUSR1', $$
|
93
|
+
wait_until { line = @log_output.readline }
|
94
|
+
line.should_not be_nil
|
95
|
+
line.strip.should be == 'INFO Servolux : usr1 was called'
|
96
|
+
|
97
|
+
line = nil
|
98
|
+
Process.kill 'SIGHUP', $$
|
99
|
+
wait_until { line = @log_output.readline }
|
100
|
+
line.should_not be_nil
|
101
|
+
line.strip.should be == 'INFO Servolux : hup was called'
|
102
|
+
|
103
|
+
line = nil
|
104
|
+
Process.kill 'SIGUSR2', $$
|
105
|
+
wait_until { line = @log_output.readline }
|
106
|
+
line.should_not be_nil
|
107
|
+
line.strip.should be == 'INFO Servolux : usr2 was called'
|
108
|
+
|
109
|
+
Process.kill 'SIGTERM', $$
|
110
|
+
wait_until { t.status == false }
|
111
|
+
@server.should_not be_running
|
96
112
|
end
|
97
113
|
end
|
98
114
|
|
data/version.txt
CHANGED
@@ -1 +1 @@
|
|
1
|
-
0.
|
1
|
+
0.10.0
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: servolux
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.10.0
|
5
5
|
prerelease:
|
6
6
|
platform: ruby
|
7
7
|
authors:
|
@@ -9,11 +9,11 @@ authors:
|
|
9
9
|
autorequire:
|
10
10
|
bindir: bin
|
11
11
|
cert_chain: []
|
12
|
-
date: 2012-
|
12
|
+
date: 2012-02-18 00:00:00.000000000Z
|
13
13
|
dependencies:
|
14
14
|
- !ruby/object:Gem::Dependency
|
15
15
|
name: bones-rspec
|
16
|
-
requirement: &
|
16
|
+
requirement: &2160654620 !ruby/object:Gem::Requirement
|
17
17
|
none: false
|
18
18
|
requirements:
|
19
19
|
- - ! '>='
|
@@ -21,32 +21,32 @@ dependencies:
|
|
21
21
|
version: 2.0.1
|
22
22
|
type: :development
|
23
23
|
prerelease: false
|
24
|
-
version_requirements: *
|
24
|
+
version_requirements: *2160654620
|
25
25
|
- !ruby/object:Gem::Dependency
|
26
26
|
name: bones-git
|
27
|
-
requirement: &
|
27
|
+
requirement: &2160654100 !ruby/object:Gem::Requirement
|
28
28
|
none: false
|
29
29
|
requirements:
|
30
30
|
- - ! '>='
|
31
31
|
- !ruby/object:Gem::Version
|
32
|
-
version: 1.2.
|
32
|
+
version: 1.2.5
|
33
33
|
type: :development
|
34
34
|
prerelease: false
|
35
|
-
version_requirements: *
|
35
|
+
version_requirements: *2160654100
|
36
36
|
- !ruby/object:Gem::Dependency
|
37
37
|
name: logging
|
38
|
-
requirement: &
|
38
|
+
requirement: &2160653660 !ruby/object:Gem::Requirement
|
39
39
|
none: false
|
40
40
|
requirements:
|
41
41
|
- - ! '>='
|
42
42
|
- !ruby/object:Gem::Version
|
43
|
-
version: 1.6.
|
43
|
+
version: 1.6.2
|
44
44
|
type: :development
|
45
45
|
prerelease: false
|
46
|
-
version_requirements: *
|
46
|
+
version_requirements: *2160653660
|
47
47
|
- !ruby/object:Gem::Dependency
|
48
48
|
name: bones
|
49
|
-
requirement: &
|
49
|
+
requirement: &2160653220 !ruby/object:Gem::Requirement
|
50
50
|
none: false
|
51
51
|
requirements:
|
52
52
|
- - ! '>='
|
@@ -54,13 +54,13 @@ dependencies:
|
|
54
54
|
version: 3.7.2
|
55
55
|
type: :development
|
56
56
|
prerelease: false
|
57
|
-
version_requirements: *
|
57
|
+
version_requirements: *2160653220
|
58
58
|
description: ! 'Serv-O-Lux is a collection of Ruby classes that are useful for daemon
|
59
59
|
and
|
60
60
|
|
61
61
|
process management, and for writing your own Ruby services. The code is well
|
62
62
|
|
63
|
-
documented and tested. It works with Ruby and JRuby
|
63
|
+
documented and tested. It works with Ruby and JRuby supporting both 1.8 and 1.9
|
64
64
|
|
65
65
|
interpreters.'
|
66
66
|
email: tim.pease@gmail.com
|
@@ -77,6 +77,7 @@ files:
|
|
77
77
|
- Rakefile
|
78
78
|
- examples/beanstalk.rb
|
79
79
|
- examples/echo.rb
|
80
|
+
- examples/preforking_server.rb
|
80
81
|
- examples/server_beanstalk.rb
|
81
82
|
- lib/servolux.rb
|
82
83
|
- lib/servolux/child.rb
|