servolux 0.7.1 → 0.8.0

Sign up to get free protection for your applications and to get access to all the features.
data/History.txt CHANGED
@@ -1,3 +1,13 @@
1
+ == 0.8.0 / 2009-11-12
2
+
3
+ * 2 Major Enhancements
4
+ * Preforking worker pool
5
+ * 1 Minor Enhancement
6
+ * Default return value for Piper#gets (useful for detecting timeouts)
7
+ * 2 Bug Fixes
8
+ * The piper is now using a socket pair for bidirectional communication
9
+ * The piper now defaults to a blocking mode (nil timeout)
10
+
1
11
  == 0.7.1 / 2009-11-06
2
12
 
3
13
  * 1 Bug Fix
data/Rakefile CHANGED
@@ -9,6 +9,7 @@ ensure_in_path 'lib'
9
9
  require 'servolux'
10
10
 
11
11
  task :default => 'spec:specdoc'
12
+ task 'gem:release' => ['spec:run', 'rubyforge:release']
12
13
 
13
14
  Bones {
14
15
  name 'servolux'
@@ -20,11 +21,13 @@ Bones {
20
21
  ignore_file '.gitignore'
21
22
 
22
23
  spec.opts << '--color'
24
+ rubyforge.name 'codeforpeople'
23
25
 
24
26
  use_gmail
25
27
  enable_sudo
26
28
 
27
29
  depend_on 'bones-extras', :development => true
30
+ depend_on 'bones-git', :development => true
28
31
  depend_on 'logging', :development => true
29
32
  depend_on 'rspec', :development => true
30
33
  }
data/a.rb ADDED
@@ -0,0 +1,31 @@
1
+
2
+ $LOAD_PATH.unshift 'lib'
3
+
4
+ require 'servolux'
5
+
6
+ STDOUT.sync = true
7
+
8
+
9
+ module DoThis
10
+ def before_executing
11
+ @fd = File.open("#$$.txt", 'w')
12
+ end
13
+
14
+ def after_executing
15
+ @fd.close
16
+ end
17
+
18
+ def execute
19
+ @fd.puts "Process #$$ @ #{Time.now}"
20
+ sleep 5
21
+ end
22
+ end
23
+
24
+ p = Servolux::Prefork.new :module => DoThis, :timeout => 0.5
25
+ p.start 6
26
+ sleep 10
27
+ p.stop
28
+
29
+ p.errors { |e|
30
+ puts e.inspect
31
+ }
data/b.rb ADDED
@@ -0,0 +1,17 @@
1
+
2
+ $LOAD_PATH.unshift 'lib'
3
+
4
+ require 'servolux'
5
+ STDOUT.sync = true
6
+
7
+ p = Servolux::Prefork.new {
8
+ puts "Process #$$ @ #{Time.now}"
9
+ sleep
10
+ }
11
+ p.start 12
12
+ sleep 30
13
+ p.stop
14
+
15
+ p.errors { |e|
16
+ puts e.inspect
17
+ }
@@ -0,0 +1,65 @@
1
+ # Preforking Beanstalkd job runner using Servolux.
2
+ #
3
+ # In this example, we prefork 7 processes each of which connect to our
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 exectuing and then
6
+ # disconnect from the beanstalk queue after exiting. These methods are called
7
+ # exactly once per child process.
8
+ #
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 advantagous because
11
+ # now you can send SIGHUP to a child process and it will restart, loading your
12
+ # Ruby libraries before executing. Now you can do a rolling deploy of new
13
+ # code.
14
+ #
15
+ # def before_executing
16
+ # Kernel.load '/your/source/code.rb'
17
+ # @job_runner = Your::Source::Code::JobRunner.new
18
+ # end
19
+ # --------
20
+
21
+ require 'servolux'
22
+ require 'beanstalk-client'
23
+
24
+ module JobProcessor
25
+ # Open a connection to our beanstalk queue
26
+ def before_executing
27
+ @beanstalk = Beanstalk::Pool.new(['localhost:11300'])
28
+ end
29
+
30
+ # Close the connection to our beanstalk queue
31
+ def after_executing
32
+ @beanstalk.close
33
+ end
34
+
35
+ # Reserve a job from the beanstalk queue, and processes jobs as we receive
36
+ # them. We have a timeout set for 2 minutes so that we can send a heartbeat
37
+ # back to the parent process even if the beanstalk queue is empty.
38
+ def execute
39
+ job = @beanstalk.reserve(120)
40
+ if job
41
+ # process job here ...
42
+ job.delete
43
+ end
44
+ end
45
+ end
46
+
47
+ # Create our preforking worker pool. Each worker will run the code found in
48
+ # the JobProcessor module. We set a timeout of 10 minutes. The child process
49
+ # must send a "heartbeat" message to the parent within this timeout period;
50
+ # otherwise, the parent will halt the child process.
51
+ #
52
+ # Our execute code in the JobProcessor takes this into account. It will wakeup
53
+ # every 2 minutes, if no jobs are reserved from the beanstalk queue, and send
54
+ # the heartbeat message.
55
+ #
56
+ # This also means that if any job processed by a worker takes longer than 10
57
+ # minutes to run, that child worker will be killed.
58
+ pool = Servolux::Prefork.new(:timeout => 600, :module => JobProcessor)
59
+
60
+ # Start up 7 child processes to handle jobs
61
+ pool.start 7
62
+
63
+ # Stop when SIGINT is received.
64
+ trap('INT') { pool.stop }
65
+ Process.waitall
data/examples/echo.rb ADDED
@@ -0,0 +1,52 @@
1
+ # Pre-forking echo server using Servolux
2
+ #
3
+ # Run this code using "ruby echo.rb"
4
+ #
5
+ # You can test the server using NetCat from a separate terminal window.
6
+ #
7
+ # echo "hello world" | nc localhost 4242
8
+ #
9
+ # This example was stolen from Ryan Tomayko and modified to demonstrate the
10
+ # Servolux gem. The original can be found here:
11
+ #
12
+ # http://tomayko.com/writings/unicorn-is-unix
13
+ # --------
14
+
15
+ require 'servolux'
16
+
17
+ # Create a socket, bind it to localhost:4242, and start listening.
18
+ # Runs once in the parent; all forked children inherit the socket's
19
+ # file descriptor.
20
+ acceptor = Socket.new(Socket::AF_INET, Socket::SOCK_STREAM, 0)
21
+ address = Socket.pack_sockaddr_in(4242, '0.0.0.0')
22
+ acceptor.bind(address)
23
+ acceptor.listen(10)
24
+
25
+ # Close the socket when we exit the parent or any child process. This
26
+ # only closes the file descriptor in the calling process, it does not
27
+ # take the socket out of the listening state (until the last fd is
28
+ # closed).
29
+ #
30
+ # The trap is guaranteed to happen, and guaranteed to happen only
31
+ # once, right before the process exits for any reason (unless
32
+ # it's terminated with a SIGKILL).
33
+ trap('EXIT') { acceptor.close }
34
+
35
+ # Create the worker pool passing in the code to execute in each child
36
+ # process.
37
+ pool = Servolux::Prefork.new {
38
+ socket, addr = acceptor.accept
39
+ socket.write "child #$$ echo> "
40
+ socket.flush
41
+ message = socket.gets
42
+ socket.write message
43
+ socket.close
44
+ puts "child #$$ echo'd: '#{message.strip}'"
45
+ }
46
+
47
+ # Start up 3 child process to handle echo requests on the socket.
48
+ pool.start 3
49
+
50
+ # Stop the child processes when SIGINT is received.
51
+ trap('INT') { pool.stop }
52
+ Process.waitall
@@ -1,4 +1,6 @@
1
1
 
2
+ require 'socket'
3
+
2
4
  # == Synopsis
3
5
  # A Piper is used to fork a child proces and then establish a communication
4
6
  # pipe between the parent and child. This communication pipe is used to pass
@@ -47,7 +49,7 @@
47
49
  class Servolux::Piper
48
50
 
49
51
  # :stopdoc:
50
- SEPERATOR = [0xDEAD, 0xBEEF].pack('n*').freeze
52
+ SIZEOF_INT = [42].pack('I').size
51
53
  # :startdoc:
52
54
 
53
55
  # call-seq:
@@ -90,12 +92,6 @@ class Servolux::Piper
90
92
  # The timeout in seconds to wait for puts / gets commands.
91
93
  attr_accessor :timeout
92
94
 
93
- # The read end of the pipe.
94
- attr_reader :read_io
95
-
96
- # The write end of the pipe.
97
- attr_reader :write_io
98
-
99
95
  # call-seq:
100
96
  # Piper.new( mode = 'r', opts = {} )
101
97
  #
@@ -111,7 +107,10 @@ class Servolux::Piper
111
107
  # rw read-write read-write
112
108
  #
113
109
  # The communication timeout can be provided as an option. This is the
114
- # number of seconds to wait for a +puts+ or +gets+ to succeed.
110
+ # number of seconds to wait for a +puts+ or +gets+ to succeed. This timeout
111
+ # will default to +nil+ such that calls through the pipe will block forever
112
+ # until data is available. You can configure the +puts+ and +gets+ to be
113
+ # non-blocking by setting the timeout to +0+.
115
114
  #
116
115
  def initialize( *args )
117
116
  opts = args.last.is_a?(Hash) ? args.pop : {}
@@ -121,55 +120,42 @@ class Servolux::Piper
121
120
  raise ArgumentError, "Unsupported mode #{mode.inspect}"
122
121
  end
123
122
 
124
- @timeout = opts[:timeout] || 0
125
- if defined? ::Encoding
126
- @read_io, @write_io = IO.pipe('ASCII-8BIT') # encoding for Ruby 1.9
127
- else
128
- @read_io, @write_io = IO.pipe
129
- end
123
+ @timeout = opts.key?(:timeout) ? opts[:timeout] : nil
124
+ socket_pair = Socket.pair(Socket::AF_UNIX, Socket::SOCK_STREAM, 0)
130
125
  @child_pid = Kernel.fork
131
126
 
132
127
  if child?
128
+ @socket = socket_pair[1]
129
+ socket_pair[0].close
130
+
133
131
  case mode
134
- when 'r'; close_read
135
- when 'w'; close_write
132
+ when 'r'; @socket.close_read
133
+ when 'w'; @socket.close_write
136
134
  end
137
135
  else
136
+ @socket = socket_pair[0]
137
+ socket_pair[1].close
138
+
138
139
  case mode
139
- when 'r'; close_write
140
- when 'w'; close_read
140
+ when 'r'; @socket.close_write
141
+ when 'w'; @socket.close_read
141
142
  end
142
143
  end
143
144
  end
144
145
 
145
- # Close both the read and write ends of the communications pipe. This only
146
- # affects the process from which it was called -- the parent or the child.
146
+ # Close both the communications socket. This only affects the process from
147
+ # which it was called -- the parent or the child.
147
148
  #
148
149
  def close
149
- @read_io.close rescue nil
150
- @write_io.close rescue nil
151
- end
152
-
153
- # Close the read end of the communications pipe. This only affects the
154
- # process from which it was called -- the parent or the child.
155
- #
156
- def close_read
157
- @read_io.close rescue nil
158
- end
159
-
160
- # Close the write end of the communications pipe. This only affects the
161
- # process from which it was called -- the parent or the child.
162
- #
163
- def close_write
164
- @write_io.close rescue nil
150
+ @socket.close rescue nil
165
151
  end
166
152
 
167
153
  # Returns +true+ if the communications pipe is readable from the process
168
154
  # and there is data waiting to be read.
169
155
  #
170
156
  def readable?
171
- return false if @read_io.closed?
172
- r,w,e = Kernel.select([@read_io], nil, nil, @timeout)
157
+ return false if @socket.closed?
158
+ r,w,e = Kernel.select([@socket], nil, nil, @timeout)
173
159
  return !(r.nil? or r.empty?)
174
160
  end
175
161
 
@@ -177,8 +163,8 @@ class Servolux::Piper
177
163
  # and the write buffer can accept more data.
178
164
  #
179
165
  def writeable?
180
- return false if @write_io.closed?
181
- r,w,e = Kernel.select(nil, [@write_io], nil, @timeout)
166
+ return false if @socket.closed?
167
+ r,w,e = Kernel.select(nil, [@socket], nil, @timeout)
182
168
  return !(w.nil? or w.empty?)
183
169
  end
184
170
 
@@ -237,22 +223,27 @@ class Servolux::Piper
237
223
  @child_pid
238
224
  end
239
225
 
240
- # Read an object from the communication pipe. Returns +nil+ if the pipe is
241
- # closed for reading or if no data is available before the timeout
242
- # expires. If data is available then it is un-marshalled and returned as a
243
- # Ruby object.
226
+ # Read an object from the communication pipe. If data is available then it
227
+ # is un-marshalled and returned as a Ruby object. If the pipe is closed for
228
+ # reading or if no data is available then the _default_ value is returned.
229
+ # You can pass in the _default_ value; otherwise it will be +nil+.
244
230
  #
245
231
  # This method will block until the +timeout+ is reached or data can be
246
232
  # read from the pipe.
247
233
  #
248
- def gets
249
- return unless readable?
234
+ def gets( default = nil )
235
+ return default unless readable?
250
236
 
251
- data = @read_io.gets SEPERATOR
252
- return if data.nil?
237
+ data = @socket.read SIZEOF_INT
238
+ return default if data.nil?
239
+
240
+ size = data.unpack('I').first
241
+ data = @socket.read size
242
+ return default if data.nil?
253
243
 
254
- data.chomp! SEPERATOR
255
244
  Marshal.load(data) rescue data
245
+ rescue SystemCallError
246
+ return default
256
247
  end
257
248
 
258
249
  # Write an object to the communication pipe. Returns +nil+ if the pipe is
@@ -267,16 +258,15 @@ class Servolux::Piper
267
258
  def puts( obj )
268
259
  return unless writeable?
269
260
 
270
- bytes = @write_io.write Marshal.dump(obj)
271
- @write_io.write SEPERATOR if bytes > 0
272
- @write_io.flush
273
-
274
- bytes
261
+ data = Marshal.dump(obj)
262
+ @socket.write([data.size].pack('I')) + @socket.write(data)
263
+ rescue SystemCallError
264
+ return nil
275
265
  end
276
266
 
277
267
  # Send the given signal to the child process. The signal may be an integer
278
268
  # signal number or a POSIX signal name (either with or without a +SIG+
279
- # prefix).
269
+ # prefix).
280
270
  #
281
271
  # This method does nothing when called from the child process.
282
272
  #
@@ -286,6 +276,18 @@ class Servolux::Piper
286
276
  Process.kill(sig, @child_pid)
287
277
  end
288
278
 
279
+ # Returns +true+ if the child process is alive. Returns +nil+ if the child
280
+ # process has not been started.
281
+ #
282
+ # Always returns +nil+ when called from the child process.
283
+ #
284
+ def alive?
285
+ return if @child_pid.nil?
286
+ Process.kill(0, @child_pid)
287
+ true
288
+ rescue Errno::ESRCH, Errno::ENOENT
289
+ false
290
+ end
291
+
289
292
  end # class Servolux::Piper
290
293
 
291
- # EOF
@@ -0,0 +1,351 @@
1
+
2
+ # == Synopsis
3
+ # The Prefork class provides a pre-forking worker pool for executing tasks in
4
+ # parallel using multiple processes.
5
+ #
6
+ # == Details
7
+ # A pre-forking worker pool is a technique for executing code in parallel in a
8
+ # UNIX environment. Each worker in the pool forks a child process and then
9
+ # executes user supplied code in that child process. The child process can
10
+ # pull jobs from a queue (beanstalkd for example) or listen on a socket for
11
+ # network requests.
12
+ #
13
+ # The code to execute in the child processes is passed as a block to the
14
+ # Prefork initialize method. The child processes executes this code in a loop;
15
+ # that is, your code block should not worry about keeping itself alive. This
16
+ # is handled by the library.
17
+ #
18
+ # If your code raises an exception, it will be captured by the library code
19
+ # and marshalled back to the parent process. This will halt the child process.
20
+ # The Prefork worker pool does not restart dead workers. A method is provided
21
+ # to iterate over workers that have errors, and it is up to the user to handle
22
+ # errors as they please.
23
+ #
24
+ # Instead of passing a block to the initialize method, you can provide a Ruby
25
+ # module that defines an "execute" method. This method will be executed in the
26
+ # child process' run loop. When using a module, you also have the option of
27
+ # defining a "before_executing" method and an "after_executing" method. These
28
+ # methods will be called before the child starts the execute loop and after
29
+ # the execute loop finishes. Each method will be called exactly once. Both
30
+ # methods are optional.
31
+ #
32
+ # Sending a SIGHUP to a child process will cause that child to stop and
33
+ # restart. The child will send a signal to the parent asking to be shutdown.
34
+ # The parent will gracefully halt the child and then start a new child process
35
+ # to replace it.
36
+ #
37
+ # This has the advantage of calling your before/after_executing methods again
38
+ # and reloading any code or resources your worker code will use. The SIGHUP
39
+ # will call Thread#wakeup on the main child process thread; please write your
40
+ # code to respond accordingly to this wakeup call (a thread waiting on a
41
+ # Queue#pop will not return when wakeup is called on the thread).
42
+ #
43
+ # == Examples
44
+ #
45
+ # A pre-forking echo server: http://github.com/TwP/servolux/blob/master/examples/echo.rb
46
+ #
47
+ # Pulling jobs from a beanstalkd work queue: http://github.com/TwP/servolux/blob/master/examples/beanstalk.rb
48
+ #
49
+ # ==== Before / After Executing
50
+ # In this example, we are creating 42 worker processes that will log the
51
+ # process ID and the current time to a file. Each worker will do this every 2
52
+ # seconds. The before/after_executing methods are used to open the file before
53
+ # the run loop starts and to close the file after the run loop completes. The
54
+ # execute method uses the stored file descriptor when logging the message.
55
+ #
56
+ # module RunMe
57
+ # def before_executing
58
+ # @fd = File.open("#{Process.pid}.txt", 'w')
59
+ # end
60
+ #
61
+ # def after_executing
62
+ # @fd.close
63
+ # end
64
+ #
65
+ # def execute
66
+ # @fd.puts "Process #{Process.pid} @ #{Time.now}"
67
+ # sleep 2
68
+ # end
69
+ # end
70
+ #
71
+ # pool = Servolux::Prefork.new(:module => RunMe)
72
+ # pool.start 42
73
+ #
74
+ # ==== Heartbeat
75
+ # When a :timeout is supplied to the constructor, a "heartbeat" is setup
76
+ # between the parent and the child worker. Each loop through the child's
77
+ # execute code must return before :timeout seconds have elapsed. If one
78
+ # iteration through the loop takes longer than :timeout seconds, then the
79
+ # parent process will halt the child worker. An error will be raised in the
80
+ # parent process.
81
+ #
82
+ # pool = Servolux::Prefork.new(:timeout => 2) {
83
+ # puts "Process #{Process.pid} is running."
84
+ # sleep(rand * 5)
85
+ # }
86
+ # pool.start 42
87
+ #
88
+ # Eventually all 42 child processes will be killed by their parents. The
89
+ # random number generator will eventually cause the child to sleep longer than
90
+ # two seconds.
91
+ #
92
+ # What is happening here is that each time the child processes executes the
93
+ # block of code, the Servolux library code will send a "heartbeat" message to
94
+ # the parent. The parent is using a Kernel#select call on the communications
95
+ # pipe to wait for this message. The timeout is passed to the select call, and
96
+ # this will cause it to return +nil+ -- this is the error condition the
97
+ # heartbeat prevents.
98
+ #
99
+ # Use the heartbeat with caution -- allow margins for timing issues and
100
+ # processor load spikes.
101
+ #
102
+ class Servolux::Prefork
103
+
104
+ Timeout = Class.new(::Servolux::Error)
105
+ UnknownSignal = Class.new(::Servolux::Error)
106
+ UnknownResponse = Class.new(::Servolux::Error)
107
+
108
+ # :stopdoc:
109
+ START = "\000START".freeze
110
+ HALT = "\000HALT".freeze
111
+ ERROR = "\000SHIT".freeze
112
+ HEARTBEAT = "\000<3".freeze
113
+ # :startdoc:
114
+
115
+ attr_accessor :timeout # Communication timeout in seconds.
116
+
117
+ # call-seq:
118
+ # Prefork.new { block }
119
+ # Prefork.new( :module => Module )
120
+ #
121
+ # Create a new pre-forking worker pool. You must provide a block of code for
122
+ # the workers to execute in their child processes. This code block can be
123
+ # passed either as a block to this method or as a module via the :module
124
+ # option.
125
+ #
126
+ # If a :timeout is given, then each worker will setup a "heartbeat" between
127
+ # the parent process and the child process. If the child does not respond to
128
+ # the parent within :timeout seconds, then the child process will be halted.
129
+ # If you do not want to use the heartbeat then leave the :timeout unset or
130
+ # manually set it to +nil+.
131
+ #
132
+ # The pre-forking worker pool makes no effort to restart dead workers. It is
133
+ # left to the user to implement this functionality.
134
+ #
135
+ def initialize( opts = {}, &block )
136
+ @timeout = opts[:timeout]
137
+ @module = opts[:module]
138
+ @module = Module.new { define_method :execute, &block } if block
139
+ @workers = []
140
+
141
+ raise ArgumentError, 'No code was given to execute by the workers.' unless @module
142
+ end
143
+
144
+ # Start up the given _number_ of workers. Each worker will create a child
145
+ # process and run the user supplied code in that child process.
146
+ #
147
+ def start( number )
148
+ @workers.clear
149
+
150
+ number.times {
151
+ @workers << Worker.new(self)
152
+ @workers.last.extend @module
153
+ }
154
+ @workers.each { |worker| worker.start }
155
+ self
156
+ end
157
+
158
+ # Stop all workers. The current process will wait for each child process to
159
+ # exit before this method will return. The worker instances are not
160
+ # destroyed by this method; this means that the +each_worker+ and the
161
+ # +errors+ methods will still function correctly after stopping the workers.
162
+ #
163
+ def stop
164
+ @workers.each { |worker| worker.stop }
165
+ @workers.each { |worker| worker.wait }
166
+ self
167
+ end
168
+
169
+ # call-seq:
170
+ # each_worker { |worker| block }
171
+ #
172
+ # Iterates over all the works and yields each, in turn, to the given
173
+ # _block_.
174
+ #
175
+ def each_worker( &block )
176
+ @workers.each(&block)
177
+ self
178
+ end
179
+
180
+ # call-seq:
181
+ # errors { |worker| block }
182
+ #
183
+ # Iterates over all the works and yields the worker to the given _block_
184
+ # only if the worker has an error condition.
185
+ #
186
+ def errors
187
+ @workers.each { |worker| yield worker unless worker.error.nil? }
188
+ self
189
+ end
190
+
191
+ # The worker encapsulates the forking of the child process and communication
192
+ # between the parent and the child. Each worker instance is extended with
193
+ # the block or module supplied to the pre-forking pool that created the
194
+ # worker.
195
+ #
196
+ class Worker
197
+
198
+ attr_reader :prefork
199
+ attr_reader :error
200
+
201
+ # Create a new worker that belongs to the _prefork_ pool.
202
+ #
203
+ def initialize( prefork )
204
+ @prefork = prefork
205
+ @thread = nil
206
+ @piper = nil
207
+ @error = nil
208
+ end
209
+
210
+ # Start this worker. A new process will be forked, and the code supplied
211
+ # by the user to the prefork pool will be executed in the child process.
212
+ #
213
+ def start
214
+ @error = nil
215
+ @piper = ::Servolux::Piper.new('rw', :timeout => @prefork.timeout)
216
+ parent if @piper.parent?
217
+ child if @piper.child?
218
+ self
219
+ end
220
+
221
+ # Stop this worker. The internal worker thread is stopped and a 'HUP'
222
+ # signal is sent to the child process. This method will return immediately
223
+ # without waiting for the child process to exit. Use the +wait+ method
224
+ # after calling +stop+ if your code needs to know when the child exits.
225
+ #
226
+ def stop
227
+ return if @thread.nil? or @piper.nil? or @piper.child?
228
+
229
+ @thread[:stop] = true
230
+ @thread.wakeup
231
+ Thread.pass until !@thread.status
232
+ kill 'HUP'
233
+ @thread = nil
234
+ self
235
+ end
236
+
237
+ # Wait for the child process to exit. This method returns immediately when
238
+ # called from the child process or if the child process has not yet been
239
+ # forked.
240
+ #
241
+ def wait
242
+ return if @piper.nil? or @piper.child?
243
+ Process.wait(@piper.pid, Process::WNOHANG|Process::WUNTRACED)
244
+ end
245
+
246
+ # Send this given _signal_ to the child process. The default signal is
247
+ # 'TERM'. This method will return immediately.
248
+ #
249
+ def kill( signal = 'TERM' )
250
+ return if @piper.nil?
251
+ @piper.signal signal
252
+ rescue Errno::ESRCH, Errno::ENOENT
253
+ return nil
254
+ end
255
+
256
+ # Returns +true+ if the child process is alive. Returns +nil+ if the child
257
+ # process has not been started.
258
+ #
259
+ # Always returns +nil+ when called from the child process.
260
+ #
261
+ def alive?
262
+ return if @piper.nil?
263
+ @piper.alive?
264
+ end
265
+
266
+
267
+ private
268
+
269
+ # This code should only be executed in the parent process.
270
+ #
271
+ def parent
272
+ @thread = Thread.new {
273
+ response = nil
274
+ begin
275
+ @piper.puts START
276
+ Thread.current[:stop] = false
277
+ loop {
278
+ break if Thread.current[:stop]
279
+ @piper.puts HEARTBEAT
280
+ response = @piper.gets(ERROR)
281
+ break if Thread.current[:stop]
282
+
283
+ case response
284
+ when HEARTBEAT; next
285
+ when START; break
286
+ when ERROR
287
+ raise Timeout,
288
+ "Child did not respond in a timely fashion. Timeout is set to #{@prefork.timeout} seconds."
289
+ when Exception
290
+ raise response
291
+ else
292
+ raise UnknownResponse,
293
+ "Child returned unknown response: #{response.inspect}"
294
+ end
295
+ }
296
+ rescue Exception => err
297
+ @error = err
298
+ ensure
299
+ @piper.timeout = 0
300
+ @piper.puts HALT rescue nil
301
+ @piper.close
302
+ self.start if START == response
303
+ end
304
+ }
305
+ Thread.pass until @thread[:stop] == false
306
+ end
307
+
308
+ # This code should only be executed in the child process. It wraps the
309
+ # user supplied "execute" code in a loop, and takes care of handling
310
+ # signals and communication with the parent.
311
+ #
312
+ def child
313
+ @thread = Thread.current
314
+
315
+ # if we get a HUP signal, then tell the parent process to stop this
316
+ # child process and start a new one to replace it
317
+ Signal.trap('HUP') {
318
+ @piper.puts START rescue nil
319
+ @thread.wakeup
320
+ }
321
+
322
+ before_executing if self.respond_to? :before_executing
323
+ :wait until @piper.gets == START
324
+
325
+ loop {
326
+ signal = @piper.gets(ERROR)
327
+ case signal
328
+ when HEARTBEAT
329
+ execute
330
+ @piper.puts HEARTBEAT
331
+ when HALT
332
+ break
333
+ when ERROR
334
+ raise Timeout,
335
+ "Parent did not respond in a timely fashion. Timeout is set to #{@prefork.timeout} seconds."
336
+ else
337
+ raise UnknownSignal,
338
+ "Child received unknown signal: #{signal.inspect}"
339
+ end
340
+ }
341
+ after_executing if self.respond_to? :after_executing
342
+ rescue Exception => err
343
+ @piper.puts err rescue nil
344
+ ensure
345
+ @piper.close
346
+ exit!
347
+ end
348
+ end # class Worker
349
+
350
+ end # class Servolux::Prefork
351
+
data/lib/servolux.rb CHANGED
@@ -2,7 +2,7 @@
2
2
  module Servolux
3
3
 
4
4
  # :stopdoc:
5
- VERSION = '0.7.1'
5
+ VERSION = '0.8.0'
6
6
  LIBPATH = ::File.expand_path(::File.dirname(__FILE__)) + ::File::SEPARATOR
7
7
  PATH = ::File.dirname(LIBPATH) + ::File::SEPARATOR
8
8
  # :startdoc:
@@ -40,8 +40,7 @@ module Servolux
40
40
 
41
41
  end # module Servolux
42
42
 
43
- %w[threaded server piper daemon child].each do |lib|
43
+ %w[threaded server piper daemon child prefork].each do |lib|
44
44
  require Servolux.libpath('servolux', lib)
45
45
  end
46
46
 
47
- # EOF
data/spec/piper_spec.rb CHANGED
@@ -11,7 +11,7 @@ describe Servolux::Piper do
11
11
 
12
12
  after :each do
13
13
  next if @piper.nil?
14
- @piper.puts :die
14
+ @piper.puts :die rescue nil
15
15
  @piper.close
16
16
  @piper = nil
17
17
  end
@@ -40,6 +40,7 @@ describe Servolux::Piper do
40
40
  end
41
41
  @piper.puts obj unless obj.nil?
42
42
  }
43
+ exit!
43
44
  }
44
45
 
45
46
  @piper.parent {
@@ -65,7 +66,7 @@ describe Servolux::Piper do
65
66
  end
66
67
 
67
68
  it 'sends signals from parent to child' do
68
- @piper = Servolux::Piper.new :timeout => 2
69
+ @piper = Servolux::Piper.new 'rw', :timeout => 2
69
70
 
70
71
  @piper.child {
71
72
  Signal.trap('USR2') { @piper.puts "'USR2' was received" rescue nil }
@@ -77,6 +78,7 @@ describe Servolux::Piper do
77
78
  Thread.new { sleep 7; exit! }
78
79
  @piper.puts :ready
79
80
  loop { sleep }
81
+ exit!
80
82
  }
81
83
 
82
84
  @piper.parent {
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.7.1
4
+ version: 0.8.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Tim Pease
@@ -9,13 +9,23 @@ autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
11
 
12
- date: 2009-11-06 00:00:00 -07:00
12
+ date: 2009-11-12 00:00:00 -07:00
13
13
  default_executable:
14
14
  dependencies:
15
15
  - !ruby/object:Gem::Dependency
16
16
  name: bones-extras
17
17
  type: :development
18
18
  version_requirement:
19
+ version_requirements: !ruby/object:Gem::Requirement
20
+ requirements:
21
+ - - ">="
22
+ - !ruby/object:Gem::Version
23
+ version: 1.1.0
24
+ version:
25
+ - !ruby/object:Gem::Dependency
26
+ name: bones-git
27
+ type: :development
28
+ version_requirement:
19
29
  version_requirements: !ruby/object:Gem::Requirement
20
30
  requirements:
21
31
  - - ">="
@@ -50,7 +60,7 @@ dependencies:
50
60
  requirements:
51
61
  - - ">="
52
62
  - !ruby/object:Gem::Version
53
- version: 3.0.0
63
+ version: 3.0.1
54
64
  version:
55
65
  description: |-
56
66
  Serv-O-Lux is a collection of Ruby classes that are useful for daemon and
@@ -69,10 +79,15 @@ files:
69
79
  - History.txt
70
80
  - README.rdoc
71
81
  - Rakefile
82
+ - a.rb
83
+ - b.rb
84
+ - examples/beanstalk.rb
85
+ - examples/echo.rb
72
86
  - lib/servolux.rb
73
87
  - lib/servolux/child.rb
74
88
  - lib/servolux/daemon.rb
75
89
  - lib/servolux/piper.rb
90
+ - lib/servolux/prefork.rb
76
91
  - lib/servolux/server.rb
77
92
  - lib/servolux/threaded.rb
78
93
  - spec/child_spec.rb
@@ -105,7 +120,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
105
120
  version:
106
121
  requirements: []
107
122
 
108
- rubyforge_project: servolux
123
+ rubyforge_project: codeforpeople
109
124
  rubygems_version: 1.3.5
110
125
  signing_key:
111
126
  specification_version: 3