pwwka 0.6.0 → 0.7.0.RC1

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: 374c2180b982d19cd0dcc1d6dd73abf3d80f493b
4
- data.tar.gz: fb315b10d90d96cbb250c034fdf4a8d2f1186ac3
3
+ metadata.gz: 86fe2875f9d9221237358a25d30bcecee224554a
4
+ data.tar.gz: c1cad94e3a3d146b18480354297b5214544ecca8
5
5
  SHA512:
6
- metadata.gz: c6fa81ed74d72ec9f7739d8e7e4cd6de94ef172b5e7d11900e3ec32f03cc3c7f13a466fefc1107d43668b29a4c123d60838839de4fd8222d2a2284b25befd2b9
7
- data.tar.gz: 885daa6a5d3615f113064c5a906d608d76aa1916f6174665221c12dd9270dfb553c4d9c3bbc0a36879a3aec335daeec22a1132f7e7375ab69508faf477146dd8
6
+ metadata.gz: b56b412dc2ce73c3dec1ca1ca1f20432bbea8565699aff56e006fa00df9605e627a58db4a6aca9db248890d5cf70a2e69e5bbe4fb093f066d65a2c706584f4ea
7
+ data.tar.gz: 154f1b930db5502349df6b0c8b15723ce4f726c95b4d742db5b277078c097afb529b17639634a9b31b079497bf1b4dfe171e55b9e903ea924d55a9746f83947d
data/Gemfile.lock CHANGED
@@ -1,7 +1,7 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- pwwka (0.6.0)
4
+ pwwka (0.7.0.RC1)
5
5
  activemodel
6
6
  activesupport
7
7
  bunny
@@ -10,23 +10,20 @@ PATH
10
10
  GEM
11
11
  remote: https://www.rubygems.org/
12
12
  specs:
13
- activemodel (4.2.6)
14
- activesupport (= 4.2.6)
15
- builder (~> 3.1)
16
- activesupport (4.2.6)
13
+ activemodel (5.0.0.1)
14
+ activesupport (= 5.0.0.1)
15
+ activesupport (5.0.0.1)
16
+ concurrent-ruby (~> 1.0, >= 1.0.2)
17
17
  i18n (~> 0.7)
18
- json (~> 1.7, >= 1.7.7)
19
18
  minitest (~> 5.1)
20
- thread_safe (~> 0.3, >= 0.3.4)
21
19
  tzinfo (~> 1.1)
22
20
  amq-protocol (2.0.1)
23
- builder (3.2.2)
24
- bunny (2.3.1)
21
+ bunny (2.6.1)
25
22
  amq-protocol (>= 2.0.1)
23
+ concurrent-ruby (1.0.2)
26
24
  diff-lcs (1.2.5)
27
25
  i18n (0.7.0)
28
- json (1.8.3)
29
- minitest (5.8.4)
26
+ minitest (5.10.1)
30
27
  mono_logger (1.1.0)
31
28
  multi_json (1.11.2)
32
29
  rack (1.6.4)
@@ -76,4 +73,4 @@ DEPENDENCIES
76
73
  rspec
77
74
 
78
75
  BUNDLED WITH
79
- 1.12.5
76
+ 1.13.6
data/README.md CHANGED
@@ -98,12 +98,12 @@ send_message!(payload, routing_key)
98
98
  ```
99
99
 
100
100
  ### Error Handling
101
+
101
102
  This method accepts several strategies for handling errors, pass in using the `on_error` parameter:
102
103
 
103
104
  * `:raise`: Log the error and raise the exception received from Bunny. (default strategy)
104
105
  * `:ignore`: Log the error and return false.
105
- * `:resque`: Log the error and return false. Also, enqueue a job with Resque
106
- to send the message. See `send_message_async` below.
106
+ * `:resque`: Log the error and return false. Also, enqueue a job with Resque to send the message. See `send_message_async` below. **Note, this doesn't guarantee the message will actually be sent—it just guarantees an attempt is made to queue a Resque job [which could fail]**
107
107
 
108
108
  ### Delayed Messages
109
109
  You might want to delay sending a message (for example, if you have just created a database
@@ -217,6 +217,32 @@ class ClientIndexMessageHandler
217
217
 
218
218
  end
219
219
  ```
220
+ #### Handling Errors
221
+
222
+ By default, if your handler raises an uncaught exception, the message will be Nacked, **but not requeued**. This means
223
+ it's dropped on the floor and likely won't have been completely processed.
224
+
225
+ You can configure `requeue_on_error` in the configuration to change this behavior:
226
+
227
+ ```ruby
228
+ require 'pwwka'
229
+ Pwwka.configure do |config|
230
+
231
+ # ...
232
+
233
+ config.requeue_on_error = true
234
+ end
235
+ ```
236
+
237
+ This will requeue the message **exactly once**. It uses the headers to check if the message has been retried. If it
238
+ hasn't, and your handler raises an exception, it will be placed back on the queue. The second time your handler
239
+ processes it, there is a header indicating it's been retried, so if a failure happens again, the message **is not
240
+ requeued**.
241
+
242
+ Because requeuing puts the message at the head of the queue, a hard failure will result in an infinite loop, which will
243
+ lead to filling up your queue. Nevertheless, this should address intermittent failures.
244
+
245
+ **It is recommended that you set this option**. It's off for backwards compatibility.
220
246
 
221
247
  #### Handling Messages with Resque
222
248
 
@@ -27,6 +27,18 @@ module Pwwka
27
27
  end
28
28
 
29
29
  def delayed_queue
30
+ # This works by hacking the dead letter exchange concept with a timeout.
31
+ # We set up a delayed exchange that has a delayed queue. This queue, configured below,
32
+ # sets its dead letter exchange to be the main exchange (topic_exchange above).
33
+ #
34
+ # This means that when a message send to the delayed queue is either nack'ed with no retry OR
35
+ # it's TTL expires, it will be sent to the configured dead letter exchange, which is the main topic_exchange.
36
+ #
37
+ # Since nothing is actually consuming messages on the delayed queue, the only way messages can be removed and
38
+ # sent back to the main exchange is if their TTL expires. As you can see in Pwwka::Transmitter#send_delayed_message!
39
+ # we set an expiration on the message and send it to the delayed exchange. This means that the delay time is the TTL,
40
+ # so the messages sits in the delayed queue until its TTL/delay expires, and then it's sent onto the
41
+ # main exchange for everyone to consume. Thus creating a delay.
30
42
  raise_if_delayed_not_allowed
31
43
  @delayed_queue ||= begin
32
44
  queue = channel.queue("pwwka_delayed_#{Pwwka.environment}", durable: true,
@@ -35,7 +47,7 @@ module Pwwka
35
47
  })
36
48
  queue.bind(delayed_exchange)
37
49
  queue
38
- end
50
+ end
39
51
  end
40
52
  alias :create_delayed_queue :delayed_queue
41
53
 
@@ -10,6 +10,7 @@ module Pwwka
10
10
  attr_accessor :logger
11
11
  attr_accessor :options
12
12
  attr_accessor :send_message_resque_backoff_strategy
13
+ attr_accessor :requeue_on_error
13
14
 
14
15
  def initialize
15
16
  @rabbit_mq_host = nil
@@ -20,6 +21,7 @@ module Pwwka
20
21
  @send_message_resque_backoff_strategy = [5, #intermittent glitch?
21
22
  60, # quick interruption
22
23
  600, 600, 600] # longer-term outage?
24
+ @requeue_on_error = false
23
25
  end
24
26
 
25
27
  def payload_logging
@@ -29,9 +29,13 @@ module Pwwka
29
29
  receiver.ack(delivery_info.delivery_tag)
30
30
  logf "Processed Message on %{queue_name} -> %{payload}, %{routing_key}", queue_name: queue_name, payload: payload, routing_key: delivery_info.routing_key
31
31
  rescue => e
32
- logf "Error Processing Message on %{queue_name} -> %{payload}, %{routing_key}: %{exception}", queue_name: queue_name, payload: payload, routing_key: delivery_info.routing_key, exception: e, at: :error
33
- # no requeue
34
- receiver.nack(delivery_info.delivery_tag)
32
+ if Pwwka.configuration.requeue_on_error && !delivery_info.redelivered
33
+ logf "Retrying an Error Processing Message on %{queue_name} -> %{payload}, %{routing_key}: %{exception}: %{backtrace}", queue_name: queue_name, payload: payload, routing_key: delivery_info.routing_key, exception: e, backtrace: e.backtrace.join(';'), at: :error
34
+ receiver.nack_requeue(delivery_info.delivery_tag)
35
+ else
36
+ logf "Error Processing Message on %{queue_name} -> %{payload}, %{routing_key}: %{exception}: %{backtrace}", queue_name: queue_name, payload: payload, routing_key: delivery_info.routing_key, exception: e, backtrace: e.backtrace.join(';'), at: :error
37
+ receiver.nack(delivery_info.delivery_tag)
38
+ end
35
39
  end
36
40
  end
37
41
  rescue Interrupt => _
@@ -44,7 +48,7 @@ module Pwwka
44
48
 
45
49
  def topic_queue
46
50
  @topic_queue ||= begin
47
- queue = channel.queue(queue_name, durable: true)
51
+ queue = channel.queue(queue_name, durable: true, arguments: {})
48
52
  queue.bind(topic_exchange, routing_key: routing_key)
49
53
  queue
50
54
  end
@@ -63,14 +67,14 @@ module Pwwka
63
67
  end
64
68
 
65
69
  def drop_queue
66
- topic_queue.purge
70
+ topic_queue.purge
67
71
  topic_queue.delete
68
72
  end
69
73
 
70
74
  def test_teardown
71
75
  drop_queue
72
76
  topic_exchange.delete
73
- channel_connector.connection_close
77
+ channel_connector.connection_close
74
78
  end
75
79
 
76
80
  end
@@ -30,7 +30,7 @@ module Pwwka
30
30
  end
31
31
  end
32
32
 
33
- # Get the message on the queue as TestHandler::Message.
33
+ # Get the message on the queue as TestHandler::Message
34
34
  def pop_message
35
35
  delivery_info, properties, payload = test_queue.pop
36
36
  Message.new(delivery_info,
@@ -70,7 +70,7 @@ module Pwwka
70
70
  channel_connector.delayed_exchange.delete
71
71
  end
72
72
 
73
- channel_connector.connection_close
73
+ channel_connector.connection_close
74
74
  end
75
75
 
76
76
  # Simple class to hold a popped message.
data/lib/pwwka/version.rb CHANGED
@@ -1,3 +1,3 @@
1
1
  module Pwwka
2
- VERSION = '0.6.0'
2
+ VERSION = '0.7.0.RC1'
3
3
  end
@@ -25,6 +25,7 @@ describe Pwwka::Receiver do
25
25
 
26
26
  after(:each) do
27
27
  Pwwka.configuration.logger = @original_logger
28
+ Pwwka.configuration.requeue_on_error = false
28
29
  @receiver.test_teardown rescue nil
29
30
  end
30
31
 
@@ -37,7 +38,12 @@ describe Pwwka::Receiver do
37
38
  end
38
39
 
39
40
  it "should nack the sent message if an error is raised" do
40
- expect(HandyHandler).to receive(:handle!).and_raise("blow up")
41
+ exception = begin
42
+ raise "blow up"
43
+ rescue => ex
44
+ ex
45
+ end
46
+ expect(HandyHandler).to receive(:handle!).and_raise(ex)
41
47
  expect(@receiver).not_to receive(:ack)
42
48
  expect(@receiver).to receive(:nack).with(instance_of(Fixnum))
43
49
  Pwwka::Transmitter.send_message!(payload, routing_key)
@@ -45,7 +51,48 @@ describe Pwwka::Receiver do
45
51
  expect(logger).to have_received(:info).with(/START Transmitting.*#{Regexp.escape(payload.to_s)}/)
46
52
  expect(logger).to have_received(:info).with(/END Transmitting.*#{Regexp.escape(payload.to_s)}/)
47
53
  expect(logger).to have_received(:info).with(/AFTER Transmitting.*#{Regexp.escape(payload.to_s)}/)
48
- expect(logger).to have_received(:error).with(/Error Processing Message.*#{Regexp.escape(payload.to_s)}/)
54
+ expect(logger).to have_received(:error).with(/Error Processing Message.*#{Regexp.escape(payload.to_s)}.*#{Regexp.escape(exception.backtrace.join(';'))}/)
55
+ end
56
+
57
+ context "when we're configured to requeue on error" do
58
+ before do
59
+ Pwwka.configuration.requeue_on_error = true
60
+ end
61
+ it "should nack_requeue the sent message if it hasn't been retried before" do
62
+ exception = begin
63
+ raise "blow up"
64
+ rescue => ex
65
+ ex
66
+ end
67
+ expect(HandyHandler).to receive(:handle!).and_raise(ex)
68
+ expect(@receiver).not_to receive(:ack)
69
+ expect(@receiver).to receive(:nack_requeue).with(instance_of(Fixnum))
70
+ Pwwka::Transmitter.send_message!(payload, routing_key)
71
+ @receiver.test_teardown # force the message to be processed and exception handled
72
+ expect(logger).to have_received(:info).with(/START Transmitting.*#{Regexp.escape(payload.to_s)}/)
73
+ expect(logger).to have_received(:info).with(/END Transmitting.*#{Regexp.escape(payload.to_s)}/)
74
+ expect(logger).to have_received(:info).with(/AFTER Transmitting.*#{Regexp.escape(payload.to_s)}/)
75
+ expect(logger).to have_received(:error).with(/Error Processing Message.*#{Regexp.escape(payload.to_s)}.*#{Regexp.escape(exception.backtrace.join(';'))}/)
76
+ end
77
+
78
+ it "should nack the sent message if it HAS been retried before" do
79
+ # Super cheesy, but I don't see another way to access this
80
+ allow_any_instance_of(Bunny::DeliveryInfo).to receive(:redelivered).and_return(true)
81
+ exception = begin
82
+ raise "blow up"
83
+ rescue => ex
84
+ ex
85
+ end
86
+ expect(HandyHandler).to receive(:handle!).and_raise(ex)
87
+ expect(@receiver).not_to receive(:ack)
88
+ expect(@receiver).to receive(:nack).with(instance_of(Fixnum))
89
+ Pwwka::Transmitter.send_message!(payload, routing_key)
90
+ @receiver.test_teardown # force the message to be processed and exception handled
91
+ expect(logger).to have_received(:info).with(/START Transmitting.*#{Regexp.escape(payload.to_s)}/)
92
+ expect(logger).to have_received(:info).with(/END Transmitting.*#{Regexp.escape(payload.to_s)}/)
93
+ expect(logger).to have_received(:info).with(/AFTER Transmitting.*#{Regexp.escape(payload.to_s)}/)
94
+ expect(logger).to have_received(:error).with(/Error Processing Message.*#{Regexp.escape(payload.to_s)}.*#{Regexp.escape(exception.backtrace.join(';'))}/)
95
+ end
49
96
  end
50
97
 
51
98
  end
data/spec/spec_helper.rb CHANGED
@@ -10,7 +10,7 @@ RSpec.configure do |config|
10
10
  c.syntax = :expect
11
11
  end
12
12
 
13
- config.before(:each) do
13
+ config.before(:all) do
14
14
  Pwwka.configure do |c|
15
15
  c.topic_exchange_name = "topics-test"
16
16
  c.logger = MonoLogger.new("/dev/null")
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: pwwka
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.6.0
4
+ version: 0.7.0.RC1
5
5
  platform: ruby
6
6
  authors:
7
7
  - Stitch Fix Engineering
@@ -15,7 +15,7 @@ authors:
15
15
  autorequire:
16
16
  bindir: bin
17
17
  cert_chain: []
18
- date: 2016-09-14 00:00:00.000000000 Z
18
+ date: 2016-12-15 00:00:00.000000000 Z
19
19
  dependencies:
20
20
  - !ruby/object:Gem::Dependency
21
21
  name: bunny
@@ -183,12 +183,12 @@ required_ruby_version: !ruby/object:Gem::Requirement
183
183
  version: '0'
184
184
  required_rubygems_version: !ruby/object:Gem::Requirement
185
185
  requirements:
186
- - - ">="
186
+ - - ">"
187
187
  - !ruby/object:Gem::Version
188
- version: '0'
188
+ version: 1.3.1
189
189
  requirements: []
190
190
  rubyforge_project:
191
- rubygems_version: 2.6.6
191
+ rubygems_version: 2.5.1
192
192
  signing_key:
193
193
  specification_version: 4
194
194
  summary: Send and receive messages via RabbitMQ