sidekiq-sqs 0.0.11 → 0.0.17

Sign up to get free protection for your applications and to get access to all the features.
data/README.md CHANGED
@@ -30,7 +30,7 @@ That'll turn it all on. Nothing else _should_ have to change.
30
30
 
31
31
  ## Caveats
32
32
 
33
- * Scheduling is completely broken
33
+ * Scheduling uses SQS message timers. The amount of time you can schedule a worker must be 0 to 900 seconds (15 mins).
34
34
  * ?
35
35
 
36
36
  ## Contributing
@@ -35,15 +35,35 @@ module Sidekiq
35
35
  end
36
36
 
37
37
  module ClassMethods
38
+ def environmental_queue_name(queue_name)
39
+ return queue_name unless defined?(::Rails)
40
+
41
+ if queue_name =~ /^#{::Rails.env}_/
42
+ queue_name
43
+ else
44
+ %(#{::Rails.env}_#{queue_name})
45
+ end
46
+ end
47
+
48
+ def clear_queue(queue_name)
49
+ queue = queue_or_create(environmental_queue_name queue_name)
50
+
51
+ while message = queue.receive_message
52
+ message.delete
53
+ end
54
+
55
+ queue
56
+ end
57
+
38
58
  def push(item)
39
59
  normed = normalize_item(item)
40
60
  normed, payload = process_single(item['class'], normed)
41
61
 
42
62
  pushed = false
43
63
  if normed['at']
44
- # FIXME - SQS only supports a delay of 15.minutes
45
- # probably a schedule queue? or keep this in redis??
46
- pushed = conn.zadd('schedule', normed['at'].to_s, payload)
64
+ delay_seconds = (normed['at'] - Time.now.to_f).round
65
+ raise "The number of seconds to delay should be from 0 to 900 (15 mins)." if delay_seconds > 900
66
+ pushed = queue_or_create(normed['queue']).send_message(payload, { delay_seconds: delay_seconds })
47
67
  else
48
68
  pushed = queue_or_create(normed['queue']).send_message(payload)
49
69
  end if normed
@@ -102,7 +122,7 @@ module Sidekiq
102
122
 
103
123
  def send_batch_to_sqs(queue, formatted_items)
104
124
  failures, retryables = [], []
105
-
125
+
106
126
  begin
107
127
  queue.batch_send(formatted_items)
108
128
  rescue AWS::SQS::Errors::BatchSendError => error
@@ -113,11 +133,12 @@ module Sidekiq
113
133
  failures.concat failed
114
134
  retryables.concat retryable
115
135
  end
116
-
136
+
117
137
  [failures, retryables]
118
138
  end
119
139
 
120
140
  def queue_or_create(queue)
141
+ queue = environmental_queue_name(queue)
121
142
  begin
122
143
  Sidekiq.sqs.queues.named(queue.to_s)
123
144
  rescue AWS::SQS::Errors::NonExistentQueue
@@ -1,6 +1,8 @@
1
1
  module Sidekiq
2
2
  module Sqs
3
3
  module Fetcher
4
+ QueueNotDoneError = Class.new(StandardError)
5
+
4
6
  extend ActiveSupport::Concern
5
7
 
6
8
  included do
@@ -12,7 +14,7 @@ module Sidekiq
12
14
  initialize_without_sqs(mgr, queues, strict)
13
15
 
14
16
  # Fix Queue names
15
- @queues = @queues.map {|queue| queue.gsub(/^queue:/, '') }
17
+ @queues = @queues.map {|queue| Sidekiq::Client.environmental_queue_name queue.gsub(/^queue:/, '') }
16
18
  @unique_queues = @queues.uniq
17
19
  end
18
20
 
@@ -29,14 +31,17 @@ module Sidekiq
29
31
  queues.pop # Last entry is TIMEOUT
30
32
 
31
33
  msg = queues.inject(nil) do |message, queue|
32
- message || Sidekiq.sqs.queues.named(queue).receive_message
34
+ message || fetch_single_message(queue)
33
35
  end
34
36
 
35
37
  if msg
36
38
  @mgr.assign!(msg, File.basename(msg.queue.url))
37
39
  else
38
- after(0) { fetch }
40
+ after(5) { fetch }
39
41
  end
42
+ rescue QueueNotDoneError
43
+ # Just wait
44
+ after(5) { fetch }
40
45
  rescue => ex
41
46
  logger.error("Error fetching message from queues (#{@queues.join(', ')}): #{ex}")
42
47
  logger.error(ex.backtrace.first)
@@ -45,6 +50,20 @@ module Sidekiq
45
50
  end
46
51
  end
47
52
  end
53
+
54
+ def fetch_single_message(queue)
55
+ queue = Sidekiq.sqs.queues.named(queue)
56
+ message = queue.receive_message
57
+
58
+ if @strictly_ordered_queues && message.nil?
59
+ # If messages being processed, raise
60
+ if queue.invisible_messages != 0
61
+ raise QueueNotDoneError
62
+ end
63
+ else
64
+ message
65
+ end
66
+ end
48
67
  end
49
68
  end
50
69
  end
@@ -39,7 +39,7 @@ module Sidekiq
39
39
 
40
40
  @done = true
41
41
  Sidekiq::Fetcher.done!
42
- @fetcher.finalize
42
+ #@fetcher.finalize
43
43
 
44
44
  logger.info { "Shutting down #{@ready.size} quiet workers" }
45
45
  @ready.each { |x| x.terminate if x.alive? }
@@ -7,17 +7,29 @@ module Sidekiq
7
7
  extend ActiveSupport::Concern
8
8
 
9
9
  included do
10
+ remove_method :stats
10
11
  alias_method_chain :process, :sqs
11
12
  end
12
13
 
13
14
  def process_with_sqs(sqs_message, queue)
14
15
  begin
15
- process_without_sqs(Zlib::Inflate.inflate(Base64.decode64(sqs_message.body)), queue)
16
- ensure
16
+ process_without_sqs(Zlib::Inflate.inflate(Base64.decode64(sqs_message.body)), queue).tap do
17
+ sqs_message.delete
18
+ end
19
+ rescue Celluloid::Task::TerminatedError => error
20
+ # If our thread was killed, requeue the job (SURE HOPE IT'S IDEMPOTENT!)
21
+ sqs_message.visibility_timeout = 10
22
+ rescue => fatal
17
23
  # FIXME Maybe we want to requeue here, if there's a non-job related error?
24
+ # If retry = true, requeue here
18
25
  sqs_message.delete
19
26
  end
20
27
  end
28
+
29
+ def stats(*args)
30
+ # FIXME this should, like, do stuff
31
+ yield
32
+ end
21
33
  end
22
34
  end
23
35
  end
@@ -1,7 +1,22 @@
1
+ require 'active_support/inflector'
2
+
1
3
  module Sidekiq
2
4
  module Util
3
5
  def constantize(string)
4
- string.constantize
6
+ # Try just requiring the files first
7
+
8
+ begin
9
+ string.constantize
10
+ rescue LoadError => ex
11
+ begin
12
+ require string.split("::").map(&:underscore).join("/")
13
+ string.constantize
14
+ rescue LoadError => ex
15
+ STDERR.puts("Error loading constant: #{string}. #{ex.message}")
16
+ STDERR.puts(ex.backtrace.join("\n"))
17
+ raise ex
18
+ end
19
+ end
5
20
  end
6
21
  end
7
22
  end
@@ -1,5 +1,5 @@
1
1
  module Sidekiq
2
2
  module Sqs
3
- VERSION = "0.0.11"
3
+ VERSION = "0.0.17"
4
4
  end
5
5
  end
@@ -28,6 +28,65 @@ describe Sidekiq::Sqs::Client do
28
28
  end
29
29
  end
30
30
 
31
+ describe ".environmental_queue_name" do
32
+ before do
33
+ if defined?(::Rails)
34
+ ::Rails.stubs(env: "test")
35
+ else
36
+ ::Rails = stub(env: "test")
37
+ end
38
+ end
39
+
40
+ it "prepends the environment name" do
41
+ subject.environmental_queue_name("queue").should eq("test_queue")
42
+ end
43
+
44
+ it "doesn't prepend if it's already prepended" do
45
+ subject.environmental_queue_name("test_queue").should eq("test_queue")
46
+ end
47
+ end
48
+
49
+ describe ".push" do
50
+
51
+ context "when scheduling a job" do
52
+ let(:item) { { 'class' => :klass } }
53
+
54
+ it "dispatches to .send_message with delay_seconds" do
55
+ normalized = { 'at' => Time.now.to_f + 900, 'jid' => 'abc123' }
56
+ subject.expects(:normalize_item).with(item).returns(normalized)
57
+ subject.expects(:process_single).with(:klass, normalized).returns([normalized, :payload])
58
+
59
+ queue = mock { expects(:send_message).with(:payload, { delay_seconds: 900 }).returns(true) }
60
+ subject.stubs(queue_or_create: queue)
61
+
62
+ subject.push(item).should eq('abc123')
63
+ end
64
+
65
+ it "raises an error if the amount to delay is > 900 seconds" do
66
+ normalized = { 'at' => Time.now.to_f + 901 }
67
+ subject.expects(:normalize_item).with(item).returns(normalized)
68
+ subject.expects(:process_single).with(:klass, normalized).returns([normalized, :payload])
69
+ subject.stubs(:queue_or_create)
70
+
71
+ expect { subject.push(item) }.to raise_error
72
+ end
73
+
74
+ end
75
+
76
+ context "when queueing a job normally" do
77
+ let(:item) { { 'class' => :klass } }
78
+ let(:queue) { mock { expects(:send_message).with(:payload) } }
79
+
80
+ it "dispatches to .send_message" do
81
+ subject.expects(:normalize_item).with(item).returns(:normalized)
82
+ subject.expects(:process_single).with(:klass, :normalized).returns([:normalized, :payload])
83
+ subject.stubs(queue_or_create: queue)
84
+
85
+ subject.push(item)
86
+ end
87
+ end
88
+ end
89
+
31
90
  describe ".bulk_send_to_sqs" do
32
91
  let(:retryable) do
33
92
  {:error_code => 'ServiceUnavailable', :message_body => "blarg"}
@@ -132,12 +191,14 @@ describe Sidekiq::Sqs::Client do
132
191
  before { Sidekiq.stubs(sqs: stub(queues: queues)) }
133
192
 
134
193
  it "returns the queue if it exists" do
194
+ subject.expects(:environmental_queue_name).with(queue).returns(queue)
135
195
  queues.expects(:named).with(queue).returns(:queue)
136
196
 
137
197
  subject.queue_or_create(queue).should eq(:queue)
138
198
  end
139
199
 
140
200
  it "creates the queue if it doesn't exists" do
201
+ subject.expects(:environmental_queue_name).with(queue).returns(queue)
141
202
  queues.expects(:named).with(queue).raises(AWS::SQS::Errors::NonExistentQueue.new)
142
203
  queues.expects(:create).with(queue).returns(:queue)
143
204
 
@@ -0,0 +1,58 @@
1
+ require 'spec_helper'
2
+
3
+ class FetcherStub
4
+ def fetch
5
+ end
6
+
7
+ def initialize(mgr, queues, strict)
8
+ @mgr, @queues, @strictly_ordered_queues = mgr, queues, strict
9
+ end
10
+
11
+ include Sidekiq::Sqs::Fetcher
12
+ end
13
+
14
+ describe Sidekiq::Sqs::Fetcher do
15
+ let(:manager) { stub }
16
+ let(:queues) { [] }
17
+ let(:queue) { stub }
18
+
19
+ before { Sidekiq.stubs(sqs: stub(queues: stub(named: queue))) }
20
+
21
+ subject { FetcherStub.new(manager, queues, strict) }
22
+ context "in strict mode" do
23
+ let(:strict) { true }
24
+
25
+ describe "#fetch_single_message" do
26
+ it "returns a message if found" do
27
+ queue.stubs(receive_message: :message)
28
+
29
+ subject.fetch_single_message(:queue).should eq(:message)
30
+ end
31
+
32
+ it "returns nil if no messages found when not processing" do
33
+ queue.stubs(receive_message: nil, invisible_messages: 0)
34
+
35
+ subject.fetch_single_message(:queue).should be_nil
36
+ end
37
+
38
+ it "raises an error if no messages found when processing" do
39
+ queue.stubs(receive_message: nil, invisible_messages: 1)
40
+
41
+ expect do
42
+ subject.fetch_single_message(:queue)
43
+ end.to raise_error(Sidekiq::Sqs::Fetcher::QueueNotDoneError)
44
+ end
45
+ end
46
+ end
47
+
48
+ context "in not strict mode" do
49
+ let(:strict) { false }
50
+ describe "#fetch_single_message" do
51
+ it "returns nil if no messages and processing" do
52
+ queue.stubs(receive_message: nil, invisible_messages: 1)
53
+
54
+ subject.fetch_single_message(:queue).should be_nil
55
+ end
56
+ end
57
+ end
58
+ end
@@ -4,6 +4,10 @@ class StubProcessor
4
4
  def process(msg, queue)
5
5
  end
6
6
 
7
+ def stats(*args)
8
+ raise RuntimeError.new
9
+ end
10
+
7
11
  include Sidekiq::Sqs::Processor
8
12
  end
9
13
 
@@ -22,4 +26,10 @@ describe Sidekiq::Sqs::Processor do
22
26
  subject.process(message, queue).should eq(:processed)
23
27
  end
24
28
  end
29
+
30
+ describe "#stats" do
31
+ it "just yields" do
32
+ expect {|b| subject.stats(&b) }.to yield_control
33
+ end
34
+ end
25
35
  end
metadata CHANGED
@@ -1,152 +1,115 @@
1
- --- !ruby/object:Gem::Specification
1
+ --- !ruby/object:Gem::Specification
2
2
  name: sidekiq-sqs
3
- version: !ruby/object:Gem::Version
4
- version: 0.0.11
3
+ version: !ruby/object:Gem::Version
5
4
  prerelease:
5
+ version: 0.0.17
6
6
  platform: ruby
7
- authors:
7
+ authors:
8
8
  - Jon Moses
9
9
  autorequire:
10
10
  bindir: bin
11
11
  cert_chain: []
12
- date: 2012-10-19 00:00:00.000000000 Z
13
- dependencies:
14
- - !ruby/object:Gem::Dependency
12
+
13
+ date: 2013-02-04 00:00:00 Z
14
+ dependencies:
15
+ - !ruby/object:Gem::Dependency
15
16
  name: activesupport
16
- requirement: !ruby/object:Gem::Requirement
17
- none: false
18
- requirements:
19
- - - ! '>'
20
- - !ruby/object:Gem::Version
21
- version: 3.0.0
22
- type: :runtime
23
17
  prerelease: false
24
- version_requirements: !ruby/object:Gem::Requirement
18
+ requirement: &id001 !ruby/object:Gem::Requirement
25
19
  none: false
26
- requirements:
27
- - - ! '>'
28
- - !ruby/object:Gem::Version
20
+ requirements:
21
+ - - ">"
22
+ - !ruby/object:Gem::Version
29
23
  version: 3.0.0
30
- - !ruby/object:Gem::Dependency
31
- name: sidekiq
32
- requirement: !ruby/object:Gem::Requirement
33
- none: false
34
- requirements:
35
- - - ~>
36
- - !ruby/object:Gem::Version
37
- version: 2.3.2
38
24
  type: :runtime
25
+ version_requirements: *id001
26
+ - !ruby/object:Gem::Dependency
27
+ name: sidekiq
39
28
  prerelease: false
40
- version_requirements: !ruby/object:Gem::Requirement
29
+ requirement: &id002 !ruby/object:Gem::Requirement
41
30
  none: false
42
- requirements:
31
+ requirements:
43
32
  - - ~>
44
- - !ruby/object:Gem::Version
33
+ - !ruby/object:Gem::Version
45
34
  version: 2.3.2
46
- - !ruby/object:Gem::Dependency
47
- name: aws-sdk
48
- requirement: !ruby/object:Gem::Requirement
49
- none: false
50
- requirements:
51
- - - ~>
52
- - !ruby/object:Gem::Version
53
- version: 1.6.6
54
35
  type: :runtime
36
+ version_requirements: *id002
37
+ - !ruby/object:Gem::Dependency
38
+ name: aws-sdk
55
39
  prerelease: false
56
- version_requirements: !ruby/object:Gem::Requirement
40
+ requirement: &id003 !ruby/object:Gem::Requirement
57
41
  none: false
58
- requirements:
42
+ requirements:
59
43
  - - ~>
60
- - !ruby/object:Gem::Version
44
+ - !ruby/object:Gem::Version
61
45
  version: 1.6.6
62
- - !ruby/object:Gem::Dependency
46
+ type: :runtime
47
+ version_requirements: *id003
48
+ - !ruby/object:Gem::Dependency
63
49
  name: rspec
64
- requirement: !ruby/object:Gem::Requirement
65
- none: false
66
- requirements:
67
- - - ! '>='
68
- - !ruby/object:Gem::Version
69
- version: '0'
70
- type: :development
71
50
  prerelease: false
72
- version_requirements: !ruby/object:Gem::Requirement
51
+ requirement: &id004 !ruby/object:Gem::Requirement
73
52
  none: false
74
- requirements:
75
- - - ! '>='
76
- - !ruby/object:Gem::Version
77
- version: '0'
78
- - !ruby/object:Gem::Dependency
79
- name: rb-fsevent
80
- requirement: !ruby/object:Gem::Requirement
81
- none: false
82
- requirements:
83
- - - ~>
84
- - !ruby/object:Gem::Version
85
- version: 0.9.1
53
+ requirements:
54
+ - - ">="
55
+ - !ruby/object:Gem::Version
56
+ version: "0"
86
57
  type: :development
58
+ version_requirements: *id004
59
+ - !ruby/object:Gem::Dependency
60
+ name: rb-fsevent
87
61
  prerelease: false
88
- version_requirements: !ruby/object:Gem::Requirement
62
+ requirement: &id005 !ruby/object:Gem::Requirement
89
63
  none: false
90
- requirements:
64
+ requirements:
91
65
  - - ~>
92
- - !ruby/object:Gem::Version
66
+ - !ruby/object:Gem::Version
93
67
  version: 0.9.1
94
- - !ruby/object:Gem::Dependency
95
- name: guard
96
- requirement: !ruby/object:Gem::Requirement
97
- none: false
98
- requirements:
99
- - - ! '>='
100
- - !ruby/object:Gem::Version
101
- version: '0'
102
68
  type: :development
69
+ version_requirements: *id005
70
+ - !ruby/object:Gem::Dependency
71
+ name: guard
103
72
  prerelease: false
104
- version_requirements: !ruby/object:Gem::Requirement
105
- none: false
106
- requirements:
107
- - - ! '>='
108
- - !ruby/object:Gem::Version
109
- version: '0'
110
- - !ruby/object:Gem::Dependency
111
- name: guard-rspec
112
- requirement: !ruby/object:Gem::Requirement
73
+ requirement: &id006 !ruby/object:Gem::Requirement
113
74
  none: false
114
- requirements:
115
- - - ! '>='
116
- - !ruby/object:Gem::Version
117
- version: '0'
75
+ requirements:
76
+ - - ">="
77
+ - !ruby/object:Gem::Version
78
+ version: "0"
118
79
  type: :development
80
+ version_requirements: *id006
81
+ - !ruby/object:Gem::Dependency
82
+ name: guard-rspec
119
83
  prerelease: false
120
- version_requirements: !ruby/object:Gem::Requirement
121
- none: false
122
- requirements:
123
- - - ! '>='
124
- - !ruby/object:Gem::Version
125
- version: '0'
126
- - !ruby/object:Gem::Dependency
127
- name: mocha
128
- requirement: !ruby/object:Gem::Requirement
84
+ requirement: &id007 !ruby/object:Gem::Requirement
129
85
  none: false
130
- requirements:
131
- - - ~>
132
- - !ruby/object:Gem::Version
133
- version: 0.12.3
86
+ requirements:
87
+ - - ">="
88
+ - !ruby/object:Gem::Version
89
+ version: "0"
134
90
  type: :development
91
+ version_requirements: *id007
92
+ - !ruby/object:Gem::Dependency
93
+ name: mocha
135
94
  prerelease: false
136
- version_requirements: !ruby/object:Gem::Requirement
95
+ requirement: &id008 !ruby/object:Gem::Requirement
137
96
  none: false
138
- requirements:
97
+ requirements:
139
98
  - - ~>
140
- - !ruby/object:Gem::Version
99
+ - !ruby/object:Gem::Version
141
100
  version: 0.12.3
142
- description: SQS backed job store for Sidekiq. Redis is still used for stats/job worker
143
- tracking
144
- email:
101
+ type: :development
102
+ version_requirements: *id008
103
+ description: SQS backed job store for Sidekiq. Redis is still used for stats/job worker tracking
104
+ email:
145
105
  - jon@burningbush.us
146
106
  executables: []
107
+
147
108
  extensions: []
109
+
148
110
  extra_rdoc_files: []
149
- files:
111
+
112
+ files:
150
113
  - .gitignore
151
114
  - Gemfile
152
115
  - Guardfile
@@ -165,36 +128,41 @@ files:
165
128
  - lib/top_level.rb
166
129
  - sidekiq-sqs.gemspec
167
130
  - spec/sidekiq-sqs/client_spec.rb
131
+ - spec/sidekiq-sqs/fetcher_spec.rb
168
132
  - spec/sidekiq-sqs/processor_spec.rb
169
133
  - spec/sidekiq-sqs/worker_spec.rb
170
134
  - spec/spec_helper.rb
171
135
  - spec/util_spec.rb
172
136
  homepage: http://github.com/jmoses/sidekiq-sqs
173
137
  licenses: []
138
+
174
139
  post_install_message:
175
140
  rdoc_options: []
176
- require_paths:
141
+
142
+ require_paths:
177
143
  - lib
178
- required_ruby_version: !ruby/object:Gem::Requirement
144
+ required_ruby_version: !ruby/object:Gem::Requirement
179
145
  none: false
180
- requirements:
181
- - - ! '>='
182
- - !ruby/object:Gem::Version
183
- version: '0'
184
- required_rubygems_version: !ruby/object:Gem::Requirement
146
+ requirements:
147
+ - - ">="
148
+ - !ruby/object:Gem::Version
149
+ version: "0"
150
+ required_rubygems_version: !ruby/object:Gem::Requirement
185
151
  none: false
186
- requirements:
187
- - - ! '>='
188
- - !ruby/object:Gem::Version
189
- version: '0'
152
+ requirements:
153
+ - - ">="
154
+ - !ruby/object:Gem::Version
155
+ version: "0"
190
156
  requirements: []
157
+
191
158
  rubyforge_project:
192
159
  rubygems_version: 1.8.24
193
160
  signing_key:
194
161
  specification_version: 3
195
162
  summary: SQS backed job store for Sidekiq
196
- test_files:
163
+ test_files:
197
164
  - spec/sidekiq-sqs/client_spec.rb
165
+ - spec/sidekiq-sqs/fetcher_spec.rb
198
166
  - spec/sidekiq-sqs/processor_spec.rb
199
167
  - spec/sidekiq-sqs/worker_spec.rb
200
168
  - spec/spec_helper.rb