pwwka 0.6.0 → 0.7.0.RC1

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