sidekiq-sqs 0.0.11 → 0.0.17

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/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