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
@@ -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
- pushed = false
42
- queue = queue_or_create(normed['queue'])
87
+ [normed['queue'], payloads]
88
+ end
43
89
 
44
- payloads.each_slice(10) do |items|
45
- ## FIXME error handling
46
- pushed = queue.batch_send( items )
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
- pushed ? payloads.size : nil
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)
@@ -1,5 +1,5 @@
1
1
  module Sidekiq
2
2
  module Sqs
3
- VERSION = "0.0.8"
3
+ VERSION = "0.0.9"
4
4
  end
5
5
  end
@@ -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.8
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-09 00:00:00.000000000 Z
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