famoseagle-sweat_shop 0.8.2 → 1.0.0

Sign up to get free protection for your applications and to get access to all the features.
data/Rakefile CHANGED
@@ -14,7 +14,6 @@ begin
14
14
  s.authors = ["Amos Elliston"]
15
15
  s.files = FileList["[A-Z]*", "{lib,test,config}/**/*"]
16
16
  s.add_dependency('famoseagle-carrot', '= 0.6.0')
17
- s.add_dependency('wonko9-i_can_daemonize', '= 0.7.1')
18
17
  end
19
18
  rescue LoadError
20
19
  puts "Jeweler not available. Install it with: sudo gem install technicalpickles-jeweler -s http://gems.github.com"
data/VERSION.yml CHANGED
@@ -1,4 +1,4 @@
1
1
  ---
2
- :major: 0
3
- :minor: 8
4
- :patch: 2
2
+ :major: 1
3
+ :minor: 0
4
+ :patch: 0
data/config/defaults.yml CHANGED
@@ -1,8 +1,9 @@
1
1
  # default options
2
- servers:
3
- - localhost:5672
2
+ default:
3
+ queue: rabbit
4
+ host: localhost
5
+ port: 5672
6
+ user: 'guest'
7
+ pass: 'guest'
8
+ vhost: '/'
4
9
  enable: true
5
- queue: rabbit
6
- user: 'guest'
7
- pass: 'guest'
8
- vhost: '/'
data/config/sweatshop.yml CHANGED
@@ -1,12 +1,18 @@
1
1
  development:
2
- servers:
3
- - localhost:5672
2
+ default:
3
+ queue: rabbit
4
+ host: localhost
5
+ port: 5672
4
6
  enable: true
5
7
  test:
6
- servers:
7
- - localhost:5672
8
- enable: false
8
+ default:
9
+ queue: rabbit
10
+ host: localhost
11
+ port: 5672
12
+ enable: true
9
13
  production:
10
- servers:
11
- - localhost:5672
14
+ default:
15
+ queue: rabbit
16
+ host: localhost
17
+ port: 5672
12
18
  enable: true
@@ -3,10 +3,7 @@ module MessageQueue
3
3
  class Rabbit < Base
4
4
 
5
5
  def initialize(opts={})
6
- @servers = opts['servers']
7
- @host, @port = @servers.first.split(':')
8
- @port = @port.to_i
9
- @opts = opts
6
+ @opts = opts
10
7
  end
11
8
 
12
9
  def delete(queue)
@@ -59,8 +56,8 @@ module MessageQueue
59
56
 
60
57
  def client
61
58
  @client ||= Carrot.new(
62
- :host => @host,
63
- :port => @port,
59
+ :host => @opts['host'],
60
+ :port => @opts['port'].to_i,
64
61
  :user => @opts['user'],
65
62
  :pass => @opts['pass'],
66
63
  :vhost => @opts['vhost']
data/lib/sweat_shop.rb CHANGED
@@ -47,6 +47,9 @@ module SweatShop
47
47
  end
48
48
  end
49
49
  if stop?
50
+ workers.each do |worker|
51
+ worker.stop
52
+ end
50
53
  queue.stop
51
54
  exit
52
55
  end
@@ -107,16 +110,18 @@ module SweatShop
107
110
  puts '-' * (max_width + 10)
108
111
  end
109
112
 
110
- def queue
111
- @queue ||= begin
112
- queue = config['queue'] || 'rabbit'
113
- queue = constantize("MessageQueue::#{queue.capitalize}")
114
- queue.new(config)
113
+ def queue(type = 'default')
114
+ @queues ||= {}
115
+ @queues[type] ||= begin
116
+ qconfig = config[type] || config['default']
117
+ qtype = qconfig['queue'] || 'rabbit'
118
+ queue = constantize("MessageQueue::#{qtype.capitalize}")
119
+ queue.new(qconfig)
115
120
  end
116
121
  end
117
122
 
118
- def queue=(queue)
119
- @queue = queue
123
+ def queue=(queue, type = 'default')
124
+ @queues[type] = queue
120
125
  end
121
126
 
122
127
  def log(msg)
@@ -0,0 +1,405 @@
1
+ require 'optparse'
2
+ require 'timeout'
3
+
4
+ module Daemoned
5
+ class DieTime < StandardError; end
6
+ class TimeoutError < StandardError; end
7
+
8
+ def self.included(base)
9
+ base.extend ClassMethods
10
+ base.initialize_options
11
+ end
12
+
13
+ class Config
14
+ METHODS = [:script_path]
15
+ CONFIG = {}
16
+ def method_missing(name, *args)
17
+ name = name.to_s.upcase.to_sym
18
+ if name.to_s =~ /^(.*)=$/
19
+ name = $1.to_sym
20
+ CONFIG[name] = args.first
21
+ else
22
+ CONFIG[name]
23
+ end
24
+ end
25
+ end
26
+
27
+ module ClassMethods
28
+
29
+ def initialize_options
30
+ @@config = Config.new
31
+ @@config.script_path = File.expand_path(File.dirname($0))
32
+ $0 = script_name
33
+ end
34
+
35
+ def parse_options
36
+ opts = OptionParser.new do |opt|
37
+ opt.banner = "Usage: #{script_name} [options] [start|stop]"
38
+
39
+ opt.on_tail('-h', '--help', 'Show this message') do
40
+ puts opt
41
+ exit(1)
42
+ end
43
+
44
+ opt.on('--loop-every=SECONDS', 'How long to sleep between each loop') do |value|
45
+ options[:loop_every] = value
46
+ end
47
+
48
+ opt.on('-t', '--ontop', 'Stay on top (does not daemonize)') do
49
+ options[:ontop] = true
50
+ end
51
+
52
+ opt.on('--instances=NUM', 'Allow multiple instances to run simultaneously? 0 for infinite. default: 1') do |value|
53
+ self.instances = value.to_i
54
+ end
55
+
56
+ opt.on('--log-file=LOGFILE', 'Logfile to log to') do |value|
57
+ options[:log_file] = File.expand_path(value)
58
+ end
59
+
60
+ opt.on('--pid-file=PIDFILE', 'Location of pidfile') do |value|
61
+ options[:pid_file] = File.expand_path(value)
62
+ end
63
+
64
+ opt.on('--no-log-prefix', 'Do not prefix PID and date/time in log file.') do
65
+ options[:log_prefix] = false
66
+ end
67
+ end
68
+
69
+ extra_args.each do |arg|
70
+ opts.on(*arg.first) do |value|
71
+ arg.last.call(value) if arg.last
72
+ end
73
+ end
74
+
75
+ opts.parse!
76
+
77
+ if ARGV.include?('stop')
78
+ stop
79
+ elsif ARGV.include?('reload')
80
+ kill('HUP')
81
+ exit
82
+ elsif not ARGV.include?('start') and not ontop?
83
+ puts opts.help
84
+ end
85
+ end
86
+
87
+ def arg(*args, &block)
88
+ self.extra_args << [args, block]
89
+ end
90
+
91
+ def extra_args
92
+ @extra_args ||= []
93
+ end
94
+
95
+ def callbacks
96
+ @callbacks ||= {}
97
+ end
98
+
99
+ def options
100
+ @options ||= {}
101
+ end
102
+
103
+ def options=(options)
104
+ @options = options
105
+ end
106
+
107
+ def config
108
+ yield @@config
109
+ end
110
+
111
+ def before(&block)
112
+ callbacks[:before] = block
113
+ end
114
+
115
+ def after(&block)
116
+ callbacks[:after] = block
117
+ end
118
+
119
+ def sig(*signals, &block)
120
+ signals.each do |s|
121
+ callbacks["sig_#{s}".to_sym] = block
122
+ end
123
+ end
124
+
125
+ def die_if(method=nil,&block)
126
+ options[:die_if] = method || block
127
+ end
128
+
129
+ def exit_if(method=nil,&block)
130
+ options[:exit_if] = method || block
131
+ end
132
+
133
+ def callback!(callback)
134
+ callbacks[callback].call if callbacks[callback]
135
+ end
136
+
137
+ # options may include:
138
+ #
139
+ # <tt>:loop_every</tt> Fixnum (DEFAULT 0)
140
+ # How many seconds to sleep between calls to your block
141
+ #
142
+ # <tt>:timeout</tt> Fixnum (DEFAULT 0)
143
+ # Timeout in if block does not execute withing passed number of seconds
144
+ #
145
+ # <tt>:kill_timeout</tt> Fixnum (DEFAULT 120)
146
+ # Wait number of seconds before using kill -9 on daemon
147
+ #
148
+ # <tt>:die_on_timeout</tt> BOOL (DEFAULT False)
149
+ # Should the daemon continue running if a block times out, or just run the block again
150
+ #
151
+ # <tt>:ontop</tt> BOOL (DEFAULT False)
152
+ # Do not daemonize. Run in current process
153
+ #
154
+ # <tt>:before</tt> BLOCK
155
+ # Run this block after daemonizing but before begining the daemonize loop.
156
+ # You can also define the before block by putting a before do/end block in your class.
157
+ #
158
+ # <tt>:after</tt> BLOCK
159
+ # Run this block before program exists.
160
+ # You can also define the after block by putting an after do/end block in your class.
161
+ #
162
+ # <tt>:die_if</tt> BLOCK
163
+ # Run this check after each iteration of the loop. If the block returns true, throw a DieTime exception and exit
164
+ # You can also define the after block by putting an die_if do/end block in your class.
165
+ #
166
+ # <tt>:exit_if</tt> BLOCK
167
+ # Run this check after each iteration of the loop. If the block returns true, exit gracefully
168
+ # You can also define the after block by putting an exit_if do/end block in your class.
169
+ #
170
+ # <tt>:log_prefix</tt> BOOL (DEFAULT true)
171
+ # Prefix log file entries with PID and timestamp
172
+ def daemonize(opts={}, &block)
173
+ self.options = opts
174
+ parse_options
175
+ return unless ok_to_start?
176
+
177
+ puts "Starting #{script_name}..."
178
+ puts "Logging to: #{log_file}" unless ontop?
179
+
180
+ unless ontop?
181
+ safefork do
182
+ open(pid_file, 'w'){|f| f << Process.pid }
183
+ at_exit { remove_pid! }
184
+
185
+ trap('TERM') { callback!(:sig_term) }
186
+ trap('INT') { callback!(:sig_int) ; Process.kill('TERM', $$) }
187
+ trap('HUP') { callback!(:sig_hup) }
188
+
189
+ sess_id = Process.setsid
190
+ reopen_filehandes
191
+
192
+ begin
193
+ at_exit { callback!(:after) }
194
+ callback!(:before)
195
+ run_block(&block)
196
+ rescue SystemExit
197
+ rescue Exception => e
198
+ $stdout.puts "Something bad happened #{e.inspect} #{e.backtrace.join("\n")}"
199
+ end
200
+ end
201
+ else
202
+ begin
203
+ callback!(:before)
204
+ run_block(&block)
205
+ rescue SystemExit, Interrupt
206
+ callback!(:after)
207
+ end
208
+ end
209
+ end
210
+
211
+ private
212
+
213
+ def run_block(&block)
214
+ loop do
215
+ if options[:timeout]
216
+ begin
217
+ Timeout::timeout(options[:timeout].to_i) do
218
+ block.call if block
219
+ end
220
+ rescue Timeout::Error => e
221
+ if options[:die_on_timeout]
222
+ raise TimeoutError.new("#{self} timed out after #{options[:timeout]} seconds while executing block in loop")
223
+ else
224
+ $stderr.puts "#{self} timed out after #{options[:timeout]} seconds while executing block in loop #{e.backtrace.join("\n")}"
225
+ end
226
+ end
227
+ else
228
+ block.call if block
229
+ end
230
+
231
+ if options[:loop_every]
232
+ sleep options[:loop_every].to_i
233
+ elsif not block
234
+ sleep 0.1
235
+ end
236
+
237
+ break if should_exit?
238
+ raise DieTime.new('Die if conditions were met!') if should_die?
239
+ end
240
+ exit(0)
241
+ end
242
+
243
+ def should_die?
244
+ die_if = options[:die_if]
245
+ if die_if
246
+ if die_if.is_a?(Symbol) or die_if.is_a?(String)
247
+ self.send(die_if)
248
+ elsif die_if.is_a?(Proc)
249
+ die_if.call
250
+ end
251
+ else
252
+ false
253
+ end
254
+ end
255
+
256
+ def should_exit?
257
+ exit_if = options[:exit_if]
258
+ if exit_if
259
+ if exit_if.is_a?(Symbol) or exit_if.is_a?(String)
260
+ self.send(exit_if.to_sym)
261
+ elsif exit_if.is_a?(Proc)
262
+ exit_if.call
263
+ end
264
+ else
265
+ false
266
+ end
267
+ end
268
+
269
+ def ok_to_start?
270
+ return true if pid.nil?
271
+
272
+ if process_alive?
273
+ $stderr.puts "#{script_name} is already running"
274
+ return false
275
+ else
276
+ $stderr.puts "Removing stale pid: #{pid}..."
277
+ end
278
+
279
+ true
280
+ end
281
+
282
+ def stop
283
+ puts "Stopping #{script_name}..."
284
+ kill
285
+ exit
286
+ end
287
+
288
+ def kill(signal = 'TERM')
289
+ if pid.nil?
290
+ $stderr.puts "#{script_name} doesn't appear to be running"
291
+ exit(1)
292
+ end
293
+ $stdout.puts("Sending pid #{pid} signal #{signal}...")
294
+ begin
295
+ Process.kill(signal, pid)
296
+ return if signal == 'HUP'
297
+ if pid_running?(options[:kill_timeout] || 120)
298
+ $stdout.puts("Using kill -9 #{pid}")
299
+ Process.kill(9, pid)
300
+ else
301
+ $stdout.puts("Process #{pid} stopped")
302
+ end
303
+ rescue Errno::ESRCH
304
+ $stdout.puts("Couldn't #{signal} #{pid} as it wasn't running")
305
+ end
306
+ end
307
+
308
+ def pid_running?(time_to_wait = 0)
309
+ times_to_check = 1
310
+ if time_to_wait > 0.5
311
+ times_to_check = (time_to_wait / 0.5).to_i
312
+ end
313
+ times_to_check.times do
314
+ return false unless process_alive?
315
+ sleep 0.5
316
+ end
317
+ true
318
+ end
319
+
320
+ def safefork(&block)
321
+ fork_tries ||= 0
322
+ fork(&block)
323
+ rescue Errno::EWOULDBLOCK
324
+ raise if fork_tries >= 20
325
+ fork_tries += 1
326
+ sleep 5
327
+ retry
328
+ end
329
+
330
+ def process_alive?
331
+ Process.kill(0, pid)
332
+ true
333
+ rescue Errno::ESRCH => e
334
+ false
335
+ end
336
+
337
+ LOG_FORMAT = '%-6d %-19s %s'
338
+ TIME_FORMAT = '%Y/%m/%d %H:%M:%S'
339
+ def reopen_filehandes
340
+ STDIN.reopen('/dev/null')
341
+ STDOUT.reopen(log_file, 'a')
342
+ STDOUT.sync = true
343
+ STDERR.reopen(STDOUT)
344
+ if log_prefix?
345
+ def STDOUT.write(string)
346
+ if @no_prefix
347
+ @no_prefix = false if string[-1, 1] == "\n"
348
+ else
349
+ string = LOG_FORMAT % [$$, Time.now.strftime(TIME_FORMAT), string]
350
+ @no_prefix = true
351
+ end
352
+ super(string)
353
+ end
354
+ end
355
+ end
356
+
357
+ def remove_pid!
358
+ if File.file?(pid_file) and File.read(pid_file).to_i == $$
359
+ File.unlink(pid_file)
360
+ end
361
+ end
362
+
363
+ def ontop?
364
+ options[:ontop]
365
+ end
366
+
367
+ def log_prefix?
368
+ options[:log_prefix] || true
369
+ end
370
+
371
+ LOG_PATHS = ['log/', 'logs/', '../log/', '../logs/', '../../log', '../../logs', '.']
372
+ LOG_PATHS.unshift("#{RAILS_ROOT}/log") if defined?(RAILS_ROOT)
373
+ def log_dir
374
+ options[:log_dir] ||= begin
375
+ LOG_PATHS.detect do |path|
376
+ File.exists?(File.expand_path(path))
377
+ end
378
+ end
379
+ end
380
+
381
+ def log_file
382
+ options[:log_file] ||= File.expand_path("#{log_dir}/#{script_name}.log")
383
+ end
384
+
385
+ def pid_dir
386
+ options[:pid_dir] ||= log_dir
387
+ end
388
+
389
+ def pid_file
390
+ options[:pid_file] ||= File.expand_path("#{pid_dir}/#{script_name}.pid")
391
+ end
392
+
393
+ def pid
394
+ @pid ||= File.file?(pid_file) ? File.read(pid_file).to_i : nil
395
+ end
396
+
397
+ def script_name
398
+ @script_name ||= File.basename($0).gsub('.rb', '')
399
+ end
400
+
401
+ def script_name=(script_name)
402
+ @script_name = script_name
403
+ end
404
+ end
405
+ end
@@ -1,12 +1,12 @@
1
- require File.dirname(__FILE__) + '/../sweat_shop'
2
- require 'i_can_daemonize'
1
+ require File.dirname(__FILE__) + '/daemoned'
3
2
 
4
3
  module SweatShop
5
4
  class Sweatd
6
- include ICanDaemonize
5
+ include Daemoned
7
6
  queues = []
8
7
  groups = []
9
8
  rails_root = nil
9
+ start_cmd = "ruby #{__FILE__} start #{ARGV.reject{|a| a == 'start'}.join(' ')}"
10
10
 
11
11
  arg '--workers=Worker,Worker', 'Workers to service (Default is all)' do |value|
12
12
  queues = value.split(',')
@@ -32,12 +32,21 @@ module SweatShop
32
32
  puts "Shutting down sweatd..."
33
33
  SweatShop.stop
34
34
  end
35
+
36
+ sig(:hup) do
37
+ puts "Received HUP"
38
+ SweatShop.stop
39
+ remove_pid!
40
+ puts "Restarting sweatd with #{start_cmd}..."
41
+ `#{start_cmd}`
42
+ end
35
43
 
36
44
  before do
37
45
  if rails_root
38
46
  puts "Loading Rails..."
39
47
  require rails_root + '/config/environment'
40
48
  end
49
+ require File.dirname(__FILE__) + '/../sweat_shop'
41
50
  end
42
51
 
43
52
  daemonize(:kill_timeout => 20) do
@@ -95,7 +95,7 @@ module SweatShop
95
95
  end
96
96
 
97
97
  def self.queue
98
- SweatShop.queue
98
+ SweatShop.queue(queue_group.to_s)
99
99
  end
100
100
 
101
101
  def self.workers
@@ -126,6 +126,14 @@ module SweatShop
126
126
  end
127
127
  end
128
128
 
129
+ def self.stop
130
+ instance.stop
131
+ end
132
+
133
+ # called before we exit -- subclass can implement this method
134
+ def stop; end;
135
+
136
+
129
137
  def self.queue_group(group=nil)
130
138
  group ? meta_def(:_queue_group){ group } : _queue_group
131
139
  end
data/test/hello_worker.rb CHANGED
@@ -1,7 +1,4 @@
1
- # hack for functional tests
2
- require File.dirname(__FILE__) + '/../../../memcache/lib/memcache_extended'
3
- require File.dirname(__FILE__) + '/../../../memcache/lib/memcache_util'
4
-
1
+ require File.dirname(__FILE__) + '/../lib/sweat_shop'
5
2
  class HelloWorker < SweatShop::Worker
6
3
  TEST_FILE = File.dirname(__FILE__) + '/test.txt' unless defined?(TEST_FILE)
7
4
 
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: famoseagle-sweat_shop
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.8.2
4
+ version: 1.0.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Amos Elliston
@@ -9,7 +9,7 @@ autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
11
 
12
- date: 2009-05-29 00:00:00 -07:00
12
+ date: 2009-07-17 00:00:00 -07:00
13
13
  default_executable: sweatd
14
14
  dependencies:
15
15
  - !ruby/object:Gem::Dependency
@@ -22,16 +22,6 @@ dependencies:
22
22
  - !ruby/object:Gem::Version
23
23
  version: 0.6.0
24
24
  version:
25
- - !ruby/object:Gem::Dependency
26
- name: wonko9-i_can_daemonize
27
- type: :runtime
28
- version_requirement:
29
- version_requirements: !ruby/object:Gem::Requirement
30
- requirements:
31
- - - "="
32
- - !ruby/object:Gem::Version
33
- version: 0.7.1
34
- version:
35
25
  description: TODO
36
26
  email: amos@geni.com
37
27
  executables:
@@ -51,9 +41,9 @@ files:
51
41
  - config/sweatshop.yml
52
42
  - lib/message_queue/base.rb
53
43
  - lib/message_queue/kestrel.rb
54
- - lib/message_queue/rabbit-async.rb
55
44
  - lib/message_queue/rabbit.rb
56
45
  - lib/sweat_shop.rb
46
+ - lib/sweat_shop/daemoned.rb
57
47
  - lib/sweat_shop/metaid.rb
58
48
  - lib/sweat_shop/sweatd.rb
59
49
  - lib/sweat_shop/worker.rb
@@ -1,83 +0,0 @@
1
- require 'mq'
2
- module MessageQueue
3
- class Rabbit < Base
4
- attr_accessor :em_thread
5
-
6
- def initialize(opts={})
7
- @servers = opts['servers']
8
- @info = {}
9
- @host, @port = @servers.first.split(':')
10
- @port = @port.to_i
11
- end
12
-
13
- def queue_size(queue)
14
- num = 0
15
- client.queue(queue).status{|messages, consumers| num = messages}
16
- num
17
- end
18
-
19
- def enqueue(queue, data)
20
- client.queue(queue, :durable => true).publish(Marshal.dump(data), :persistent => true)
21
- end
22
-
23
- def dequeue(queue)
24
- client.queue(queue).pop do |info, task|
25
- @info[queue] = info
26
- return Marshal.load(task)
27
- end
28
- end
29
-
30
- def confirm(queue)
31
- if @info[queue]
32
- @info[queue].ack
33
- @info[queue] = nil
34
- end
35
- end
36
-
37
- def client
38
- @client ||= begin
39
- start_em
40
- if servers
41
- MQ.new(AMQP.connect(:host => @host, :port => @port))
42
- else
43
- MQ.new
44
- end
45
- end
46
- end
47
-
48
- def start_em
49
- if em_thread.nil? and not EM.reactor_running?
50
- self.em_thread = Thread.new{EM.run}
51
- ['INT', 'TERM'].each do |sig|
52
- old = trap(sig) do
53
- stop
54
- old.call
55
- end
56
- end
57
- end
58
- end
59
-
60
- def subscribe?
61
- true
62
- end
63
-
64
- def subscribe(queue, &block)
65
- AMQP.start(:host => @host, :port => @port) do
66
- mq = MQ.new
67
- mq.send(AMQP::Protocol::Basic::Qos.new(:prefetch_size => 0, :prefetch_count => 1, :global => false))
68
- mq.queue(queue, :durable => true).subscribe(:ack => true) do |info, task|
69
- if task
70
- @info[queue] = info
71
- task = Marshal.load(task)
72
- block.call(task)
73
- end
74
- end
75
- end
76
- end
77
-
78
- def stop
79
- em_thread.join(0.15) unless em_thread.nil?
80
- AMQP.stop{ EM.stop }
81
- end
82
- end
83
- end