pwwka 0.8.0 → 0.9.0.RC1

Sign up to get free protection for your applications and to get access to all the features.
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