pwwka 0.8.0 → 0.9.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.
Files changed (33) hide show
  1. checksums.yaml +4 -4
  2. data/.gitignore +1 -0
  3. data/.travis.yml +7 -3
  4. data/CONTRIBUTING.md +17 -2
  5. data/Gemfile.lock +29 -7
  6. data/README.md +144 -128
  7. data/docker-compose.yml +11 -0
  8. data/lib/pwwka/configuration.rb +6 -0
  9. data/lib/pwwka/receiver.rb +19 -2
  10. data/lib/pwwka/test_handler.rb +1 -0
  11. data/lib/pwwka/transmitter.rb +1 -0
  12. data/lib/pwwka/version.rb +1 -1
  13. data/pwwka.gemspec +3 -0
  14. data/spec/integration/interrupted_receivers_spec.rb +47 -0
  15. data/spec/integration/send_and_receive_spec.rb +98 -0
  16. data/spec/integration/support/integration_test_helpers.rb +5 -0
  17. data/spec/integration/support/integration_test_setup.rb +40 -0
  18. data/spec/integration/support/logging_receiver.rb +10 -0
  19. data/spec/integration/test_handler_spec.rb +78 -0
  20. data/spec/integration/unhandled_errors_in_receivers_spec.rb +115 -0
  21. data/spec/{handling_spec.rb → legacy/handling_spec.rb} +1 -1
  22. data/spec/{receiver_spec.rb → legacy/receiver_spec.rb} +2 -1
  23. data/spec/{send_message_async_job_spec.rb → legacy/send_message_async_job_spec.rb} +1 -1
  24. data/spec/{transmitter_spec.rb → legacy/transmitter_spec.rb} +1 -1
  25. data/spec/spec_helper.rb +32 -6
  26. data/spec/support/test_configuration.rb +10 -0
  27. data/spec/unit/channel_connector_spec.rb +40 -0
  28. data/spec/{logging_spec.rb → unit/logging_spec.rb} +0 -0
  29. data/spec/{message_queuer_spec.rb → unit/message_queuer_spec.rb} +4 -4
  30. data/spec/{queue_resque_job_handler_spec.rb → unit/queue_resque_job_handler_spec.rb} +0 -0
  31. data/spec/unit/test_handler_message_spec.rb +26 -0
  32. data/spec/unit/transmitter_spec.rb +229 -0
  33. metadata +83 -18
@@ -0,0 +1,11 @@
1
+ version: '2'
2
+ services:
3
+ rabbit:
4
+ image: rabbitmq:3.5.6-management
5
+ ports:
6
+ - "10001:5672"
7
+ - "10002:15672"
8
+ resque:
9
+ image: redis:2.8.12
10
+ ports:
11
+ - "10003:6379"
@@ -11,6 +11,7 @@ module Pwwka
11
11
  attr_accessor :options
12
12
  attr_accessor :send_message_resque_backoff_strategy
13
13
  attr_accessor :requeue_on_error
14
+ attr_writer :keep_alive_on_handler_klass_exceptions
14
15
 
15
16
  def initialize
16
17
  @rabbit_mq_host = nil
@@ -22,6 +23,11 @@ module Pwwka
22
23
  60, # quick interruption
23
24
  600, 600, 600] # longer-term outage?
24
25
  @requeue_on_error = false
26
+ @keep_alive_on_handler_klass_exceptions = false
27
+ end
28
+
29
+ def keep_alive_on_handler_klass_exceptions?
30
+ @keep_alive_on_handler_klass_exceptions
25
31
  end
26
32
 
27
33
  def payload_logging
@@ -29,13 +29,22 @@ 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
+ error_options = {
33
+ queue_name: queue_name,
34
+ payload: payload,
35
+ routing_key: delivery_info.routing_key,
36
+ exception: e
37
+ }
32
38
  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
39
+ log_error "Retrying an Error Processing Message", error_options
34
40
  receiver.nack_requeue(delivery_info.delivery_tag)
35
41
  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
42
+ log_error "Error Processing Message", error_options
37
43
  receiver.nack(delivery_info.delivery_tag)
38
44
  end
45
+ unless Pwwka.configuration.keep_alive_on_handler_klass_exceptions?
46
+ raise Interrupt,"Exiting due to exception #{e.inspect}"
47
+ end
39
48
  end
40
49
  end
41
50
  rescue Interrupt => _
@@ -77,5 +86,13 @@ module Pwwka
77
86
  channel_connector.connection_close
78
87
  end
79
88
 
89
+ private
90
+
91
+ def self.log_error(message,options)
92
+ options[:message] = message
93
+ options[:backtrace] = options.fetch(:exception).backtrace.join(';')
94
+ logf "%{message} on %{queue_name} -> %{payload}, %{routing_key}: %{exception}: %{backtrace}", options
95
+ end
96
+
80
97
  end
81
98
  end
@@ -8,6 +8,7 @@ module Pwwka
8
8
  # 2. Arrange for `test_teardown` to be called during teardown of your tests
9
9
  # 3. Use the method `pop_message` to examine the message on the queue
10
10
  class TestHandler
11
+ include Pwwka::Logging
11
12
 
12
13
  attr_reader :channel_connector
13
14
 
@@ -1,5 +1,6 @@
1
1
  begin # optional dependency
2
2
  require 'resque'
3
+ require 'resque-retry'
3
4
  rescue LoadError
4
5
  end
5
6
 
data/lib/pwwka/version.rb CHANGED
@@ -1,3 +1,3 @@
1
1
  module Pwwka
2
- VERSION = '0.8.0'
2
+ VERSION = '0.9.0.RC1'
3
3
  end
data/pwwka.gemspec CHANGED
@@ -26,4 +26,7 @@ Gem::Specification.new do |s|
26
26
  s.add_development_dependency("rake")
27
27
  s.add_development_dependency("rspec")
28
28
  s.add_development_dependency("resque")
29
+ s.add_development_dependency("resque-retry")
30
+ s.add_development_dependency("simplecov")
31
+ s.add_development_dependency("resqutils")
29
32
  end
@@ -0,0 +1,47 @@
1
+ require 'spec_helper.rb'
2
+ require_relative "support/integration_test_setup"
3
+ require_relative "support/logging_receiver"
4
+ require_relative "support/integration_test_helpers"
5
+
6
+ describe "receivers being interrupted", :integration do
7
+ include IntegrationTestHelpers
8
+
9
+ before do
10
+ @testing_setup = IntegrationTestSetup.new
11
+ setup_receivers
12
+ end
13
+
14
+ before :each do
15
+ WellBehavedReceiver.reset!
16
+ end
17
+
18
+ after do
19
+ @testing_setup.kill_threads_and_clear_queues
20
+ end
21
+
22
+ it "an error in one receiver doesn't prevent others from getting messages" do
23
+ Pwwka::Transmitter.send_message!({ sample: "payload", has: { deeply: true, nested: 4 }},
24
+ "pwwka.testing.foo")
25
+ allow_receivers_to_process_queues
26
+
27
+ expect(WellBehavedReceiver.messages_received.size).to eq(1)
28
+ expect(@testing_setup.threads[WellBehavedReceiver].alive?).to eq(true)
29
+ expect(@testing_setup.threads[InterruptingReceiver].alive?).to eq(false)
30
+ end
31
+
32
+ def setup_receivers
33
+ [
34
+ [InterruptingReceiver, "interrupting_receiver_pwwkatesting"],
35
+ [WellBehavedReceiver, "well_behaved_receiver_pwwkatesting"],
36
+ ].each do |(klass, queue_name)|
37
+ @testing_setup.make_queue_and_setup_receiver(klass,queue_name,"#")
38
+ end
39
+ end
40
+ class InterruptingReceiver
41
+ def self.handle!(delivery_info,properties,payload)
42
+ raise Interrupt,'simulated interrupt would realy be a signal'
43
+ end
44
+ end
45
+ class WellBehavedReceiver < LoggingReceiver
46
+ end
47
+ end
@@ -0,0 +1,98 @@
1
+ require 'spec_helper.rb'
2
+ require 'resqutils/spec/resque_helpers'
3
+
4
+ require_relative "support/integration_test_setup"
5
+ require_relative "support/logging_receiver"
6
+ require_relative "support/integration_test_helpers"
7
+
8
+ describe "sending and receiving messages", :integration do
9
+ include IntegrationTestHelpers
10
+ include Resqutils::Spec::ResqueHelpers
11
+
12
+ before do
13
+ @testing_setup = IntegrationTestSetup.new
14
+ [
15
+ [AllReceiver , "all_receiver_pwwkatesting" , "#"] ,
16
+ [FooReceiver , "foo_receiver_pwwkatesting" , "pwwka.testing.foo"] ,
17
+ [OtherFooReceiver , "other_foo_receiver_pwwkatesting" , "pwwka.testing.foo"] ,
18
+ ].each do |(klass, queue_name, routing_key)|
19
+ @testing_setup.make_queue_and_setup_receiver(klass,queue_name,routing_key)
20
+ end
21
+ end
22
+
23
+ before :each do
24
+ AllReceiver.reset!
25
+ FooReceiver.reset!
26
+ OtherFooReceiver.reset!
27
+ clear_queue(:delayed)
28
+ end
29
+
30
+ after do
31
+ @testing_setup.kill_threads_and_clear_queues
32
+ end
33
+
34
+ it "can send a message that gets routed to all receivers" do
35
+ Pwwka::Transmitter.send_message!({ sample: "payload", has: { deeply: true, nested: 4 }},
36
+ "pwwka.testing.foo")
37
+ allow_receivers_to_process_queues
38
+
39
+ expect(AllReceiver.messages_received.size).to eq(1)
40
+ expect(FooReceiver.messages_received.size).to eq(1)
41
+ expect(OtherFooReceiver.messages_received.size).to eq(1)
42
+ @testing_setup.queues.each do |queue|
43
+ expect(queue.message_count).to eq(0)
44
+ end
45
+ end
46
+
47
+ it "can send a message delayed" do
48
+ Pwwka::Transmitter.send_message!({ sample: "payload", has: { deeply: true, nested: 4 }},
49
+ "pwwka.testing.foo",
50
+ delayed: true,
51
+ delay_by: 5_000)
52
+ allow_receivers_to_process_queues(1_000)
53
+
54
+ expect(AllReceiver.messages_received.size).to eq(0)
55
+ expect(FooReceiver.messages_received.size).to eq(0)
56
+ expect(OtherFooReceiver.messages_received.size).to eq(0)
57
+
58
+ allow_receivers_to_process_queues(5_000)
59
+ expect(AllReceiver.messages_received.size).to eq(1)
60
+ expect(FooReceiver.messages_received.size).to eq(1)
61
+ expect(OtherFooReceiver.messages_received.size).to eq(1)
62
+ end
63
+
64
+ it "can send a message that is only delivered to some handlers based on routing key" do
65
+ Pwwka::Transmitter.send_message!({ sample: "payload", has: { deeply: true, nested: 4 }},
66
+ "pwwka.testing.bar")
67
+ allow_receivers_to_process_queues
68
+
69
+ expect(AllReceiver.messages_received.size).to eq(1)
70
+ expect(FooReceiver.messages_received.size).to eq(0)
71
+ expect(OtherFooReceiver.messages_received.size).to eq(0)
72
+ @testing_setup.queues.each do |queue|
73
+ expect(queue.message_count).to eq(0)
74
+ end
75
+ end
76
+
77
+ it "can queue a job to send a message from a Resque job" do
78
+ Pwwka::Transmitter.send_message_async({ sample: "payload", has: { deeply: true, nested: 4 }},
79
+ "pwwka.testing.bar")
80
+
81
+ allow_receivers_to_process_queues # not expecting anything to be processed
82
+
83
+ expect(AllReceiver.messages_received.size).to eq(0)
84
+
85
+ process_resque_job(Pwwka::SendMessageAsyncJob,:delayed)
86
+
87
+ allow_receivers_to_process_queues
88
+
89
+ expect(AllReceiver.messages_received.size).to eq(1)
90
+ end
91
+
92
+ class AllReceiver < LoggingReceiver
93
+ end
94
+ class FooReceiver < AllReceiver
95
+ end
96
+ class OtherFooReceiver < AllReceiver
97
+ end
98
+ end
@@ -0,0 +1,5 @@
1
+ module IntegrationTestHelpers
2
+ def allow_receivers_to_process_queues(ms_to_sleep = 1_000)
3
+ sleep (ms_to_sleep.to_f / 1_000.0)
4
+ end
5
+ end
@@ -0,0 +1,40 @@
1
+ class IntegrationTestSetup
2
+
3
+ def threads
4
+ @threads ||= {}
5
+ end
6
+
7
+ def queues
8
+ @queues ||= []
9
+ end
10
+
11
+ def make_queue_and_setup_receiver(klass,queue_name,routing_key)
12
+ queue = channel.queue(queue_name, durable: true, arguments: {})
13
+ queue.bind(topic_exchange, routing_key: routing_key)
14
+ queues << queue
15
+ threads[klass] = Thread.new do
16
+ Pwwka::Receiver.subscribe(klass, queue_name, routing_key: routing_key)
17
+ end
18
+ end
19
+
20
+ def kill_threads_and_clear_queues
21
+ threads.each do |_,thread|
22
+ Thread.kill(thread)
23
+ end
24
+ queues.each do |queue|
25
+ queue.purge
26
+ queue.delete
27
+ end
28
+ end
29
+
30
+ def channel_connector
31
+ @channel_connector ||= Pwwka::ChannelConnector.new
32
+ end
33
+ def channel
34
+ channel_connector.channel
35
+ end
36
+ def topic_exchange
37
+ channel_connector.topic_exchange
38
+ end
39
+
40
+ end
@@ -0,0 +1,10 @@
1
+ class LoggingReceiver
2
+ def self.reset!; @messages_received = []; end
3
+ def self.messages_received; @messages_received ||= []; end
4
+
5
+ reset!
6
+
7
+ def self.handle!(delivery_info,properties,payload)
8
+ messages_received << [ delivery_info,properties,payload ]
9
+ end
10
+ end
@@ -0,0 +1,78 @@
1
+ require 'spec_helper.rb'
2
+ require_relative "support/integration_test_setup"
3
+ require_relative "support/integration_test_helpers"
4
+
5
+ describe "test handler for integration tests", :integration do
6
+ include IntegrationTestHelpers
7
+
8
+ subject(:test_handler) { Pwwka::TestHandler.new }
9
+ before do
10
+ test_handler.purge_test_queue
11
+ test_handler.test_setup
12
+ @testing_setup = IntegrationTestSetup.new
13
+ @logger = Pwwka.configuration.logger
14
+ end
15
+
16
+ after do
17
+ test_handler.test_teardown
18
+ @testing_setup.kill_threads_and_clear_queues
19
+ Pwwka.configuration.logger = @logger
20
+ end
21
+
22
+ it "allows introspecting messages that were sent" do
23
+ first_payload = { sample: "payload", has: { deeply: true, nested: 4 }}
24
+ second_payload = { other: :payload }
25
+
26
+ Pwwka::Transmitter.send_message!(first_payload, "pwwka.testing.foo")
27
+ Pwwka::Transmitter.send_message!(second_payload, "pwwka.testing.bar")
28
+
29
+ first_message = test_handler.pop_message
30
+ expect(first_message.delivery_info).not_to be_nil
31
+ expect(first_message.properties).not_to be_nil
32
+ expect(first_message.payload).to eq(JSON.parse(first_payload.to_json))
33
+
34
+ second_message = test_handler.pop_message
35
+ expect(second_message.delivery_info).not_to be_nil
36
+ expect(second_message.properties).not_to be_nil
37
+ expect(second_message.payload).to eq(JSON.parse(second_payload.to_json))
38
+
39
+ end
40
+
41
+ it "get_topic_message_payload_for_tests" do
42
+ first_payload = { sample: "payload", has: { deeply: true, nested: 4 }}
43
+
44
+ stringio = StringIO.new
45
+ Pwwka.configuration.logger = Logger.new(stringio)
46
+ Pwwka::Transmitter.send_message!(first_payload, "pwwka.testing.foo")
47
+
48
+ payload = test_handler.get_topic_message_payload_for_tests
49
+ expect(payload).to eq(JSON.parse(first_payload.to_json))
50
+ expect(stringio.string).to match(/get_topic_message_payload_for_tests is deprecated/)
51
+ end
52
+
53
+ it "get_topic_message_properties_for_tests" do
54
+ first_payload = { sample: "payload", has: { deeply: true, nested: 4 }}
55
+
56
+ stringio = StringIO.new
57
+ Pwwka.configuration.logger = Logger.new(stringio)
58
+ Pwwka::Transmitter.send_message!(first_payload, "pwwka.testing.foo")
59
+
60
+ properties = test_handler.get_topic_message_properties_for_tests
61
+ expect(properties).to_not be_nil
62
+ expect(stringio.string).to match(/get_topic_message_properties_for_tests is deprecated/)
63
+ end
64
+
65
+ it "get_topic_message_delivery_info_for_tests" do
66
+ first_payload = { sample: "payload", has: { deeply: true, nested: 4 }}
67
+
68
+ stringio = StringIO.new
69
+ Pwwka.configuration.logger = Logger.new(stringio)
70
+ Pwwka::Transmitter.send_message!(first_payload, "pwwka.testing.foo")
71
+
72
+ delivery_info = test_handler.get_topic_message_delivery_info_for_tests
73
+ expect(delivery_info).to_not be_nil
74
+ expect(stringio.string).to match(/get_topic_message_delivery_info_for_tests is deprecated/)
75
+ end
76
+
77
+
78
+ end
@@ -0,0 +1,115 @@
1
+ require 'spec_helper.rb'
2
+ require_relative "support/integration_test_setup"
3
+ require_relative "support/logging_receiver"
4
+ require_relative "support/integration_test_helpers"
5
+
6
+ describe "receivers with unhandled errors", :integration do
7
+ include IntegrationTestHelpers
8
+
9
+ before do
10
+ @testing_setup = IntegrationTestSetup.new
11
+ setup_receivers
12
+ Pwwka.configure do |c|
13
+ c.requeue_on_error = false
14
+ c.keep_alive_on_handler_klass_exceptions = false
15
+ end
16
+ end
17
+
18
+ before :each do
19
+ WellBehavedReceiver.reset!
20
+ ExceptionThrowingReceiver.reset!
21
+ IntermittentErrorReceiver.reset!
22
+ end
23
+
24
+ after do
25
+ @testing_setup.kill_threads_and_clear_queues
26
+ end
27
+
28
+ it "an error in one receiver doesn't prevent others from getting messages" do
29
+ Pwwka::Transmitter.send_message!({ sample: "payload", has: { deeply: true, nested: 4 }},
30
+ "pwwka.testing.foo")
31
+ allow_receivers_to_process_queues
32
+
33
+ expect(WellBehavedReceiver.messages_received.size).to eq(1)
34
+ expect(ExceptionThrowingReceiver.messages_received.size).to eq(1)
35
+ end
36
+
37
+ it "when configured to requeue failed messages, the message is requeued exactly once" do
38
+ Pwwka.configure do |c|
39
+ c.requeue_on_error = true
40
+ c.keep_alive_on_handler_klass_exceptions = true # only so we can check that the requeued message got sent; otherwise the receiver crashes and we can't test that
41
+ end
42
+ Pwwka::Transmitter.send_message!({ sample: "payload", has: { deeply: true, nested: 4 }},
43
+ "pwwka.testing.foo")
44
+ allow_receivers_to_process_queues
45
+
46
+ expect(WellBehavedReceiver.messages_received.size).to eq(1)
47
+ expect(ExceptionThrowingReceiver.messages_received.size).to eq(2)
48
+ expect(ExceptionThrowingReceiver.messages_received[1][0].redelivered).to eq(true)
49
+ expect(ExceptionThrowingReceiver.messages_received[1][2]).to eq(ExceptionThrowingReceiver.messages_received[0][2])
50
+ end
51
+
52
+ it "crashes the receiver that received an error" do
53
+ Pwwka::Transmitter.send_message!({ sample: "payload", has: { deeply: true, nested: 4 }},
54
+ "pwwka.testing.foo")
55
+ allow_receivers_to_process_queues
56
+
57
+ expect(@testing_setup.threads[ExceptionThrowingReceiver].alive?).to eq(false)
58
+ end
59
+
60
+ it "does not crash the receiver that received an error when we configure it not to" do
61
+ Pwwka.configure do |c|
62
+ c.keep_alive_on_handler_klass_exceptions = true
63
+ end
64
+ Pwwka::Transmitter.send_message!({ sample: "payload", has: { deeply: true, nested: 4 }},
65
+ "pwwka.testing.foo")
66
+ allow_receivers_to_process_queues
67
+
68
+ expect(@testing_setup.threads[ExceptionThrowingReceiver].alive?).to eq(true)
69
+ end
70
+
71
+ it "does not crash the receiver that successfully processed a message" do
72
+ Pwwka::Transmitter.send_message!({ sample: "payload", has: { deeply: true, nested: 4 }},
73
+ "pwwka.testing.foo")
74
+ allow_receivers_to_process_queues
75
+
76
+ expect(@testing_setup.threads[WellBehavedReceiver].alive?).to eq(true)
77
+ end
78
+
79
+ it "crashes the receiver if it gets a failure that we retry" do
80
+ Pwwka.configure do |c|
81
+ c.requeue_on_error = true
82
+ end
83
+ Pwwka::Transmitter.send_message!({ sample: "payload", has: { deeply: true, nested: 4 }},
84
+ "pwwka.testing.foo")
85
+ allow_receivers_to_process_queues
86
+
87
+ expect(@testing_setup.threads[IntermittentErrorReceiver].alive?).to eq(false)
88
+ end
89
+
90
+ def setup_receivers
91
+ [
92
+ [ExceptionThrowingReceiver, "exception_throwing_receiver_pwwkatesting"],
93
+ [WellBehavedReceiver, "well_behaved_receiver_pwwkatesting"],
94
+ [IntermittentErrorReceiver, "intermittent_error_receiver_pwwkatesting"],
95
+ ].each do |(klass, queue_name)|
96
+ @testing_setup.make_queue_and_setup_receiver(klass,queue_name,"#")
97
+ end
98
+ end
99
+ class ExceptionThrowingReceiver < LoggingReceiver
100
+ def self.handle!(delivery_info,properties,payload)
101
+ super(delivery_info,properties,payload)
102
+ raise "OH NOES!"
103
+ end
104
+ end
105
+ class IntermittentErrorReceiver < LoggingReceiver
106
+ def self.handle!(delivery_info,properties,payload)
107
+ super(delivery_info,properties,payload)
108
+ unless delivery_info.redelivered
109
+ raise "OH NOES!"
110
+ end
111
+ end
112
+ end
113
+ class WellBehavedReceiver < LoggingReceiver
114
+ end
115
+ end