raemon 0.1.2 → 0.2.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/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!