sidekiq-sqs 0.0.8 → 0.0.9
Sign up to get free protection for your applications and to get access to all the features.
data/lib/sidekiq-sqs.rb
CHANGED
@@ -7,6 +7,7 @@ require 'sidekiq-sqs/fetcher'
|
|
7
7
|
require 'sidekiq-sqs/client'
|
8
8
|
require 'sidekiq-sqs/processor'
|
9
9
|
require 'sidekiq-sqs/worker'
|
10
|
+
require 'sidekiq-sqs/aws-sdk/batch_send_failure_patch'
|
10
11
|
|
11
12
|
# TODO The retry server middleware directly writes to a retry zset.
|
12
13
|
# TODO Need a queue-prefix option to support multiple rails envs
|
@@ -25,6 +26,7 @@ module Sidekiq
|
|
25
26
|
Sidekiq::Client.send :include, Sidekiq::Sqs::Client
|
26
27
|
Sidekiq::Processor.send :include, Sidekiq::Sqs::Processor
|
27
28
|
Sidekiq::Worker::ClassMethods.send :include, Sidekiq::Sqs::Worker
|
29
|
+
AWS::SQS::Queue.send :include, Sidekiq::Sqs::AwsSdk::BatchSendFailurePatch
|
28
30
|
|
29
31
|
# Can't figure how to include/extend and not get a private method...
|
30
32
|
def Sidekiq.sqs
|
@@ -0,0 +1,34 @@
|
|
1
|
+
module Sidekiq
|
2
|
+
module Sqs
|
3
|
+
module AwsSdk
|
4
|
+
module BatchSendFailurePatch
|
5
|
+
extend ActiveSupport::Concern
|
6
|
+
|
7
|
+
included do
|
8
|
+
remove_method :batch_failures
|
9
|
+
end
|
10
|
+
|
11
|
+
def batch_failures entries, response
|
12
|
+
response[:failed].inject([]) do |failures, failure|
|
13
|
+
|
14
|
+
entry = entries.find{|e| e[:id] == failure[:id] }
|
15
|
+
|
16
|
+
details = {
|
17
|
+
:error_code => failure[:code],
|
18
|
+
:error_message => failure[:message],
|
19
|
+
:sender_fault => failure[:sender_fault],
|
20
|
+
:message => entry[:message_body] # We need this for retrying
|
21
|
+
}
|
22
|
+
|
23
|
+
if handle = entry[:receipt_handle]
|
24
|
+
details[:receipt_handle] = handle
|
25
|
+
end
|
26
|
+
|
27
|
+
failures << details
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
data/lib/sidekiq-sqs/client.rb
CHANGED
@@ -6,6 +6,25 @@ module Sidekiq
|
|
6
6
|
module Client
|
7
7
|
extend ActiveSupport::Concern
|
8
8
|
|
9
|
+
RETRYABLE_ERRORS = %w(
|
10
|
+
AWS.SimpleQueueService.InternalError
|
11
|
+
InternalError
|
12
|
+
RequestThrottled
|
13
|
+
ServiceUnavailable
|
14
|
+
)
|
15
|
+
|
16
|
+
class BulkInsertionError < StandardError
|
17
|
+
attr_reader :failed
|
18
|
+
|
19
|
+
def initialize(message, failed = [])
|
20
|
+
super(message)
|
21
|
+
@failed = failed
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
class Retryable < StandardError
|
26
|
+
end
|
27
|
+
|
9
28
|
included do
|
10
29
|
class << self
|
11
30
|
remove_method :push
|
@@ -31,22 +50,71 @@ module Sidekiq
|
|
31
50
|
pushed ? normed['jid'] : nil
|
32
51
|
end
|
33
52
|
|
53
|
+
MAX_BULK_RETRIES = 5
|
34
54
|
def push_bulk(items)
|
55
|
+
queue_name, payloads = format_items(items)
|
56
|
+
queue = queue_or_create(queue_name)
|
57
|
+
|
58
|
+
failures, can_retry = bulk_send_to_sqs(queue, payloads)
|
59
|
+
|
60
|
+
retries = 0
|
61
|
+
begin
|
62
|
+
if can_retry.size > 0
|
63
|
+
failed, can_retry = bulk_send_to_sqs(queue, can_retry)
|
64
|
+
failures.concat failed
|
65
|
+
|
66
|
+
raise Retryable if can_retry.size > 0
|
67
|
+
end
|
68
|
+
rescue Retryable
|
69
|
+
sleep retries ** 2
|
70
|
+
retry if (retries += 1) < MAX_BULK_RETRIES
|
71
|
+
end
|
72
|
+
|
73
|
+
if failures.size > 0
|
74
|
+
raise BulkInsertionError.new("Some messages failed to insert", failed)
|
75
|
+
end
|
76
|
+
|
77
|
+
failures.empty? ? payloads.size : nil
|
78
|
+
end
|
79
|
+
|
80
|
+
def format_items(items)
|
35
81
|
normed = normalize_item(items)
|
36
82
|
payloads = items['args'].map do |args|
|
37
83
|
_, payload = process_single(items['class'], normed.merge('args' => args, 'jid' => SecureRandom.hex(12)))
|
38
84
|
payload
|
39
85
|
end.compact
|
40
86
|
|
41
|
-
|
42
|
-
|
87
|
+
[normed['queue'], payloads]
|
88
|
+
end
|
43
89
|
|
44
|
-
|
45
|
-
|
46
|
-
|
90
|
+
def bulk_send_to_sqs(queue, formatted_items)
|
91
|
+
failures = []
|
92
|
+
can_retry = []
|
93
|
+
formatted_items.each_slice(10) do |items|
|
94
|
+
failed, retryable = send_batch_to_sqs(queue, items)
|
95
|
+
|
96
|
+
failures.concat failed
|
97
|
+
can_retry.concat retryable
|
47
98
|
end
|
48
99
|
|
49
|
-
|
100
|
+
[failures, can_retry]
|
101
|
+
end
|
102
|
+
|
103
|
+
def send_batch_to_sqs(queue, formatted_items)
|
104
|
+
failures, retryables = [], []
|
105
|
+
|
106
|
+
begin
|
107
|
+
queue.batch_send(formatted_items)
|
108
|
+
rescue AWS::SQS::Errors::BatchSendError => error
|
109
|
+
retryable, failed = error.failures.partition do |failure|
|
110
|
+
RETRYABLE_ERRORS.include?(failure[:error_code])
|
111
|
+
end
|
112
|
+
|
113
|
+
failures.concat failed
|
114
|
+
retryables.concat retryable
|
115
|
+
end
|
116
|
+
|
117
|
+
[failures, retryables]
|
50
118
|
end
|
51
119
|
|
52
120
|
def queue_or_create(queue)
|
data/lib/sidekiq-sqs/version.rb
CHANGED
@@ -10,6 +10,10 @@ class StubClient
|
|
10
10
|
def self.process_single(*args)
|
11
11
|
end
|
12
12
|
|
13
|
+
def self.normalize_item(*args)
|
14
|
+
Hash.new
|
15
|
+
end
|
16
|
+
|
13
17
|
include Sidekiq::Sqs::Client
|
14
18
|
end
|
15
19
|
|
@@ -24,6 +28,104 @@ describe Sidekiq::Sqs::Client do
|
|
24
28
|
end
|
25
29
|
end
|
26
30
|
|
31
|
+
describe ".bulk_send_to_sqs" do
|
32
|
+
let(:retryable) do
|
33
|
+
{:error_code => 'ServiceUnavailable', :message_body => "blarg"}
|
34
|
+
end
|
35
|
+
let(:failed) do
|
36
|
+
{:error_code => "GFYS", :message_body => "and your little dog, too"}
|
37
|
+
end
|
38
|
+
it "dispatches to .send_batch_to_sqs in groups of 10" do
|
39
|
+
items = 1.upto(20).to_a
|
40
|
+
subject.expects(:send_batch_to_sqs).with(:queue, 1.upto(10).to_a).returns([[], []])
|
41
|
+
subject.expects(:send_batch_to_sqs).with(:queue, 11.upto(20).to_a).returns([[], []])
|
42
|
+
|
43
|
+
subject.bulk_send_to_sqs(:queue, items)
|
44
|
+
end
|
45
|
+
|
46
|
+
it "aggregates failed and retryable messages" do
|
47
|
+
items = 1.upto(20).to_a
|
48
|
+
subject.expects(:send_batch_to_sqs).with(:queue, 1.upto(10).to_a).returns([[failed], []])
|
49
|
+
subject.expects(:send_batch_to_sqs).with(:queue, 11.upto(20).to_a).returns([[], [retryable]])
|
50
|
+
|
51
|
+
subject.bulk_send_to_sqs(:queue, items).should eq([[failed], [retryable]])
|
52
|
+
end
|
53
|
+
end
|
54
|
+
|
55
|
+
describe ".send_batch_to_sqs" do
|
56
|
+
let(:queue) { stub }
|
57
|
+
let(:retryable) do
|
58
|
+
{:error_code => 'ServiceUnavailable', :message_body => "blarg"}
|
59
|
+
end
|
60
|
+
let(:failed) do
|
61
|
+
{:error_code => "GFYS", :message_body => "and your little dog, too"}
|
62
|
+
end
|
63
|
+
|
64
|
+
it "dispatches to the queue" do
|
65
|
+
queue.expects(:batch_send).with(:items)
|
66
|
+
|
67
|
+
subject.send_batch_to_sqs(queue, :items).should eq([[], []])
|
68
|
+
end
|
69
|
+
|
70
|
+
it "aggregates errors correctly" do
|
71
|
+
queue.expects(:batch_send).with(:items).raises(
|
72
|
+
AWS::SQS::Errors::BatchSendError.new(
|
73
|
+
[:sent],
|
74
|
+
[failed, retryable]
|
75
|
+
)
|
76
|
+
)
|
77
|
+
|
78
|
+
subject.send_batch_to_sqs(queue, :items).should eq([
|
79
|
+
[failed], [retryable]
|
80
|
+
])
|
81
|
+
end
|
82
|
+
end
|
83
|
+
|
84
|
+
describe ".push_bulk" do
|
85
|
+
let(:queue) { stub }
|
86
|
+
let(:retries) { [] }
|
87
|
+
let(:fails) { [] }
|
88
|
+
|
89
|
+
before do
|
90
|
+
subject.stubs(queue_or_create: queue)
|
91
|
+
end
|
92
|
+
|
93
|
+
|
94
|
+
it "needs more tests"
|
95
|
+
|
96
|
+
context "when some messages fail to insert" do
|
97
|
+
before do
|
98
|
+
subject.expects(:bulk_send_to_sqs).with(queue, :payloads).returns([fails, retries])
|
99
|
+
end
|
100
|
+
|
101
|
+
context "and all are retryable" do
|
102
|
+
before do
|
103
|
+
retries.push :error
|
104
|
+
|
105
|
+
subject.expects(:format_items).with(:items).returns([:queue, :payloads])
|
106
|
+
end
|
107
|
+
|
108
|
+
it "retries" do
|
109
|
+
subject.expects(:bulk_send_to_sqs).with(queue, [:error]).returns([[], []])
|
110
|
+
|
111
|
+
subject.push_bulk(:items)
|
112
|
+
end
|
113
|
+
end
|
114
|
+
|
115
|
+
context "and there are non-tryable failures" do
|
116
|
+
before do
|
117
|
+
fails.push :fail
|
118
|
+
|
119
|
+
subject.expects(:format_items).with(:items).returns([:queue, :payloads])
|
120
|
+
end
|
121
|
+
|
122
|
+
it "raises an error" do
|
123
|
+
expect { subject.push_bulk :items }.to raise_error(Sidekiq::Sqs::Client::BulkInsertionError)
|
124
|
+
end
|
125
|
+
end
|
126
|
+
end
|
127
|
+
end
|
128
|
+
|
27
129
|
describe ".queue_or_create" do
|
28
130
|
let(:queue) { 'queue' }
|
29
131
|
let(:queues) { stub }
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: sidekiq-sqs
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.0.
|
4
|
+
version: 0.0.9
|
5
5
|
prerelease:
|
6
6
|
platform: ruby
|
7
7
|
authors:
|
@@ -9,7 +9,7 @@ authors:
|
|
9
9
|
autorequire:
|
10
10
|
bindir: bin
|
11
11
|
cert_chain: []
|
12
|
-
date: 2012-10-
|
12
|
+
date: 2012-10-17 00:00:00.000000000 Z
|
13
13
|
dependencies:
|
14
14
|
- !ruby/object:Gem::Dependency
|
15
15
|
name: activesupport
|
@@ -154,6 +154,7 @@ files:
|
|
154
154
|
- README.md
|
155
155
|
- Rakefile
|
156
156
|
- lib/sidekiq-sqs.rb
|
157
|
+
- lib/sidekiq-sqs/aws-sdk/batch_send_failure_patch.rb
|
157
158
|
- lib/sidekiq-sqs/client.rb
|
158
159
|
- lib/sidekiq-sqs/fetcher.rb
|
159
160
|
- lib/sidekiq-sqs/manager.rb
|