raemon 0.1.2 → 0.2.0

Sign up to get free protection for your applications and to get access to all the features.
data/VERSION CHANGED
@@ -1 +1 @@
1
- 0.1.2
1
+ 0.2.0
@@ -17,13 +17,10 @@ class JobWorker
17
17
  logger.info "=> Stopping worker #{Process.pid}"
18
18
 
19
19
  @beanstalk.close
20
- exit
21
20
  end
22
21
 
23
- def execute
22
+ def run
24
23
  loop do
25
- stop if shutting_down?
26
-
27
24
  begin
28
25
  job = @beanstalk.reserve(2)
29
26
  rescue Beanstalk::TimedOut
@@ -35,17 +32,18 @@ class JobWorker
35
32
  # process job here ...
36
33
  job.delete
37
34
  end
35
+
36
+ heartbeat!
38
37
  end
39
38
  end
40
-
41
39
  end
42
40
 
43
41
  ROOT_DIR = File.expand_path('~')
44
42
 
45
- # Raemon::Master.startup 3, JobWorker, {
43
+ # Raemon::Master.start 3, JobWorker, {
46
44
  # :detach => true,
47
45
  # :logger => Logger.new("#{ROOT_DIR}/beanstalk.log"),
48
46
  # :pid_file => "#{ROOT_DIR}/beanstalk.pid"
49
47
  # }
50
48
 
51
- Raemon::Master.startup 3, JobWorker
49
+ Raemon::Master.start 3, JobWorker
data/examples/evented.rb CHANGED
@@ -15,11 +15,10 @@ class EventedJobWorker
15
15
  logger.info "=> Stopping worker #{Process.pid}"
16
16
 
17
17
  EM.stop_event_loop if EM.reactor_running?
18
- exit
19
18
  end
20
19
 
21
- def execute
22
- EventMachine.run do
20
+ def run
21
+ EM.run do
23
22
  @queue = EMJack::Connection.new
24
23
 
25
24
  @queue.each_job(5) do |job|
@@ -27,21 +26,23 @@ class EventedJobWorker
27
26
  # process(job)
28
27
  @queue.delete(job)
29
28
 
30
- stop if shutting_down?
29
+ heartbeat!
31
30
  end
32
31
 
33
32
  @queue.on_error do |error|
34
33
  case error
35
34
  when :timed_out
36
- # We use the reserve timeout to check if we should shutdown
37
- stop if shutting_down?
35
+ # We use the reserve timeout for the heartbeat
36
+ heartbeat!
37
+
38
+ # Note: this will only run once.. we need to call the @queue.each_job
39
+ # again .. maybe put it in a block
38
40
  else
39
41
  logger.error error.to_s
40
42
  end
41
43
  end
42
44
  end
43
45
  end
44
-
45
46
  end
46
47
 
47
- Raemon::Master.startup 2, EventedJobWorker
48
+ Raemon::Master.start 2, EventedJobWorker
data/examples/queue.rb ADDED
@@ -0,0 +1,14 @@
1
+ # Use the code below to put some jobs onto the queue to test beanstalk.rb
2
+ # and evented.rb examples
3
+
4
+ require 'rubygems'
5
+ require 'beanstalk-client'
6
+
7
+ @queue = Beanstalk::Pool.new(['localhost:11300'])
8
+
9
+ num_jobs = 20
10
+
11
+ (0...num_jobs).each do |id|
12
+ job = "Job#{id}"
13
+ @queue.put job
14
+ end
@@ -9,6 +9,8 @@ Raemon::Server.run do |config|
9
9
  config.name = 'Sampled'
10
10
  config.worker_klass = 'Sampled::Worker'
11
11
  config.num_workers = 1
12
+ config.timeout = 60
13
+ config.memory_limit = 50 # in MB
12
14
 
13
15
  config.log_level = :info
14
16
  end
@@ -4,18 +4,15 @@ module Sampled
4
4
  include Raemon::Worker
5
5
 
6
6
  def start
7
- logger.info "Start .. #{Process.ppid}:#{Process.pid}"
7
+ logger.info "=> Starting worker #{Process.pid}"
8
8
  end
9
9
 
10
10
  def stop
11
11
  logger.info "=> Stopping worker #{Process.pid}"
12
- exit
13
12
  end
14
13
 
15
- def execute
14
+ def run
16
15
  loop do
17
- stop if shutting_down?
18
-
19
16
  logger.warn "I'm executing .. #{Process.ppid}:#{Process.pid}"
20
17
  sleep 2
21
18
  end
File without changes
data/examples/test.rb CHANGED
@@ -12,18 +12,25 @@ class Test
12
12
 
13
13
  def stop
14
14
  logger.info "=> Stopping worker #{Process.pid}"
15
- exit
16
15
  end
17
16
 
18
- def execute
19
- loop do
20
- stop if shutting_down?
17
+ def run
18
+ x = 0
21
19
 
22
- logger.warn "I'm executing .. #{Process.ppid}:#{Process.pid}"
23
- sleep 2
20
+ loop do
21
+ logger.warn "I'm executing #{x}.. #{Process.ppid}:#{Process.pid}"
22
+
23
+ if x < 3
24
+ sleep 2
25
+ else
26
+ sleep 6
27
+ end
28
+
29
+ heartbeat!
30
+
31
+ x += 1
24
32
  end
25
33
  end
26
-
27
34
  end
28
35
 
29
- Raemon::Master.startup 3, Test
36
+ Raemon::Master.start 2, Test, :timeout => 5
data/lib/raemon/master.rb CHANGED
@@ -1,78 +1,413 @@
1
1
  module Raemon
2
2
  class Master
3
- attr_reader :worker_pids
4
- attr_reader :worker_klass
5
- attr_reader :logger
3
+ WORKERS = {}
4
+
5
+ SELF_PIPE = []
6
+
7
+ SIG_QUEUE = []
8
+
9
+ CHUNK_SIZE = (16*1024)
6
10
 
7
- def self.startup(num_workers, worker_klass, opts={})
8
- master = new(opts)
9
- master.startup(num_workers, worker_klass)
11
+ attr_accessor :name, :num_workers, :worker_klass,
12
+ :master_pid, :pid_file,
13
+ :logger, :timeout, :memory_limit
14
+
15
+ def self.start(num_workers, worker_klass, options={})
16
+ master = new(options)
17
+ master.start(num_workers, worker_klass)
10
18
  end
11
19
 
12
- def self.shutdown(pid_file)
13
- pid = File.open(pid_file, 'r') {|f| f.gets }.to_i
14
- Process.kill('TERM', pid) if pid > 0
15
- File.unlink(pid_file)
20
+ def self.stop(options={})
21
+ pid_file = options[:pid_file]
22
+ pid = options[:pid] || (File.read(pid_file).to_i rescue 0)
23
+ Process.kill('QUIT', pid) if pid > 0
16
24
  rescue Errno::ESRCH
17
25
  end
18
26
 
19
- def initialize(opts={})
20
- @detach = opts[:detach] || false
21
- @logger = opts[:logger] || Logger.new(STDOUT)
22
- @pid_file = opts[:pid_file]
23
- @worker_pids = []
27
+ def initialize(options={})
28
+ @detach = options[:detach] || false
29
+ @name = options[:name] || 'Raemon'
30
+ @pid_file = options[:pid_file]
31
+ @logger = options[:logger] || Logger.new(STDOUT)
32
+ @timeout = options[:timeout] || 180 # 3 minutes
33
+ @memory_limit = options[:memory_limit] # in MB
24
34
 
25
35
  daemonize if @detach
26
36
  end
27
37
 
28
- def startup(num_workers, worker_klass)
29
- logger.info "=> Starting Raemon::Master with #{num_workers} worker(s)"
30
-
38
+ def start(num_workers, worker_klass)
39
+ logger.info "=> Starting #{name} with #{num_workers} worker(s)"
40
+
41
+ @master_pid = $$
42
+ @num_workers = num_workers
31
43
  @worker_klass = worker_klass
32
44
 
33
- # Check if the worker implements our protocol
45
+ # Check if the worker implements our interface
34
46
  if !worker_klass.include?(Raemon::Worker)
35
47
  logger.error "** Invalid Raemon worker"
48
+ logger.close
36
49
  exit
37
50
  end
38
51
 
39
- # Spawn workers
40
- num_workers.times { worker_pids << worker_klass.start!(self) }
41
-
42
- # Setup signals for the master process
43
- setup_signals
44
-
45
- # Wait for all the workers
46
- Process.waitall
47
-
48
- logger.close
52
+ # Start the master loop which spawns and monitors workers
53
+ master_loop!
49
54
  end
50
55
 
51
- def shutdown
52
- @worker_pids.each { |wpid| worker_klass.stop!(wpid) }
56
+ # Terminates all workers, but does not exit master process
57
+ def stop(graceful=true)
58
+ kill_each_worker(graceful ? :QUIT : :TERM)
59
+ timeleft = timeout
60
+ step = 0.2
61
+ reap_all_workers
62
+ until WORKERS.empty?
63
+ sleep(step)
64
+ reap_all_workers
65
+ (timeleft -= step) > 0 and next
66
+ kill_each_worker(:KILL)
67
+ end
53
68
  end
54
69
 
55
- def daemonize
56
- exit if Process.fork
70
+ def worker_heartbeat!(worker)
71
+ return if timeout <= 0
72
+ @last_pulse ||= 0
57
73
 
58
- Process.setsid
74
+ begin
75
+ if Time.now.to_i > @last_pulse + (timeout/2)
76
+ # Pulse (our lifeline to the master process)
77
+ worker.pulse.chmod(@last_pulse = Time.now.to_i)
78
+
79
+ # Make sure master is still around otherwise exit
80
+ master_pid == Process.ppid or return
81
+ end
82
+ rescue => ex
83
+ if worker.pulse
84
+ logger.error "Unhandled listen loop exception #{ex.inspect}."
85
+ logger.error ex.backtrace.join("\n")
86
+ end
87
+ end
88
+ end
59
89
 
60
- Dir.chdir '/'
61
- File.umask 0000
90
+
91
+ private
92
+
93
+ # list of signals we care about and trap in master.
94
+ QUEUE_SIGS = [ :WINCH, :QUIT, :INT, :TERM, :USR1, :USR2, :HUP, :TTIN, :TTOU ]
95
+
96
+
97
+ # monitors children and receives signals forever
98
+ # (or until a termination signal is sent). This handles signals
99
+ # one-at-a-time time and we'll happily drop signals in case somebody
100
+ # is signalling us too often.
101
+ def master_loop!
102
+ # this pipe is used to wake us up from select(2) in #join when signals
103
+ # are trapped. See trap_deferred
104
+ init_self_pipe!
105
+ respawn = true
106
+
107
+ QUEUE_SIGS.each { |sig| trap_deferred(sig) }
108
+ trap(:CHLD) { |sig_nr| awaken_master }
109
+
110
+ process_name 'master'
111
+ logger.info "master process ready"
112
+
113
+ # Spawn workers for the first time
114
+ maintain_worker_count
115
+
116
+ begin
117
+ loop do
118
+ monitor_memory_usage
119
+ reap_all_workers
62
120
 
63
- STDIN.reopen '/dev/null'
64
- STDOUT.reopen '/dev/null', 'a'
65
- STDERR.reopen '/dev/null', 'a'
121
+ case SIG_QUEUE.shift
122
+ when nil
123
+ murder_lazy_workers
124
+ maintain_worker_count if respawn
125
+ master_sleep
126
+ when :QUIT # graceful shutdown
127
+ break
128
+ when :TERM, :INT # immediate shutdown
129
+ stop(false)
130
+ break
131
+ when :USR1
132
+ kill_each_worker(:USR1)
133
+ when :USR2
134
+ kill_each_worker(:USR2)
135
+ when :WINCH
136
+ if Process.ppid == 1 || Process.getpgrp != $$
137
+ respawn = false
138
+ logger.info "gracefully stopping all workers"
139
+ kill_each_worker(:QUIT)
140
+ else
141
+ logger.info "SIGWINCH ignored because we're not daemonized"
142
+ end
143
+ when :TTIN
144
+ @num_workers += 1
145
+ when :TTOU
146
+ @num_workers -= 1 if @num_workers > 0
147
+ when :HUP
148
+ # TODO: should restart the workers, but a :QUIT could stall
149
+ # respawn = true
150
+ # kill_each_worker(:QUIT)
151
+ end
152
+ end
153
+ rescue Errno::EINTR
154
+ retry
155
+ rescue => ex
156
+ logger.error "Unhandled master loop exception #{ex.inspect}."
157
+ logger.error ex.backtrace.join("\n")
158
+ retry
159
+ end
160
+
161
+ # Gracefully shutdown all workers on our way out
162
+ stop
163
+ logger.info "master complete"
164
+
165
+ # Close resources
166
+ unlink_pid_safe(pid_file) if pid_file
167
+ logger.close
168
+ end
169
+
170
+
171
+ # defer a signal for later processing in #master_loop! (master process)
172
+ def trap_deferred(signal)
173
+ trap(signal) do |sig_nr|
174
+ if SIG_QUEUE.size < 5
175
+ SIG_QUEUE << signal
176
+ awaken_master
177
+ else
178
+ logger.error "ignoring SIG#{signal}, queue=#{SIG_QUEUE.inspect}"
179
+ end
180
+ end
181
+ end
66
182
 
67
- File.open(@pid_file, 'w') { |f| f.puts(Process.pid) } if @pid_file
68
- end
183
+ # wait for a signal hander to wake us up and then consume the pipe
184
+ # Wake up every second anyways to run murder_lazy_workers
185
+ def master_sleep
186
+ begin
187
+ ready = IO.select([SELF_PIPE.first], nil, nil, 1) or return
188
+ ready.first && ready.first.first or return
189
+ loop { SELF_PIPE.first.read_nonblock(CHUNK_SIZE) }
190
+ rescue Errno::EAGAIN, Errno::EINTR
191
+ end
192
+ end
69
193
 
70
- def setup_signals
71
- shutdown_block = Proc.new { shutdown }
194
+ def awaken_master
195
+ begin
196
+ SELF_PIPE.last.write_nonblock('.') # wakeup master process from select
197
+ rescue Errno::EAGAIN, Errno::EINTR
198
+ # pipe is full, master should wake up anyways
199
+ retry
200
+ end
201
+ end
202
+
203
+ # reaps all unreaped workers
204
+ def reap_all_workers
205
+ begin
206
+ loop do
207
+ wpid, status = Process.waitpid2(-1, Process::WNOHANG)
208
+ wpid or break
209
+ worker = WORKERS.delete(wpid) and worker.pulse.close rescue nil
210
+ logger.info "reaped #{status.inspect} " \
211
+ "worker=#{worker.id rescue 'unknown'}"
212
+ end
213
+ rescue Errno::ECHILD
214
+ end
215
+ end
216
+
217
+ # forcibly terminate all workers that haven't checked in in timeout
218
+ # seconds. The timeout is implemented using an unlinked File
219
+ # shared between the parent process and each worker. The worker
220
+ # runs File#chmod to modify the ctime of the File. If the ctime
221
+ # is stale for >timeout seconds, then we'll kill the corresponding
222
+ # worker.
223
+ def murder_lazy_workers
224
+ return if timeout <= 0
225
+
226
+ diff = stat = nil
227
+
228
+ WORKERS.dup.each_pair do |wpid, worker|
229
+ begin
230
+ stat = worker.pulse.stat
231
+ rescue => ex
232
+ logger.warn "worker=#{worker.id} PID:#{wpid} stat error: #{ex.inspect}"
233
+ kill_worker(:QUIT, wpid)
234
+ next
235
+ end
236
+ stat.mode == 0100000 and next
237
+ (diff = (Time.now - stat.ctime)) <= timeout and next
238
+ logger.error "worker=#{worker.id} PID:#{wpid} timeout " \
239
+ "(#{diff}s > #{timeout}s), killing"
240
+ kill_worker(:KILL, wpid) # take no prisoners for timeout violations
241
+ end
242
+ end
243
+
244
+ # Spawn workers, and initalize new workers if some are
245
+ # no longer running
246
+ def spawn_workers
247
+ (0...num_workers).each do |id|
248
+ WORKERS.values.include?(id) and next
249
+ worker = worker_klass.new(self, id, Raemon::Util.tmpio)
250
+
251
+ # Fork the worker processes wrapped in the worker loop
252
+ WORKERS[fork { worker_loop!(worker) }] = worker
253
+ end
254
+ end
255
+
256
+ def maintain_worker_count
257
+ off = num_workers - WORKERS.size
258
+
259
+ if off.zero?
260
+ return
261
+ elsif off == num_workers
262
+ # None of the workers are running, lets be gentle
263
+ @spawn_attempts ||= 0
264
+ sleep 1 if @spawn_attempts > 1
265
+ if timeout > 0 && @spawn_attempts > timeout
266
+ # We couldn't get the workers going after timeout
267
+ # seconds, so let's assume this will never work
268
+ logger.error "Unable to spawn workers after #{@spawn_attempts} attempts"
269
+ master_quit
270
+ return
271
+ end
272
+ @spawn_attempts += 1
273
+ else
274
+ @spawn_attempts = nil
275
+ end
276
+
277
+ return spawn_workers if off > 0
278
+
279
+ WORKERS.dup.each_pair do |wpid, worker|
280
+ worker.id >= num_workers && kill_worker(:QUIT, wpid) rescue nil
281
+ end
282
+ end
283
+
284
+ # gets rid of stuff the worker has no business keeping track of
285
+ # to free some resources and drops all sig handlers.
286
+ # traps for USR1, USR2, and HUP may be set in the after_fork Proc
287
+ # by the user.
288
+ def init_worker_process(worker)
289
+ QUEUE_SIGS.each { |sig| trap(sig, nil) }
290
+ trap(:CHLD, 'DEFAULT')
291
+ SIG_QUEUE.clear
292
+ process_name "worker[#{worker.id}]"
293
+
294
+ init_self_pipe!
295
+ WORKERS.values.each { |other_worker| other_worker.pulse.close rescue nil }
296
+ WORKERS.clear
297
+
298
+ worker.pulse.fcntl(Fcntl::F_SETFD, Fcntl::FD_CLOEXEC)
299
+ @timeout /= 2.0 # halve it for select
300
+ end
72
301
 
73
- trap('INT', shutdown_block)
74
- trap('TERM', shutdown_block)
75
- end
302
+ # runs inside each forked worker, this sits around and waits
303
+ # for connections and doesn't die until the parent dies (or is
304
+ # given a INT, QUIT, or TERM signal)
305
+ def worker_loop!(worker)
306
+ init_worker_process(worker)
307
+
308
+ # Graceful shutdown
309
+ trap(:QUIT) do
310
+ worker.stop if worker.respond_to?(:stop)
311
+ exit!(0)
312
+ end
313
+
314
+ # Immediate termination
315
+ [:TERM, :INT].each { |sig| trap(sig) { exit!(0) } }
316
+
317
+ # Worker start
318
+ logger.info "worker=#{worker.id} ready"
319
+ worker.start if worker.respond_to?(:start)
320
+
321
+ # Worker run loop
322
+ worker.run
323
+ end
324
+
325
+ # delivers a signal to a worker and fails gracefully if the worker
326
+ # is no longer running.
327
+ def kill_worker(signal, wpid)
328
+ begin
329
+ Process.kill(signal, wpid)
330
+ rescue Errno::ESRCH
331
+ worker = WORKERS.delete(wpid) and worker.pulse.close rescue nil
332
+ end
333
+ end
334
+
335
+ # delivers a signal to each worker
336
+ def kill_each_worker(signal)
337
+ WORKERS.keys.each { |wpid| kill_worker(signal, wpid) }
338
+ end
339
+
340
+ # Make the master quit
341
+ def master_quit
342
+ SIG_QUEUE << :QUIT
343
+ end
344
+
345
+ # unlinks a PID file at given +path+ if it contains the current PID
346
+ # still potentially racy without locking the directory (which is
347
+ # non-portable and may interact badly with other programs), but the
348
+ # window for hitting the race condition is small
349
+ def unlink_pid_safe(path)
350
+ (File.read(path).to_i == $$ and File.unlink(path)) rescue nil
351
+ end
352
+
353
+ # returns a PID if a given path contains a non-stale PID file,
354
+ # nil otherwise.
355
+ def valid_pid?(path)
356
+ wpid = File.read(path).to_i
357
+ wpid <= 0 and return
358
+ begin
359
+ Process.kill(0, wpid) # send null signal to check if its alive
360
+ return wpid
361
+ rescue Errno::ESRCH
362
+ # don't unlink stale pid files, racy without non-portable locking...
363
+ end
364
+ rescue Errno::ENOENT
365
+ end
366
+
367
+ def process_name(tag)
368
+ $0 = "#{name} #{tag}"
369
+ end
370
+
371
+ def init_self_pipe!
372
+ SELF_PIPE.each { |io| io.close rescue nil }
373
+ SELF_PIPE.replace(IO.pipe)
374
+ SELF_PIPE.each { |io| io.fcntl(Fcntl::F_SETFD, Fcntl::FD_CLOEXEC) }
375
+ end
376
+
377
+ def daemonize
378
+ exit if Process.fork
379
+
380
+ Process.setsid
381
+
382
+ Dir.chdir '/'
383
+ File.umask 0000
384
+
385
+ STDIN.reopen '/dev/null'
386
+ STDOUT.reopen '/dev/null', 'a'
387
+ STDERR.reopen '/dev/null', 'a'
388
+
389
+ File.open(pid_file, 'w') { |f| f.puts(Process.pid) } if pid_file
390
+ end
391
+
392
+ # Check memory usage every 60 seconds if a memory limit is enforced
393
+ def monitor_memory_usage
394
+ return if memory_limit.nil?
395
+ @last_memory_chk ||= 0
396
+
397
+ if @last_memory_chk + 60 < Time.now.to_i
398
+ @last_memory_chk = Time.now.to_i
399
+ WORKERS.dup.each_pair do |wpid, worker|
400
+ if memory_usage(wpid) > (memory_limit*1024)
401
+ logger.warn "memory limit (#{memory_limit}MB) reached by worker=#{worker.id}"
402
+ kill_worker(:QUIT, wpid)
403
+ end
404
+ end
405
+ end
406
+ end
407
+
408
+ def memory_usage(pid)
409
+ `ps -o rss= -p #{pid}`.to_i
410
+ end
76
411
 
77
412
  end
78
413
  end
data/lib/raemon/server.rb CHANGED
@@ -16,7 +16,7 @@ module Raemon
16
16
 
17
17
  # Check if the server is already running
18
18
  if running?
19
- STDERR.puts "Error: #{server_name} is already running."
19
+ $stderr.puts "Error: #{server_name} is already running."
20
20
  exit
21
21
  end
22
22
 
@@ -25,15 +25,18 @@ module Raemon
25
25
 
26
26
  worker_klass = instance_eval(config.worker_klass)
27
27
 
28
- Raemon::Master.startup config.num_workers, worker_klass, {
29
- :detach => config.detach,
30
- :logger => config.logger,
31
- :pid_file => pid_file
28
+ Raemon::Master.start config.num_workers, worker_klass, {
29
+ :name => server_name,
30
+ :pid_file => pid_file,
31
+ :detach => config.detach,
32
+ :logger => config.logger,
33
+ :timeout => config.timeout,
34
+ :memory_limit => config.memory_limit
32
35
  }
33
36
  end
34
37
 
35
38
  def shutdown!
36
- Raemon::Master.shutdown pid_file
39
+ Raemon::Master.stop :pid_file => pid_file
37
40
  end
38
41
 
39
42
  def console!
@@ -101,13 +104,15 @@ module Raemon
101
104
  end
102
105
 
103
106
  def running?
104
- # TODO
105
- false
107
+ pid = File.read(pid_file).to_i rescue 0
108
+ Process.kill(0, pid) if pid > 0
109
+ rescue Errno::ESRCH
106
110
  end
107
111
  end
108
112
 
109
113
  class Configuration
110
- ATTRIBUTES = [ :name, :detach, :worker_klass, :num_workers, :log_level, :logger ]
114
+ ATTRIBUTES = [ :name, :detach, :num_workers, :worker_klass,
115
+ :log_level, :logger, :timeout, :memory_limit ]
111
116
 
112
117
  attr_accessor *ATTRIBUTES
113
118
 
@@ -0,0 +1,23 @@
1
+ module Raemon
2
+ class Util
3
+ class << self
4
+
5
+ # Creates and returns a new File object. The File is unlinked
6
+ # immediately, switched to binary mode, and userspace output
7
+ # buffering is disabled. Method pulled from Unicorn library.
8
+ def tmpio
9
+ begin
10
+ fp = File.open("#{Dir::tmpdir}/#{rand}",
11
+ File::RDWR | File::CREAT | File::EXCL, 0600)
12
+ rescue Errno::EEXIST
13
+ retry
14
+ end
15
+ File.unlink(fp.path)
16
+ fp.binmode
17
+ fp.sync = true
18
+ fp
19
+ end
20
+
21
+ end
22
+ end
23
+ end
data/lib/raemon/worker.rb CHANGED
@@ -2,52 +2,32 @@ module Raemon
2
2
  module Worker
3
3
 
4
4
  def self.included(base)
5
- base.send :extend, ClassMethods
6
5
  base.send :include, InstanceMethods
7
6
  end
8
7
 
9
- module ClassMethods
10
- def start!(master=nil)
11
- child_pid = Process.fork do
12
- # Child process
13
- worker = new(master)
14
- worker.execute
15
- end
16
-
17
- # Parent returns the worker's pid
18
- return child_pid
19
- end
20
-
21
- def stop!(worker_pid)
22
- Process.kill('QUIT', worker_pid) rescue nil
23
- end
24
- end
25
-
26
8
  module InstanceMethods
27
- attr_reader :logger
9
+ attr_reader :master, :logger, :id, :pid, :pulse
28
10
 
29
- def initialize(master=nil)
11
+ def initialize(master, id, pulse)
30
12
  @master = master
31
- @logger = master.logger if master
32
-
33
- setup_signals
34
- start
13
+ @logger = master.logger
14
+ @id = id
15
+ @pid = $$
16
+ @pulse = pulse
35
17
  end
36
-
37
- def start; end
38
- def stop; end
39
- def shutting_down?; @shutting_down; end
40
- def execute; raise "Abstract method"; end
41
-
42
- def setup_signals
43
- quit_block = Proc.new { @shutting_down = true }
44
- force_quit_block = Proc.new { exit }
45
-
46
- trap('QUIT', quit_block)
47
- trap('TERM', force_quit_block)
48
- trap('INT') {} # Reset INT signal handler
18
+
19
+ def ==(other_id)
20
+ @id == other_id
21
+ end
22
+
23
+ def run
24
+ raise "Abstract method"
25
+ end
26
+
27
+ def heartbeat!
28
+ master.worker_heartbeat!(self)
49
29
  end
50
30
  end
51
-
31
+
52
32
  end
53
33
  end
data/lib/raemon.rb CHANGED
@@ -1,10 +1,12 @@
1
- require 'socket'
1
+ require 'fcntl'
2
+ require 'tmpdir'
2
3
  require 'logger'
3
4
 
4
5
  module Raemon
5
- VERSION = '0.1.2'
6
+ VERSION = '0.2.0'
7
+
8
+ autoload :Master, 'raemon/master'
9
+ autoload :Worker, 'raemon/worker'
10
+ autoload :Server, 'raemon/server'
11
+ autoload :Util, 'raemon/util'
6
12
  end
7
-
8
- require 'raemon/server'
9
- require 'raemon/master'
10
- require 'raemon/worker'
data/raemon.gemspec ADDED
@@ -0,0 +1,78 @@
1
+ # Generated by jeweler
2
+ # DO NOT EDIT THIS FILE DIRECTLY
3
+ # Instead, edit Jeweler::Tasks in Rakefile, and run the gemspec command
4
+ # -*- encoding: utf-8 -*-
5
+
6
+ Gem::Specification.new do |s|
7
+ s.name = %q{raemon}
8
+ s.version = "0.2.0"
9
+
10
+ s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
11
+ s.authors = ["Peter Kieltyka"]
12
+ s.date = %q{2010-03-15}
13
+ s.description = %q{Raemon is a Ruby framework for building UNIX daemons.}
14
+ s.email = %q{peter.kieltyka@nulayer.com}
15
+ s.extra_rdoc_files = [
16
+ "LICENSE",
17
+ "README"
18
+ ]
19
+ s.files = [
20
+ ".gitignore",
21
+ "LICENSE",
22
+ "README",
23
+ "Rakefile",
24
+ "VERSION",
25
+ "examples/beanstalk.rb",
26
+ "examples/evented.rb",
27
+ "examples/queue.rb",
28
+ "examples/sampled/bin/sampled",
29
+ "examples/sampled/config/boot.rb",
30
+ "examples/sampled/config/environment.rb",
31
+ "examples/sampled/config/environments/development.rb",
32
+ "examples/sampled/config/environments/production.rb",
33
+ "examples/sampled/config/environments/test.rb",
34
+ "examples/sampled/config/initializers/settings.rb",
35
+ "examples/sampled/config/settings.yml",
36
+ "examples/sampled/lib/sampled.rb",
37
+ "examples/sampled/log/.keep",
38
+ "examples/sampled/test/.keep",
39
+ "examples/sampled/tmp/pids/.keep",
40
+ "examples/sampled/vendor/.keep",
41
+ "examples/test.rb",
42
+ "lib/raemon.rb",
43
+ "lib/raemon/master.rb",
44
+ "lib/raemon/server.rb",
45
+ "lib/raemon/util.rb",
46
+ "lib/raemon/worker.rb",
47
+ "raemon.gemspec"
48
+ ]
49
+ s.homepage = %q{http://github.com/pkieltyka/raemon}
50
+ s.rdoc_options = ["--charset=UTF-8"]
51
+ s.require_paths = ["lib"]
52
+ s.rubygems_version = %q{1.3.6}
53
+ s.summary = %q{Raemon is a Ruby framework for building UNIX daemons.}
54
+ s.test_files = [
55
+ "examples/beanstalk.rb",
56
+ "examples/evented.rb",
57
+ "examples/queue.rb",
58
+ "examples/sampled/config/boot.rb",
59
+ "examples/sampled/config/environment.rb",
60
+ "examples/sampled/config/environments/development.rb",
61
+ "examples/sampled/config/environments/production.rb",
62
+ "examples/sampled/config/environments/test.rb",
63
+ "examples/sampled/config/initializers/settings.rb",
64
+ "examples/sampled/lib/sampled.rb",
65
+ "examples/test.rb"
66
+ ]
67
+
68
+ if s.respond_to? :specification_version then
69
+ current_version = Gem::Specification::CURRENT_SPECIFICATION_VERSION
70
+ s.specification_version = 3
71
+
72
+ if Gem::Version.new(Gem::RubyGemsVersion) >= Gem::Version.new('1.2.0') then
73
+ else
74
+ end
75
+ else
76
+ end
77
+ end
78
+
metadata CHANGED
@@ -1,7 +1,12 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: raemon
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.2
4
+ prerelease: false
5
+ segments:
6
+ - 0
7
+ - 2
8
+ - 0
9
+ version: 0.2.0
5
10
  platform: ruby
6
11
  authors:
7
12
  - Peter Kieltyka
@@ -9,7 +14,7 @@ autorequire:
9
14
  bindir: bin
10
15
  cert_chain: []
11
16
 
12
- date: 2010-01-01 00:00:00 -05:00
17
+ date: 2010-03-15 00:00:00 -04:00
13
18
  default_executable:
14
19
  dependencies: []
15
20
 
@@ -30,6 +35,7 @@ files:
30
35
  - VERSION
31
36
  - examples/beanstalk.rb
32
37
  - examples/evented.rb
38
+ - examples/queue.rb
33
39
  - examples/sampled/bin/sampled
34
40
  - examples/sampled/config/boot.rb
35
41
  - examples/sampled/config/environment.rb
@@ -39,6 +45,7 @@ files:
39
45
  - examples/sampled/config/initializers/settings.rb
40
46
  - examples/sampled/config/settings.yml
41
47
  - examples/sampled/lib/sampled.rb
48
+ - examples/sampled/log/.keep
42
49
  - examples/sampled/test/.keep
43
50
  - examples/sampled/tmp/pids/.keep
44
51
  - examples/sampled/vendor/.keep
@@ -46,7 +53,9 @@ files:
46
53
  - lib/raemon.rb
47
54
  - lib/raemon/master.rb
48
55
  - lib/raemon/server.rb
56
+ - lib/raemon/util.rb
49
57
  - lib/raemon/worker.rb
58
+ - raemon.gemspec
50
59
  has_rdoc: true
51
60
  homepage: http://github.com/pkieltyka/raemon
52
61
  licenses: []
@@ -60,24 +69,27 @@ required_ruby_version: !ruby/object:Gem::Requirement
60
69
  requirements:
61
70
  - - ">="
62
71
  - !ruby/object:Gem::Version
72
+ segments:
73
+ - 0
63
74
  version: "0"
64
- version:
65
75
  required_rubygems_version: !ruby/object:Gem::Requirement
66
76
  requirements:
67
77
  - - ">="
68
78
  - !ruby/object:Gem::Version
79
+ segments:
80
+ - 0
69
81
  version: "0"
70
- version:
71
82
  requirements: []
72
83
 
73
84
  rubyforge_project:
74
- rubygems_version: 1.3.5
85
+ rubygems_version: 1.3.6
75
86
  signing_key:
76
87
  specification_version: 3
77
88
  summary: Raemon is a Ruby framework for building UNIX daemons.
78
89
  test_files:
79
90
  - examples/beanstalk.rb
80
91
  - examples/evented.rb
92
+ - examples/queue.rb
81
93
  - examples/sampled/config/boot.rb
82
94
  - examples/sampled/config/environment.rb
83
95
  - examples/sampled/config/environments/development.rb
@@ -85,6 +97,4 @@ test_files:
85
97
  - examples/sampled/config/environments/test.rb
86
98
  - examples/sampled/config/initializers/settings.rb
87
99
  - examples/sampled/lib/sampled.rb
88
- - examples/sampled/test/example_test.rb
89
- - examples/sampled/test/test_helper.rb
90
100
  - examples/test.rb
@@ -1,11 +0,0 @@
1
- require 'test_helper'
2
-
3
- class ExampleTest < Test::Unit::TestCase
4
-
5
- def test_nothing
6
- assert true
7
-
8
- assert defined? Sampled::Worker
9
- end
10
-
11
- end
@@ -1,7 +0,0 @@
1
- ENV['RAEMON_ENV'] = 'test'
2
-
3
- # Add any libs you need for your test suite here
4
- require 'rubygems'
5
-
6
- require File.dirname(__FILE__) + '/../config/environment'
7
- Raemon::Server.startup!