micro_q 0.9.2 → 0.9.4

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 141649ff7578ce38ec4f3a05e0f4e921e7c4956f
4
- data.tar.gz: 08fec63af5415737543f29e115482c1b252f34fd
3
+ metadata.gz: f8736323665ab0facb37dfc92fd2463077cfe0f0
4
+ data.tar.gz: e7f7022cd9c3c74efbb68f68d85f49270436ca8c
5
5
  SHA512:
6
- metadata.gz: 76159ff7b731416bad67ef04361dbb98127691316a331355e06a74268c1370bc8d3f2f1cacb87b58d0edf68d7deff63f63abd1ac8faa0d2c372a707ca67bd7e5
7
- data.tar.gz: 80b6d7d74b10270c6ff61aa8d1d7b3cee09bbf3676fa6ecc89cc2c94ce93f15cbb2616dfb768a1e88de705553d3df8bbd38285ae634dd243d8d51d99a63b3858
6
+ metadata.gz: aeeea9df0091afbb6593457b7664b36e21edd7800f1e5dc3cd6d71b6a92ecba6c9e7540413b75d18bf89020b6f65efa033097de0f1372e5ac68ff6bd604d4161
7
+ data.tar.gz: e2ebf0f5f4c4e116c432979276e0d0c436fad69cbc6f6b0475d7e240a0d3b46c824e4ae85c79cea797e8d9f2f06ac3976451dc88e3311152cd43ce29e5634735
data/.rspec CHANGED
@@ -1,2 +1,2 @@
1
1
  --color
2
- --format documentation
2
+ --format Fuubar
data/README.md CHANGED
@@ -49,6 +49,9 @@ MyWorker.update_async(:user_id => user.id)
49
49
 
50
50
  Safely using an ActiveRecord instance via the [Custom Loader](https://github.com/bnorton/micro_q/wiki/Loaders) API
51
51
  ```ruby
52
+ # config/initializers/micro_q
53
+ require 'micro_q/methods/active_record'
54
+
52
55
  # app/models/user.rb
53
56
  class User < Activerecord::Base
54
57
  def update_social_data
@@ -63,6 +66,51 @@ def update
63
66
  end
64
67
  ```
65
68
 
69
+ ##Queues
70
+ By default the queue is an in-memory queue meaning that messages are shared per-process
71
+ and any unprocessed messages are saved to a file when shutdown occurs.
72
+
73
+ The **Redis queue** requires some configuration in your gemfile to keep the runtime dependencies to a minimum
74
+ ```ruby
75
+ # Gemfile
76
+ gem 'redis'
77
+ gem 'micro_q'
78
+
79
+ # config/initializers/micro_q.rb
80
+ require 'redis'
81
+ require 'micro_q'
82
+
83
+ # when MicroQ starts simply use the redis queue
84
+ MicroQ.configure do |config|
85
+ config.queue = MicroQ::Queue::Redis
86
+ end
87
+ ```
88
+
89
+ The **Amazon SQS (coming soon) queue** require some extra configuration in your gemfile.
90
+ ```ruby
91
+ # Gemfile
92
+ gem 'aws-sdk'
93
+ gem 'micro_q'
94
+
95
+ # config/initializers/micro_q.rb
96
+ require 'aws-sdk'
97
+ require 'micro_q'
98
+
99
+ # when MicroQ starts simply use the sqs queue
100
+ # this will take care of all other switchover for the system
101
+ MicroQ.configure do |config|
102
+ config.queue = MicroQ::Queue::Sqs
103
+ config.aws = { :key => 'YOUR KEY', :secret => 'YOUR SECRET' }
104
+ end
105
+
106
+ **Note that when using the SQS Queue only the MicroQ's started via command-line will actually process messages**
107
+
108
+ # Then just use the queues in your workers
109
+ class SomeWorker
110
+ worker :queue => :critical
111
+ end
112
+ ```
113
+
66
114
  ## Contributing
67
115
 
68
116
  1. Fork it
data/bin/microq ADDED
@@ -0,0 +1,11 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require_relative '../lib/micro_q/cli'
4
+
5
+ begin
6
+ MicroQ::CLI.run
7
+ rescue => e
8
+ STDERR.puts e.message
9
+ STDERR.puts e.backtrace.join("\n")
10
+ exit 1
11
+ end
@@ -0,0 +1,60 @@
1
+ require 'slop'
2
+ require 'micro_q'
3
+
4
+ module MicroQ
5
+ class CLI
6
+ def self.run
7
+ @cli ||= new
8
+ @cli.parse
9
+ @cli.verify!
10
+ @cli.setup
11
+ end
12
+
13
+ def parse
14
+ opts = Slop.parse do
15
+ banner 'Usage: microq [options]'
16
+
17
+ on 'r=', 'The path to the rails application'
18
+ on 'require=', 'The path to the rails application'
19
+ on 'w=', 'The number of worker threads'
20
+ on 'workers=', 'The number of worker threads'
21
+ end
22
+
23
+ @workers = opts[:workers] || opts[:w]
24
+ @require = opts[:require] || opts[:r]
25
+ end
26
+
27
+ def verify!
28
+ raise "Need a valid path to a rails application, you gave us #{@require}\n" unless /environment\.rb/ === @require || File.exist?("#{@require}/config/application.rb")
29
+ end
30
+
31
+ def setup
32
+ puts 'Requiring rails...'
33
+ require 'rails'
34
+
35
+ puts 'Requiring rails application...'
36
+ if File.directory?(@require)
37
+ require File.expand_path("#{@require}/config/environment.rb")
38
+ else
39
+ require @require
40
+ end
41
+
42
+ aws_keys = MicroQ.config.aws.try(:keys) || []
43
+ raise 'SQS mode requires an aws :key and :secret see https://github.com/bnorton/micro_q/wiki/Named-Queues' unless aws_keys.include?(:key) && aws_keys.include?(:secret)
44
+
45
+ MicroQ.configure do |config|
46
+ config.queue = MicroQ::Queue::Sqs # set workers after since this must set workers=0 internally
47
+ config.workers = @workers.to_i if @workers
48
+ config['worker_mode?'] = true
49
+ end
50
+
51
+ puts "Running micro_q in SQS mode with #{MicroQ.config.workers} workers... Hit ctl+c to stop...\n"
52
+ MicroQ.start
53
+
54
+ sleep
55
+ rescue Interrupt
56
+ puts 'Exiting via interrupt'
57
+ exit(1)
58
+ end
59
+ end
60
+ end
@@ -13,11 +13,13 @@ module MicroQ
13
13
  'workers' => 5,
14
14
  'timeout' => 120,
15
15
  'interval' => 5,
16
+ 'env' => defined?(Rails) ? Rails.env : 'development',
16
17
  'middleware' => Middleware::Chain.new,
17
18
  'manager' => Manager::Default,
18
19
  'worker' => Worker::Standard,
19
20
  'queue' => Queue::Default,
20
21
  'statistics' => Statistics::Default,
22
+ 'aws' => {},
21
23
  'redis_pool' => { :size => 15, :timeout => 1 },
22
24
  'redis' => { :host => 'localhost', :port => 6379 }
23
25
  }
@@ -31,6 +33,19 @@ module MicroQ
31
33
  @data[key.to_s]
32
34
  end
33
35
 
36
+ def queue=(q)
37
+ if q == Queue::Sqs
38
+ require 'aws-sdk'
39
+
40
+ @data['sqs?'] = true
41
+ @data['workers'] = 0
42
+ end
43
+
44
+ @data['queue'] = q
45
+ rescue LoadError
46
+ raise "Looks you need `gem 'aws-sdk'` in your Gemfile to use the SQS queue."
47
+ end
48
+
34
49
  def method_missing(method, *args)
35
50
  case method
36
51
  when /(.+)=$/ then @data[$1] = args.first
@@ -0,0 +1,37 @@
1
+ module MicroQ
2
+ module Fetcher
3
+ class Sqs
4
+ include Celluloid
5
+ attr_reader :name
6
+
7
+ def initialize(name, manager)
8
+ @name = name.to_s
9
+ @manager = manager
10
+ end
11
+
12
+ def start
13
+ defer do
14
+ client.messages.tap do |messages|
15
+ @manager.receive_messages!(messages) if messages.any?
16
+ end
17
+ end
18
+
19
+ after(2) { start }
20
+ end
21
+
22
+ def add_message(message, time=nil)
23
+ message['run_at'] = time.to_f if time
24
+
25
+ defer do
26
+ client.messages_create(message)
27
+ end
28
+ end
29
+
30
+ private
31
+
32
+ def client
33
+ @client ||= MicroQ::SqsClient.new(name)
34
+ end
35
+ end
36
+ end
37
+ end
@@ -0,0 +1,17 @@
1
+ module MicroQ
2
+ module Inspect
3
+ def self.included(base)
4
+ base.send(:include, InstanceMethods)
5
+ end
6
+
7
+ module InstanceMethods
8
+ def inspect
9
+ "#<#{self.class.name}: #{self.object_id}>"
10
+ end
11
+ end
12
+ end
13
+ end
14
+
15
+ ['Worker::Standard', 'Queue::Default', 'Queue::Redis', 'Queue::Sqs', 'Manager::Default', 'Fetcher::Sqs'].each do |postfix|
16
+ MicroQ::Util.constantize("MicroQ::#{postfix}").send(:include, MicroQ::Inspect)
17
+ end
@@ -23,6 +23,8 @@ module MicroQ
23
23
  attr_reader :queue, :workers
24
24
 
25
25
  def start
26
+ return if queue_only?
27
+
26
28
  count = workers.size
27
29
 
28
30
  if (messages = queue.dequeue(count)).any?
@@ -53,6 +55,7 @@ module MicroQ
53
55
  end
54
56
 
55
57
  @busy ||= []
58
+ @workers ||= []
56
59
 
57
60
  build_missing_workers
58
61
  end
@@ -61,9 +64,10 @@ module MicroQ
61
64
 
62
65
  # Don't shrink the pool if the config changes
63
66
  def build_missing_workers
64
- @workers ||= []
67
+ return if queue_only?
65
68
 
66
69
  workers.select!(&:alive?)
70
+ @busy.select!(&:alive?)
67
71
 
68
72
  missing_worker_count.times do
69
73
  workers << MicroQ.config.worker.new_link(current_actor)
@@ -78,6 +82,10 @@ module MicroQ
78
82
  (@workers + @busy).each {|w| w.terminate if w.alive? }
79
83
  end
80
84
 
85
+ def queue_only?
86
+ @queue_only ||= MicroQ.config.sqs? && !MicroQ.config.worker_mode?
87
+ end
88
+
81
89
  def self.shutdown?
82
90
  !!@shutdown
83
91
  end
@@ -0,0 +1,85 @@
1
+ module MicroQ
2
+ module Queue
3
+ class Sqs
4
+ include Celluloid
5
+
6
+ exit_handler :build_missing_fetchers
7
+
8
+ attr_accessor :messages
9
+ attr_reader :fetchers, :entries, :later
10
+
11
+ def initialize
12
+ @lock = Mutex.new
13
+
14
+ @messages, @entries, @later = [], [], []
15
+ @fetcher_map = {}
16
+
17
+ build_missing_fetchers
18
+ end
19
+
20
+ def push(item)
21
+ async.sync_push(item)
22
+ end
23
+
24
+ def sync_push(item, options={})
25
+ item, options = MicroQ::Util.stringify(item, options)
26
+ item['class'] = item['class'].to_s
27
+
28
+ MicroQ.middleware.client.call(item, options) do
29
+ args, queue_name = [item], verify_queue(item['queue'].to_s)
30
+
31
+ if (time = options['when'])
32
+ args << time.to_f
33
+ end
34
+
35
+ @fetcher_map[queue_name].add_message(*args)
36
+ end
37
+ end
38
+
39
+ def receive_messages(*items)
40
+ @lock.synchronize do
41
+ (@messages += items).flatten!
42
+ end
43
+ end
44
+
45
+ def dequeue(limit=30)
46
+ return [] unless limit > 0 && messages.any?
47
+
48
+ @lock.synchronize do
49
+ limit.times.collect do
50
+ messages.pop
51
+ end.compact
52
+ end
53
+ end
54
+
55
+ def verify_queue(name)
56
+ QUEUES_KEYS.include?(name) ? name : 'default'
57
+ end
58
+
59
+ def self.shutdown!
60
+ @shutdown = true
61
+ end
62
+
63
+ private
64
+
65
+ def self.shutdown?
66
+ @shutdown
67
+ end
68
+
69
+ def build_missing_fetchers(*)
70
+ return if self.class.shutdown?
71
+
72
+ @fetchers = QUEUES_KEYS.map do |name|
73
+ ((existing = @fetcher_map[name]) && existing.alive? && existing) ||
74
+ MicroQ::Fetcher::Sqs.new_link(name, current_actor).tap do |fetcher|
75
+ @fetcher_map[name] = fetcher
76
+ fetcher.start!
77
+ end
78
+ end
79
+ end
80
+
81
+ QUEUES = { 'low' => 1, 'default' => 3, 'critical' => 5 }
82
+ QUEUES_KEYS = QUEUES.keys
83
+ end
84
+ end
85
+ end
data/lib/micro_q/queue.rb CHANGED
@@ -1,5 +1,6 @@
1
1
  require 'micro_q/queue/default'
2
2
  require 'micro_q/queue/redis'
3
+ require 'micro_q/queue/sqs'
3
4
 
4
5
  ##
5
6
  # The Queueing interface
@@ -0,0 +1,47 @@
1
+ module MicroQ
2
+ class SqsClient
3
+ attr_reader :url
4
+
5
+ def initialize(name)
6
+ @name = "#{MicroQ.config.env}_#{name}"
7
+ @url = client.create_queue(:queue_name => @name)[:queue_url]
8
+ end
9
+
10
+ def messages
11
+ response = client.receive_message(
12
+ :queue_url => url,
13
+ :wait_time_seconds => 10,
14
+ :max_number_of_messages => 10,
15
+ :visibility_timeout => 5 * 60
16
+ )
17
+
18
+ ((response && response[:messages]) || []).collect do |message|
19
+ JSON.parse(message[:body]).merge(
20
+ 'sqs_id' => message[:message_id],
21
+ 'sqs_handle' => message[:receipt_handle],
22
+ 'sqs_queue' => url
23
+ )
24
+ end
25
+ end
26
+
27
+ def messages_create(message)
28
+ attrs = {
29
+ :queue_url => url,
30
+ :message_body => message.to_json
31
+ }
32
+
33
+ attrs[:delay_seconds] = (message['run_at'].to_i - Time.now.to_i) if message.key?('run_at')
34
+
35
+ client.send_message(attrs)[:message_id]
36
+ end
37
+
38
+ private
39
+
40
+ def client
41
+ @client ||= AWS::SQS::Client.new(
42
+ :access_key_id => MicroQ.config.aws[:key],
43
+ :secret_access_key => MicroQ.config.aws[:secret]
44
+ )
45
+ end
46
+ end
47
+ end
@@ -1,7 +1,7 @@
1
1
  module MicroQ
2
2
  MAJOR = 0
3
3
  MINOR = 9
4
- POINT = 2
4
+ POINT = 4
5
5
 
6
6
  VERSION = [MAJOR, MINOR, POINT].join('.')
7
7
  end
data/lib/micro_q.rb CHANGED
@@ -6,6 +6,8 @@ require 'micro_q/util'
6
6
  require 'micro_q/config'
7
7
  require 'micro_q/manager'
8
8
 
9
+ Celluloid.logger = nil
10
+
9
11
  module MicroQ
10
12
  def self.config
11
13
  @config ||= Config.new
@@ -51,17 +53,20 @@ require 'micro_q/proxies'
51
53
  require 'micro_q/dsl'
52
54
  require 'micro_q/worker'
53
55
  require 'micro_q/queue'
56
+ require 'micro_q/sqs_client'
54
57
 
58
+ require 'micro_q/fetchers/sqs'
55
59
  require 'micro_q/redis'
60
+ require 'micro_q/inspect'
56
61
 
57
62
  require 'micro_q/wrappers/action_mailer'
58
63
 
64
+
59
65
  # add Class and Instance methods first then
60
66
  # override with additional extensions
61
67
 
62
68
  require 'micro_q/methods/class'
63
69
  require 'micro_q/methods/instance'
64
- require 'micro_q/methods/active_record'
65
70
  require 'micro_q/methods/action_mailer'
66
71
 
67
72
  require 'micro_q/statistics/base'
@@ -71,4 +76,5 @@ require 'micro_q/statistics/redis'
71
76
  # There is a better way coming soon 2/18/13
72
77
  at_exit do
73
78
  MicroQ::Manager::Default.shutdown!
79
+ MicroQ::Queue::Sqs.shutdown!
74
80
  end
data/micro_q.gemspec CHANGED
@@ -19,7 +19,9 @@ Gem::Specification.new do |gem|
19
19
 
20
20
  gem.add_dependency "celluloid", '~> 0.12.0'
21
21
  gem.add_dependency "connection_pool"
22
+ gem.add_dependency "slop"
22
23
  gem.add_development_dependency "rake"
24
+ gem.add_development_dependency "fuubar"
23
25
  gem.add_development_dependency "rspec"
24
26
  gem.add_development_dependency "timecop"
25
27
  gem.add_development_dependency "psych"
@@ -27,4 +29,5 @@ Gem::Specification.new do |gem|
27
29
  gem.add_development_dependency "actionmailer", "> 3.2.0"
28
30
  gem.add_development_dependency "sqlite3-ruby"
29
31
  gem.add_development_dependency "mock_redis"
32
+ gem.add_development_dependency "aws-sdk"
30
33
  end
@@ -1,4 +1,4 @@
1
- shared_examples_for 'Queue#sync_push' do
1
+ shared_examples 'Queue#sync_push' do
2
2
  it 'should add to the entries' do
3
3
  subject.sync_push(item)
4
4
 
@@ -36,6 +36,14 @@ describe MicroQ::Config do
36
36
  subject.timeout.should == 120
37
37
  end
38
38
 
39
+ it 'should have the default env' do
40
+ subject.env.should == 'development'
41
+ end
42
+
43
+ it 'should not be in sqs mode' do
44
+ subject.should_not be_sqs
45
+ end
46
+
39
47
  it 'should have middleware chain' do
40
48
  subject.middleware.class.should == MicroQ::Middleware::Chain
41
49
  end
@@ -68,4 +76,43 @@ describe MicroQ::Config do
68
76
  subject.statistics.should == MicroQ::Statistics::Default
69
77
  end
70
78
  end
79
+
80
+ describe 'when rails is defined' do
81
+ before do
82
+ module Rails end
83
+ def Rails.env; 'the-env' end
84
+ end
85
+
86
+ it 'should have the rails env' do
87
+ subject.env.should == 'the-env'
88
+ end
89
+ end
90
+
91
+ describe '#queue=' do
92
+ before do
93
+ subject.queue = 'blah-blah'
94
+ end
95
+
96
+ it 'should have the given queue' do
97
+ subject.queue.should == 'blah-blah'
98
+ end
99
+
100
+ describe 'when setting the SQS queue' do
101
+ before do
102
+ subject.queue = MicroQ::Queue::Sqs
103
+ end
104
+
105
+ it 'should have the given queue' do
106
+ subject.queue.should == MicroQ::Queue::Sqs
107
+ end
108
+
109
+ it 'should enable sqs mode' do
110
+ subject.sqs?.should == true
111
+ end
112
+
113
+ it 'should have zero workers' do
114
+ subject.workers.should == 0
115
+ end
116
+ end
117
+ end
71
118
  end
@@ -0,0 +1,81 @@
1
+ require 'spec_helper'
2
+
3
+ describe MicroQ::Fetcher::Sqs do
4
+ let(:queue) { mock(MicroQ::Queue::Sqs, :receive_messages! => nil) }
5
+
6
+ subject { MicroQ::Fetcher::Sqs.new(:low, queue) }
7
+
8
+ before do
9
+ @client = mock(MicroQ::SqsClient, :messages => [])
10
+ MicroQ::SqsClient.stub(:new => @client)
11
+ end
12
+
13
+ describe '.new' do
14
+ it 'should have the queue name' do
15
+ subject.name.should == 'low'
16
+ end
17
+ end
18
+
19
+ describe '#start' do
20
+ it 'should create an sqs client' do
21
+ MicroQ::SqsClient.should_receive(:new).with('low').and_return(@client)
22
+
23
+ subject.start
24
+ end
25
+
26
+ describe 'when called again' do
27
+ it 'should create an sqs client' do
28
+ MicroQ::SqsClient.should_receive(:new).and_return(@client)
29
+ subject.start
30
+
31
+ MicroQ::SqsClient.rspec_verify
32
+ MicroQ::SqsClient.rspec_reset
33
+
34
+ MicroQ::SqsClient.should_not_receive(:new)
35
+
36
+ subject.start
37
+ end
38
+ end
39
+
40
+ it 'should request messages from the queue' do
41
+ @client.should_receive(:messages)
42
+
43
+ subject.start
44
+ end
45
+
46
+ describe 'when there are messages in the queue' do
47
+ let(:messages) { 2.times.map {|i| mock("message_#{i}") }}
48
+
49
+ before do
50
+ @client.stub(:messages).and_return(messages)
51
+ end
52
+
53
+ it 'should hand of the messages to the manager' do
54
+ queue.should_receive(:receive_messages!).with(messages)
55
+
56
+ subject.start
57
+ end
58
+ end
59
+ end
60
+
61
+ describe '#add_message' do
62
+ let(:message) { {'class' => 'FooBar'} }
63
+ let(:add_message) { subject.add_message(message) }
64
+
65
+ it 'should create the message' do
66
+ @client.should_receive(:messages_create).with(message)
67
+
68
+ add_message
69
+ end
70
+
71
+ describe 'when the message has an associated time' do
72
+ let(:add_message) { subject.add_message(message, Time.now.to_i) }
73
+
74
+ it 'should send the time' do
75
+ @client.should_receive(:messages_create).with(message.merge('run_at' => Time.now.to_i))
76
+
77
+ add_message
78
+ end
79
+ end
80
+ end
81
+ end
@@ -82,6 +82,38 @@ describe MicroQ::Manager::Default do
82
82
 
83
83
  subject.start
84
84
  end
85
+
86
+ describe 'when the manager is in SQS mode' do
87
+ before do
88
+ MicroQ.config['sqs?'] = true
89
+ end
90
+
91
+ it 'should not perform the items' do
92
+ @queue.should_not_receive(:dequeue)
93
+ [@worker1, @worker2].each {|w| w.should_not_receive(:perform!) }
94
+
95
+ subject.start
96
+ end
97
+
98
+ describe 'when in worker mode' do
99
+ before do
100
+ MicroQ.config['worker_mode?'] = true
101
+ end
102
+
103
+ it 'should dequeue the number of free workers' do
104
+ @queue.should_receive(:dequeue).with(2)
105
+
106
+ subject.start
107
+ end
108
+
109
+ it 'should perform the items' do
110
+ @worker1.should_receive(:perform!).with(@other_item)
111
+ @worker2.should_receive(:perform!).with(@item)
112
+
113
+ subject.start
114
+ end
115
+ end
116
+ end
85
117
  end
86
118
  end
87
119
 
@@ -144,6 +176,43 @@ describe MicroQ::Manager::Default do
144
176
 
145
177
  subject.workers.should == [@worker1, @new_worker2]
146
178
  end
179
+
180
+ describe 'when a busy worker has died' do
181
+ before do
182
+ subject.wrapped_object.instance_variable_set(:@busy, [@worker2])
183
+ end
184
+
185
+ it 'should restart the dead worker' do
186
+ MicroQ::Worker::Standard.should_receive(:new_link).and_return(@new_worker2)
187
+
188
+ death.call
189
+ end
190
+
191
+ it 'should remove the worker from the busy list' do
192
+ death.call
193
+
194
+ subject.wrapped_object.instance_variable_get(:@busy).should == []
195
+ end
196
+
197
+ it 'should have the new worker' do
198
+ death.call
199
+
200
+ subject.workers.should == [@worker1, @new_worker2]
201
+ end
202
+ end
203
+
204
+ describe 'when in SQS mode' do
205
+ before do
206
+ MicroQ.config['sqs?'] = true
207
+ end
208
+
209
+ it 'should have the original items' do
210
+ death.call
211
+
212
+ subject.queue.should == @queue
213
+ subject.workers.should == [@worker1, @worker2]
214
+ end
215
+ end
147
216
  end
148
217
  end
149
218
  end
@@ -65,11 +65,6 @@ describe MicroQ::Queue::Redis do
65
65
  describe '#dequeue' do
66
66
  let(:item) { { 'class' => 'MyWorker', 'args' => [] } }
67
67
 
68
- class MyWorker
69
- def perform(*)
70
- end
71
- end
72
-
73
68
  describe 'when there are entries' do
74
69
  before do
75
70
  subject.sync_push(item)
@@ -0,0 +1,206 @@
1
+ require 'spec_helper'
2
+
3
+ describe MicroQ::Queue::Sqs do
4
+ let(:item) { { 'class' => 'MyWorker', 'args' => [4] } }
5
+
6
+ before do
7
+ @fetcher = mock('fetcher', :start! => true)
8
+ MicroQ::Fetcher::Sqs.stub(:new_link).and_return(@fetcher)
9
+ end
10
+
11
+ describe '.new' do
12
+ it 'should create three fetchers' do
13
+ MicroQ::Fetcher::Sqs.should_receive(:new_link).exactly(3).and_return(@fetcher)
14
+
15
+ subject
16
+ end
17
+
18
+ it 'should send the current actor along too' do
19
+ MicroQ::Fetcher::Sqs.should_receive(:new_link).exactly(3).with(anything, subject).and_return(@fetcher)
20
+
21
+ subject
22
+ end
23
+
24
+ it 'should start the fetcher' do
25
+ @fetcher.should_receive(:start!)
26
+
27
+ subject
28
+ end
29
+
30
+ it 'should have the fetchers' do
31
+ subject.fetchers.uniq.should == [@fetcher]
32
+ end
33
+ end
34
+
35
+ describe '#receive_messages' do
36
+ let(:messages) { 3.times.map {|i| mock("message_#{i}")} }
37
+
38
+ it 'should have no messages' do
39
+ subject.messages.should == []
40
+ end
41
+
42
+ describe 'when messages have given back' do
43
+ before do
44
+ subject.receive_messages(messages.first(1))
45
+ end
46
+
47
+ it 'should have the messages' do
48
+ subject.messages.should == [messages.first]
49
+ end
50
+
51
+ describe 'when more messages have been received' do
52
+ before do
53
+ subject.receive_messages(messages.last(2))
54
+ end
55
+
56
+ it 'should have the messages' do
57
+ subject.messages.should == messages
58
+ end
59
+ end
60
+ end
61
+ end
62
+
63
+ describe '#sync_push' do
64
+ before do
65
+ @fetchers = [:low, :default, :critical].collect do |name|
66
+ mock('MicroQ::Fetcher::Sqs : ' + name.to_s, :start! => nil).tap do |fetcher|
67
+ MicroQ::Fetcher::Sqs.stub(:new_link).with(name.to_s, anything).and_return(fetcher)
68
+ end
69
+ end
70
+ end
71
+
72
+ it 'should add to the entries' do
73
+ @fetchers[1].should_receive(:add_message).with(item)
74
+
75
+ subject.sync_push(item)
76
+ end
77
+
78
+ it 'should stringify the class' do
79
+ @fetchers[1].should_receive(:add_message).with(hash_including('class' => 'MyWorker'))
80
+
81
+ subject.sync_push(:class => MyWorker)
82
+ end
83
+
84
+ [:low, :default, :critical].each_with_index do |name, i|
85
+ describe "when the message has a queue named #{name}" do
86
+ before do
87
+ item['queue'] = name
88
+ end
89
+
90
+ it 'should create the message on the right queue' do
91
+ @fetchers[i].should_receive(:add_message).with(item)
92
+
93
+ subject.sync_push(item)
94
+ end
95
+ end
96
+ end
97
+
98
+ describe 'when given the \'when\' key' do
99
+ let(:worker) { [item, { 'when' => (Time.now + 100).to_i }] }
100
+
101
+ it 'should schedule the item for later' do
102
+ @fetchers[1].should_receive(:add_message).with(item, (Time.now + 100).to_i)
103
+
104
+ subject.sync_push(*worker)
105
+ end
106
+
107
+ it 'should process the middleware chain' do
108
+ MicroQ.middleware.client.should_receive(:call) do |payload, options|
109
+ payload['class'].should == 'MyWorker'
110
+ payload['args'].should == [4]
111
+ options['when'].should == (Time.now + 100).to_i
112
+ end
113
+
114
+ subject.sync_push(*worker)
115
+ end
116
+ end
117
+
118
+ describe 'when given the symbol :when key' do
119
+ let(:worker) { [item, { :when => (Time.now + 100).to_i }] }
120
+
121
+ it 'should schedule the item for later' do
122
+ @fetchers[1].should_receive(:add_message).with(item, (Time.now + 100).to_i)
123
+
124
+ subject.sync_push(*worker)
125
+ end
126
+ end
127
+
128
+ describe 'client middleware' do
129
+ it 'should process the middleware chain' do
130
+ MicroQ.middleware.client.should_receive(:call) do |payload, opts|
131
+ payload['class'].should == 'MyWorker'
132
+ payload['args'].should == [4]
133
+
134
+ opts['when'].should == 'now'
135
+ end
136
+
137
+ subject.sync_push(item, 'when' => 'now')
138
+ end
139
+ end
140
+ end
141
+
142
+ describe '#push' do
143
+ before do
144
+ @async = mock(Celluloid::ActorProxy)
145
+ subject.stub(:async).and_return(@async)
146
+ end
147
+
148
+ it 'should asynchronously push the item' do
149
+ @async.should_receive(:sync_push).with(item)
150
+
151
+ subject.push(item)
152
+ end
153
+ end
154
+
155
+ describe '#dequeue' do
156
+ let(:items) { 2.times.map {|i| { 'class' => 'SqsWorker', 'args' => [i] }} }
157
+ let(:item) { items.first }
158
+
159
+ class SqsWorker
160
+ def perform(*)
161
+ end
162
+ end
163
+
164
+ describe 'when there are messages' do
165
+ before do
166
+ subject.messages = items.map(&:dup)
167
+ end
168
+
169
+ it 'should return the limited number of items' do
170
+ subject.dequeue(1).should == [items.last]
171
+ end
172
+
173
+ it 'should remove the item from the list' do
174
+ subject.dequeue.should == items.reverse
175
+
176
+ subject.messages.should == []
177
+ end
178
+ end
179
+
180
+ describe 'when there are many messages' do
181
+ let(:messages) do
182
+ 5.times.collect {|i|
183
+ item.dup.tap {|it| it['args'] = [i]}
184
+ }
185
+ end
186
+
187
+ before do
188
+ subject.messages = messages.map(&:dup)
189
+ end
190
+
191
+ it 'should return up to the limit number of items' do
192
+ subject.dequeue(4).should == messages.last(4).reverse
193
+
194
+ subject.messages.should include(messages.first)
195
+ subject.dequeue.should == messages.first(1)
196
+ end
197
+
198
+ it 'should remove the items' do
199
+ subject.dequeue.should == messages.reverse
200
+ subject.dequeue.should == []
201
+
202
+ subject.messages.should == []
203
+ end
204
+ end
205
+ end
206
+ end
@@ -0,0 +1,166 @@
1
+ require 'spec_helper'
2
+
3
+ describe MicroQ::SqsClient, :aws => true do
4
+ let(:url) { 'http://the.queue/' }
5
+
6
+ subject { MicroQ::SqsClient.new('low') }
7
+
8
+ before do
9
+ MicroQ.config.env = 'dev-env'
10
+
11
+ @client = mock(AWS::SQS::Client, :receive_message => {}, :create_queue => {:queue_url => url})
12
+ AWS::SQS::Client.stub(:new).and_return(@client)
13
+ end
14
+
15
+ describe '.new' do
16
+ it 'should create the queue' do
17
+ @client.should_receive(:create_queue).and_return({})
18
+
19
+ subject
20
+ end
21
+
22
+ it 'should prefix the name with the environment' do
23
+ @client.should_receive(:create_queue).with(
24
+ :queue_name => 'dev-env_low'
25
+ ).and_return({})
26
+
27
+ subject
28
+ end
29
+
30
+ it 'should have the url' do
31
+ subject.url.should == url
32
+ end
33
+
34
+ describe 'when the call fails' do
35
+ before do
36
+ @client.stub(:create_queue).and_raise
37
+ end
38
+
39
+ it 'should error' do
40
+ expect {
41
+ subject
42
+ }.to raise_error
43
+ end
44
+ end
45
+ end
46
+
47
+ describe '#messages' do
48
+ let(:messages) { subject.messages }
49
+
50
+ it 'should be an empty' do
51
+ messages.should == []
52
+ end
53
+
54
+ it 'should connect to the api for reading messages' do
55
+ @client.should_receive(:receive_message)
56
+
57
+ messages
58
+ end
59
+
60
+ it 'should have the queue url' do
61
+ @client.should_receive(:receive_message).with(hash_including(:queue_url => url))
62
+
63
+ messages
64
+ end
65
+
66
+ it 'should timeout after 10 seconds' do
67
+ @client.should_receive(:receive_message).with(hash_including(:wait_time_seconds => 10))
68
+
69
+ messages
70
+ end
71
+
72
+ it 'should request 10 items' do
73
+ @client.should_receive(:receive_message).with(hash_including(:max_number_of_messages => 10))
74
+
75
+ messages
76
+ end
77
+
78
+ it 'should make message available again after 5 minutes' do
79
+ @client.should_receive(:receive_message).with(hash_including(:visibility_timeout => 5 * 60))
80
+
81
+ messages
82
+ end
83
+
84
+ describe 'when messages are returned (body as json)' do
85
+ let(:response) do
86
+ { :messages => 3.times.map {|i|
87
+ {
88
+ :body => {:id => i}.to_json,
89
+ :message_id => "id:#{i*5}",
90
+ :receipt_handle => "hand:#{i+10}"
91
+ }
92
+ }}
93
+ end
94
+
95
+ before do
96
+ @client.stub(:receive_message).and_return(response)
97
+ end
98
+
99
+ it 'should return the messages' do
100
+ messages.map {|m| m['id']}.should == [0, 1, 2]
101
+ end
102
+
103
+ it 'should merge in the message id' do
104
+ messages.map {|m| m['sqs_id']}.should == ['id:0', 'id:5', 'id:10']
105
+ end
106
+
107
+ it 'should merge in the message id' do
108
+ messages.map {|m| m['sqs_handle']}.should == ['hand:10', 'hand:11', 'hand:12']
109
+ end
110
+
111
+ it 'should merge in the queue url' do
112
+ messages.map {|m| m['sqs_queue']}.uniq.should == [url]
113
+ end
114
+ end
115
+ end
116
+
117
+ describe '#messages_create' do
118
+ let(:message) { {:class => 'MyWorker', :args => ['hi']} }
119
+ let(:send_message) { subject.messages_create(message) }
120
+
121
+ before do
122
+ @response = {:message_id => '10'}
123
+ @client.stub(:send_message).and_return(@response)
124
+ end
125
+
126
+ it 'should send the message' do
127
+ @client.should_receive(:send_message).and_return(@response)
128
+
129
+ send_message
130
+ end
131
+
132
+ it 'should send it for the queue' do
133
+ @client.should_receive(:send_message).with(hash_including(:queue_url => url)).and_return(@response)
134
+
135
+ send_message
136
+ end
137
+
138
+ it 'should send it for the queue' do
139
+ @client.should_receive(:send_message).with(hash_including(:message_body => message.to_json)).and_return(@response)
140
+
141
+ send_message
142
+ end
143
+
144
+ it 'should not delay the message' do
145
+ @client.should_receive(:send_message).with(hash_excluding(:delay_seconds)).and_return(@response)
146
+
147
+ send_message
148
+ end
149
+
150
+ it 'should return the message id' do
151
+ send_message.should == '10'
152
+ end
153
+
154
+ describe 'when the message is to be run later' do
155
+ before do
156
+ message['run_at'] = (Time.now + 60 * 60)
157
+ end
158
+
159
+ it 'should set the delay for sqs' do
160
+ @client.should_receive(:send_message).with(hash_including(:delay_seconds => 60*60)).and_return(@response)
161
+
162
+ send_message
163
+ end
164
+ end
165
+ end
166
+ end
data/spec/spec_helper.rb CHANGED
@@ -13,17 +13,34 @@ silence_warnings do
13
13
  Redis = MockRedis
14
14
  end
15
15
 
16
+ class MyWorker; end
17
+
16
18
  RSpec.configure do |config|
17
19
  config.treat_symbols_as_metadata_keys_with_true_values = true
18
20
  config.run_all_when_everything_filtered = true
19
21
 
20
22
  config.order = 'default'
21
23
 
24
+ config.before :all do
25
+ GC.disable
26
+ end
27
+
22
28
  config.before :each do
23
29
  MicroQ.send :clear
24
30
  MicroQ.redis {|r| r.flushdb }
25
31
  end
26
32
 
33
+ config.before :each, :aws => true do
34
+ require 'aws-sdk'
35
+ end
36
+
37
+ config.before :each, :middleware => true do
38
+ class WorkerClass; end
39
+
40
+ @worker = WorkerClass.new
41
+ @payload = { 'class' => 'WorkerClass', 'args' => [1, 2], 'queue' => 'a-queue'}
42
+ end
43
+
27
44
  config.before :each, :active_record => true do
28
45
  require 'active_record'
29
46
  require 'sqlite3' # https://github.com/luislavena/sqlite3-ruby
@@ -52,11 +69,8 @@ RSpec.configure do |config|
52
69
  @_db.rollback
53
70
  end
54
71
 
55
- config.before :each, :middleware => true do
56
- class WorkerClass; end
57
-
58
- @worker = WorkerClass.new
59
- @payload = { 'class' => 'WorkerClass', 'args' => [1, 2], 'queue' => 'a-queue'}
72
+ config.after :all do
73
+ GC.enable
60
74
  end
61
75
  end
62
76
 
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: micro_q
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.9.2
4
+ version: 0.9.4
5
5
  platform: ruby
6
6
  authors:
7
7
  - Brian Norton
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2013-03-25 00:00:00.000000000 Z
11
+ date: 2013-03-29 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: celluloid
@@ -38,6 +38,20 @@ dependencies:
38
38
  - - '>='
39
39
  - !ruby/object:Gem::Version
40
40
  version: '0'
41
+ - !ruby/object:Gem::Dependency
42
+ name: slop
43
+ requirement: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - '>='
46
+ - !ruby/object:Gem::Version
47
+ version: '0'
48
+ type: :runtime
49
+ prerelease: false
50
+ version_requirements: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - '>='
53
+ - !ruby/object:Gem::Version
54
+ version: '0'
41
55
  - !ruby/object:Gem::Dependency
42
56
  name: rake
43
57
  requirement: !ruby/object:Gem::Requirement
@@ -52,6 +66,20 @@ dependencies:
52
66
  - - '>='
53
67
  - !ruby/object:Gem::Version
54
68
  version: '0'
69
+ - !ruby/object:Gem::Dependency
70
+ name: fuubar
71
+ requirement: !ruby/object:Gem::Requirement
72
+ requirements:
73
+ - - '>='
74
+ - !ruby/object:Gem::Version
75
+ version: '0'
76
+ type: :development
77
+ prerelease: false
78
+ version_requirements: !ruby/object:Gem::Requirement
79
+ requirements:
80
+ - - '>='
81
+ - !ruby/object:Gem::Version
82
+ version: '0'
55
83
  - !ruby/object:Gem::Dependency
56
84
  name: rspec
57
85
  requirement: !ruby/object:Gem::Requirement
@@ -150,9 +178,24 @@ dependencies:
150
178
  - - '>='
151
179
  - !ruby/object:Gem::Version
152
180
  version: '0'
181
+ - !ruby/object:Gem::Dependency
182
+ name: aws-sdk
183
+ requirement: !ruby/object:Gem::Requirement
184
+ requirements:
185
+ - - '>='
186
+ - !ruby/object:Gem::Version
187
+ version: '0'
188
+ type: :development
189
+ prerelease: false
190
+ version_requirements: !ruby/object:Gem::Requirement
191
+ requirements:
192
+ - - '>='
193
+ - !ruby/object:Gem::Version
194
+ version: '0'
153
195
  description: ''
154
196
  email: brian.nort@gmail.com
155
- executables: []
197
+ executables:
198
+ - microq
156
199
  extensions: []
157
200
  extra_rdoc_files: []
158
201
  files:
@@ -164,9 +207,13 @@ files:
164
207
  - LICENSE
165
208
  - README.md
166
209
  - Rakefile
210
+ - bin/microq
167
211
  - lib/micro_q.rb
212
+ - lib/micro_q/cli.rb
168
213
  - lib/micro_q/config.rb
169
214
  - lib/micro_q/dsl.rb
215
+ - lib/micro_q/fetchers/sqs.rb
216
+ - lib/micro_q/inspect.rb
170
217
  - lib/micro_q/manager.rb
171
218
  - lib/micro_q/manager/default.rb
172
219
  - lib/micro_q/methods/action_mailer.rb
@@ -189,7 +236,9 @@ files:
189
236
  - lib/micro_q/queue.rb
190
237
  - lib/micro_q/queue/default.rb
191
238
  - lib/micro_q/queue/redis.rb
239
+ - lib/micro_q/queue/sqs.rb
192
240
  - lib/micro_q/redis.rb
241
+ - lib/micro_q/sqs_client.rb
193
242
  - lib/micro_q/statistics/base.rb
194
243
  - lib/micro_q/statistics/default.rb
195
244
  - lib/micro_q/statistics/redis.rb
@@ -203,6 +252,7 @@ files:
203
252
  - spec/helpers/queues_examples.rb
204
253
  - spec/lib/config_spec.rb
205
254
  - spec/lib/dsl_spec.rb
255
+ - spec/lib/fetchers/sqs_spec.rb
206
256
  - spec/lib/manager/default_spec.rb
207
257
  - spec/lib/methods/action_mailer_spec.rb
208
258
  - spec/lib/methods/active_record_spec.rb
@@ -221,6 +271,8 @@ files:
221
271
  - spec/lib/proxies/instance_spec.rb
222
272
  - spec/lib/queue/default_spec.rb
223
273
  - spec/lib/queue/redis_spec.rb
274
+ - spec/lib/queue/sqs_spec.rb
275
+ - spec/lib/sqs_client_spec.rb
224
276
  - spec/lib/statistics/default_spec.rb
225
277
  - spec/lib/statistics/redis_spec.rb
226
278
  - spec/lib/util_spec.rb
@@ -255,6 +307,7 @@ test_files:
255
307
  - spec/helpers/queues_examples.rb
256
308
  - spec/lib/config_spec.rb
257
309
  - spec/lib/dsl_spec.rb
310
+ - spec/lib/fetchers/sqs_spec.rb
258
311
  - spec/lib/manager/default_spec.rb
259
312
  - spec/lib/methods/action_mailer_spec.rb
260
313
  - spec/lib/methods/active_record_spec.rb
@@ -273,6 +326,8 @@ test_files:
273
326
  - spec/lib/proxies/instance_spec.rb
274
327
  - spec/lib/queue/default_spec.rb
275
328
  - spec/lib/queue/redis_spec.rb
329
+ - spec/lib/queue/sqs_spec.rb
330
+ - spec/lib/sqs_client_spec.rb
276
331
  - spec/lib/statistics/default_spec.rb
277
332
  - spec/lib/statistics/redis_spec.rb
278
333
  - spec/lib/util_spec.rb