servolux 0.9.7 → 0.10.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.
- 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
|