hutch 0.25.0-java → 2.0.0-java
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 +4 -4
- data/CHANGELOG.md +402 -5
- data/LICENSE +1 -0
- data/README.md +157 -47
- data/bin/ci/before_build.sh +20 -0
- data/bin/ci/before_build_docker.sh +15 -0
- data/bin/ci/install_on_debian.sh +46 -0
- data/lib/hutch/acknowledgements/nack_on_all_failures.rb +1 -1
- data/lib/hutch/adapters/bunny.rb +29 -1
- data/lib/hutch/adapters/march_hare.rb +24 -0
- data/lib/hutch/broker.rb +98 -35
- data/lib/hutch/cli.rb +16 -12
- data/lib/hutch/config.rb +42 -13
- data/lib/hutch/consumer.rb +63 -4
- data/lib/hutch/error_handlers/bugsnag.rb +30 -0
- data/lib/hutch/error_handlers/{opbeat.rb → rollbar.rb} +11 -13
- data/lib/hutch/error_handlers/sentry.rb +6 -11
- data/lib/hutch/error_handlers.rb +2 -1
- data/lib/hutch/publisher.rb +1 -1
- data/lib/hutch/serializers/json.rb +3 -3
- data/lib/hutch/tracers/datadog.rb +18 -0
- data/lib/hutch/tracers.rb +1 -1
- data/lib/hutch/version.rb +1 -2
- data/lib/hutch/worker.rb +46 -9
- data/lib/hutch.rb +8 -4
- data/lib/yard-settings/yard-settings.rb +2 -2
- data/spec/hutch/broker_spec.rb +192 -14
- data/spec/hutch/cli_spec.rb +12 -9
- data/spec/hutch/config_spec.rb +84 -12
- data/spec/hutch/consumer_spec.rb +93 -4
- data/spec/hutch/error_handlers/airbrake_spec.rb +4 -1
- data/spec/hutch/error_handlers/bugsnag_spec.rb +56 -0
- data/spec/hutch/error_handlers/honeybadger_spec.rb +1 -1
- data/spec/hutch/error_handlers/logger_spec.rb +1 -1
- data/spec/hutch/error_handlers/rollbar_spec.rb +45 -0
- data/spec/hutch/error_handlers/sentry_spec.rb +13 -3
- data/spec/hutch/message_spec.rb +1 -1
- data/spec/hutch/tracers/datadog_spec.rb +46 -0
- data/spec/hutch/waiter_spec.rb +2 -2
- data/spec/hutch/worker_spec.rb +138 -24
- data/spec/integration/channel_recovery_spec.rb +187 -0
- data/spec/integration/publish_consume_spec.rb +47 -0
- metadata +32 -59
- data/.gitignore +0 -10
- data/.rspec +0 -1
- data/.travis.yml +0 -16
- data/.yardopts +0 -5
- data/Gemfile +0 -33
- data/Guardfile +0 -14
- data/Rakefile +0 -21
- data/examples/consumer.rb +0 -13
- data/examples/producer.rb +0 -10
- data/hutch.gemspec +0 -29
- data/lib/hutch/tracers/opbeat.rb +0 -37
- data/spec/hutch/error_handlers/opbeat_spec.rb +0 -37
- data/spec/spec_helper.rb +0 -42
- data/spec/tracers/opbeat_spec.rb +0 -44
data/spec/hutch/consumer_spec.rb
CHANGED
|
@@ -28,6 +28,32 @@ describe Hutch::Consumer do
|
|
|
28
28
|
ComplexConsumer
|
|
29
29
|
end
|
|
30
30
|
|
|
31
|
+
let(:consumer_using_quorum_queue) do
|
|
32
|
+
unless defined? ConsumerUsingQuorumQueue
|
|
33
|
+
class ConsumerUsingQuorumQueue
|
|
34
|
+
include Hutch::Consumer
|
|
35
|
+
consume 'hutch.test1'
|
|
36
|
+
arguments foo: :bar
|
|
37
|
+
|
|
38
|
+
quorum_queue
|
|
39
|
+
end
|
|
40
|
+
end
|
|
41
|
+
ConsumerUsingQuorumQueue
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
let(:consumer_using_classic_queue) do
|
|
45
|
+
unless defined? ConsumerUsingLazyQueue
|
|
46
|
+
class ConsumerUsingLazyQueue
|
|
47
|
+
include Hutch::Consumer
|
|
48
|
+
consume 'hutch.test1'
|
|
49
|
+
arguments foo: :bar
|
|
50
|
+
lazy_queue
|
|
51
|
+
classic_queue
|
|
52
|
+
end
|
|
53
|
+
end
|
|
54
|
+
ConsumerUsingLazyQueue
|
|
55
|
+
end
|
|
56
|
+
|
|
31
57
|
describe 'module inclusion' do
|
|
32
58
|
it 'registers the class as a consumer' do
|
|
33
59
|
expect(Hutch).to receive(:register_consumer) do |klass|
|
|
@@ -71,6 +97,54 @@ describe Hutch::Consumer do
|
|
|
71
97
|
end
|
|
72
98
|
end
|
|
73
99
|
|
|
100
|
+
describe '.without_namespace' do
|
|
101
|
+
it 'is false by default' do
|
|
102
|
+
expect(simple_consumer.without_namespace?).to eq(false)
|
|
103
|
+
end
|
|
104
|
+
|
|
105
|
+
it 'returns true after calling without_namespace' do
|
|
106
|
+
simple_consumer.without_namespace
|
|
107
|
+
expect(simple_consumer.without_namespace?).to eq(true)
|
|
108
|
+
end
|
|
109
|
+
end
|
|
110
|
+
|
|
111
|
+
describe 'default queue mode' do
|
|
112
|
+
it 'does not specify any mode by default' do
|
|
113
|
+
expect(simple_consumer.queue_mode).to eq(nil)
|
|
114
|
+
expect(simple_consumer.queue_type).to eq(nil)
|
|
115
|
+
end
|
|
116
|
+
end
|
|
117
|
+
|
|
118
|
+
describe '.lazy_queue' do
|
|
119
|
+
context 'when queue mode has been set explicitly to lazy' do
|
|
120
|
+
it 'sets queue mode to lazy' do
|
|
121
|
+
expect(consumer_using_classic_queue.queue_mode).to eq('lazy')
|
|
122
|
+
end
|
|
123
|
+
end
|
|
124
|
+
end
|
|
125
|
+
|
|
126
|
+
describe '.classic_queue' do
|
|
127
|
+
context 'when queue type has been set explicitly to classic' do
|
|
128
|
+
it 'sets queue type to classic' do
|
|
129
|
+
expect(consumer_using_classic_queue.queue_type).to eq('classic')
|
|
130
|
+
end
|
|
131
|
+
end
|
|
132
|
+
end
|
|
133
|
+
|
|
134
|
+
describe '.quorum_queue' do
|
|
135
|
+
context 'when queue type has been set explicitly to quorum' do
|
|
136
|
+
it 'sets queue type to quorum' do
|
|
137
|
+
expect(consumer_using_quorum_queue.queue_type).to eq('quorum')
|
|
138
|
+
end
|
|
139
|
+
|
|
140
|
+
it 'accepts initial group size as an option' do
|
|
141
|
+
consumer = simple_consumer
|
|
142
|
+
expect { consumer.quorum_queue(initial_group_size: 3) }
|
|
143
|
+
.to change { consumer.initial_group_size }.to(3)
|
|
144
|
+
end
|
|
145
|
+
end
|
|
146
|
+
end
|
|
147
|
+
|
|
74
148
|
describe '.arguments' do
|
|
75
149
|
let(:args) { { foo: :bar} }
|
|
76
150
|
|
|
@@ -82,15 +156,30 @@ describe Hutch::Consumer do
|
|
|
82
156
|
end
|
|
83
157
|
|
|
84
158
|
describe '.get_arguments' do
|
|
85
|
-
|
|
86
159
|
context 'when defined' do
|
|
87
|
-
it { expect(complex_consumer.get_arguments).to
|
|
160
|
+
it { expect(complex_consumer.get_arguments).to include(foo: :bar) }
|
|
88
161
|
end
|
|
89
162
|
|
|
90
|
-
context 'when
|
|
91
|
-
it
|
|
163
|
+
context 'when queue is lazy' do
|
|
164
|
+
it 'has the x-queue-mode argument set to lazy' do
|
|
165
|
+
expect(consumer_using_classic_queue.get_arguments['x-queue-mode'])
|
|
166
|
+
.to eq('lazy')
|
|
167
|
+
end
|
|
92
168
|
end
|
|
93
169
|
|
|
170
|
+
context "when queue's type is quorum" do
|
|
171
|
+
let(:arguments) { consumer_using_quorum_queue.get_arguments }
|
|
172
|
+
it 'has the x-queue-type argument set to quorum' do
|
|
173
|
+
expect(arguments['x-queue-type']).to eq('quorum')
|
|
174
|
+
expect(arguments).to_not have_key('x-quorum-initial-group-size')
|
|
175
|
+
end
|
|
176
|
+
|
|
177
|
+
it 'has the x-quorum-initial-group-size argument set to quorum' do
|
|
178
|
+
consumer_using_quorum_queue.quorum_queue(initial_group_size: 5)
|
|
179
|
+
expect(arguments['x-queue-type']).to eq('quorum')
|
|
180
|
+
expect(arguments['x-quorum-initial-group-size']).to eq(5)
|
|
181
|
+
end
|
|
182
|
+
end
|
|
94
183
|
end
|
|
95
184
|
|
|
96
185
|
describe '.get_queue_name' do
|
|
@@ -1,5 +1,8 @@
|
|
|
1
1
|
require 'spec_helper'
|
|
2
2
|
|
|
3
|
+
# The airbrake gem is MRI-only, see the Gemfile
|
|
4
|
+
return if defined?(JRUBY_VERSION)
|
|
5
|
+
|
|
3
6
|
describe Hutch::ErrorHandlers::Airbrake do
|
|
4
7
|
let(:error_handler) { Hutch::ErrorHandlers::Airbrake.new }
|
|
5
8
|
|
|
@@ -14,7 +17,7 @@ describe Hutch::ErrorHandlers::Airbrake do
|
|
|
14
17
|
|
|
15
18
|
it "logs the error to Airbrake" do
|
|
16
19
|
message_id = "1"
|
|
17
|
-
properties =
|
|
20
|
+
properties = Struct.new(:message_id).new(message_id)
|
|
18
21
|
payload = "{}"
|
|
19
22
|
consumer = double
|
|
20
23
|
ex = error
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "spec_helper"
|
|
4
|
+
|
|
5
|
+
describe Hutch::ErrorHandlers::Bugsnag do
|
|
6
|
+
let(:error_handler) { described_class.new }
|
|
7
|
+
|
|
8
|
+
before do
|
|
9
|
+
Bugsnag.configure do |bugsnag|
|
|
10
|
+
bugsnag.api_key = "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"
|
|
11
|
+
bugsnag.logger = Logger.new(File::NULL) # suppress logging
|
|
12
|
+
end
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
describe "#handle" do
|
|
16
|
+
let(:error) do
|
|
17
|
+
begin
|
|
18
|
+
raise "Stuff went wrong"
|
|
19
|
+
rescue RuntimeError => err
|
|
20
|
+
err
|
|
21
|
+
end
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
it "logs the error to Bugsnag" do
|
|
25
|
+
message_id = "1"
|
|
26
|
+
properties = Struct.new(:message_id).new(message_id)
|
|
27
|
+
payload = "{}"
|
|
28
|
+
consumer = double
|
|
29
|
+
ex = error
|
|
30
|
+
message = {
|
|
31
|
+
payload: payload,
|
|
32
|
+
consumer: consumer
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
expect(::Bugsnag).to receive(:notify).with(ex).and_call_original
|
|
36
|
+
expect_any_instance_of(::Bugsnag::Report).to receive(:add_tab).with(:hutch, message)
|
|
37
|
+
error_handler.handle(properties, payload, consumer, ex)
|
|
38
|
+
end
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
describe "#handle_setup_exception" do
|
|
42
|
+
let(:error) do
|
|
43
|
+
begin
|
|
44
|
+
raise "Stuff went wrong"
|
|
45
|
+
rescue RuntimeError => err
|
|
46
|
+
err
|
|
47
|
+
end
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
it "logs the error to Bugsnag" do
|
|
51
|
+
ex = error
|
|
52
|
+
expect(::Bugsnag).to receive(:notify).with(ex)
|
|
53
|
+
error_handler.handle_setup_exception(ex)
|
|
54
|
+
end
|
|
55
|
+
end
|
|
56
|
+
end
|
|
@@ -14,7 +14,7 @@ describe Hutch::ErrorHandlers::Honeybadger do
|
|
|
14
14
|
|
|
15
15
|
it "logs the error to Honeybadger" do
|
|
16
16
|
message_id = "1"
|
|
17
|
-
properties =
|
|
17
|
+
properties = Struct.new(:message_id).new(message_id)
|
|
18
18
|
payload = "{}"
|
|
19
19
|
consumer = double
|
|
20
20
|
ex = error
|
|
@@ -4,7 +4,7 @@ describe Hutch::ErrorHandlers::Logger do
|
|
|
4
4
|
let(:error_handler) { Hutch::ErrorHandlers::Logger.new }
|
|
5
5
|
|
|
6
6
|
describe '#handle' do
|
|
7
|
-
let(:properties) {
|
|
7
|
+
let(:properties) { Struct.new(:message_id).new("1") }
|
|
8
8
|
let(:payload) { "{}" }
|
|
9
9
|
let(:error) { double(message: "Stuff went wrong", class: "RuntimeError",
|
|
10
10
|
backtrace: ["line 1", "line 2"]) }
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
require 'spec_helper'
|
|
2
|
+
|
|
3
|
+
describe Hutch::ErrorHandlers::Rollbar do
|
|
4
|
+
let(:error_handler) { Hutch::ErrorHandlers::Rollbar.new }
|
|
5
|
+
|
|
6
|
+
describe '#handle' do
|
|
7
|
+
let(:error) do
|
|
8
|
+
begin
|
|
9
|
+
raise "Stuff went wrong"
|
|
10
|
+
rescue RuntimeError => err
|
|
11
|
+
err
|
|
12
|
+
end
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
it "logs the error to Rollbar" do
|
|
16
|
+
message_id = "1"
|
|
17
|
+
properties = Struct.new(:message_id).new(message_id)
|
|
18
|
+
payload = "{}"
|
|
19
|
+
consumer = double
|
|
20
|
+
ex = error
|
|
21
|
+
message = {
|
|
22
|
+
payload: payload,
|
|
23
|
+
consumer: consumer
|
|
24
|
+
}
|
|
25
|
+
expect(::Rollbar).to receive(:error).with(ex, message)
|
|
26
|
+
error_handler.handle(properties, payload, consumer, ex)
|
|
27
|
+
end
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
describe '#handle_setup_exception' do
|
|
31
|
+
let(:error) do
|
|
32
|
+
begin
|
|
33
|
+
raise "Stuff went wrong"
|
|
34
|
+
rescue RuntimeError => err
|
|
35
|
+
err
|
|
36
|
+
end
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
it "logs the error to Rollbar" do
|
|
40
|
+
ex = error
|
|
41
|
+
expect(::Rollbar).to receive(:error).with(ex)
|
|
42
|
+
error_handler.handle_setup_exception(ex)
|
|
43
|
+
end
|
|
44
|
+
end
|
|
45
|
+
end
|
|
@@ -3,8 +3,16 @@ require 'spec_helper'
|
|
|
3
3
|
describe Hutch::ErrorHandlers::Sentry do
|
|
4
4
|
let(:error_handler) { Hutch::ErrorHandlers::Sentry.new }
|
|
5
5
|
|
|
6
|
+
before do
|
|
7
|
+
Sentry.init do
|
|
8
|
+
# initialize Sentry so that the integration acutally works
|
|
9
|
+
# otherwise, all its methods are going to return early
|
|
10
|
+
# so it will be impossible to check if it actually works
|
|
11
|
+
end
|
|
12
|
+
end
|
|
13
|
+
|
|
6
14
|
describe '#handle' do
|
|
7
|
-
let(:properties) {
|
|
15
|
+
let(:properties) { Struct.new(:message_id).new("1") }
|
|
8
16
|
let(:payload) { "{}" }
|
|
9
17
|
let(:error) do
|
|
10
18
|
begin
|
|
@@ -15,7 +23,8 @@ describe Hutch::ErrorHandlers::Sentry do
|
|
|
15
23
|
end
|
|
16
24
|
|
|
17
25
|
it "logs the error to Sentry" do
|
|
18
|
-
expect(
|
|
26
|
+
expect(::Sentry).to receive(:capture_exception).with(error).and_call_original
|
|
27
|
+
|
|
19
28
|
error_handler.handle(properties, payload, double, error)
|
|
20
29
|
end
|
|
21
30
|
end
|
|
@@ -30,7 +39,8 @@ describe Hutch::ErrorHandlers::Sentry do
|
|
|
30
39
|
end
|
|
31
40
|
|
|
32
41
|
it "logs the error to Sentry" do
|
|
33
|
-
expect(
|
|
42
|
+
expect(::Sentry).to receive(:capture_exception).with(error).and_call_original
|
|
43
|
+
|
|
34
44
|
error_handler.handle_setup_exception(error)
|
|
35
45
|
end
|
|
36
46
|
end
|
data/spec/hutch/message_spec.rb
CHANGED
|
@@ -4,7 +4,7 @@ describe Hutch::Message do
|
|
|
4
4
|
let(:delivery_info) { double('Delivery Info') }
|
|
5
5
|
let(:props) { double('Properties', content_type: "application/json") }
|
|
6
6
|
let(:body) {{ foo: 'bar' }.with_indifferent_access}
|
|
7
|
-
let(:json_body) {
|
|
7
|
+
let(:json_body) { JSON.generate(body) }
|
|
8
8
|
subject(:message) { Hutch::Message.new(delivery_info, props, json_body, Hutch::Config[:serializer]) }
|
|
9
9
|
|
|
10
10
|
describe '#body' do
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
require 'spec_helper'
|
|
2
|
+
|
|
3
|
+
RSpec.describe Hutch::Tracers::Datadog do
|
|
4
|
+
::Datadog.logger.level = Logger::FATAL # suppress logging
|
|
5
|
+
|
|
6
|
+
describe "#handle" do
|
|
7
|
+
subject(:handle) { tracer.handle(message) }
|
|
8
|
+
|
|
9
|
+
let(:tracer) { described_class.new(klass) }
|
|
10
|
+
let(:klass) do
|
|
11
|
+
Class.new do
|
|
12
|
+
attr_reader :message
|
|
13
|
+
|
|
14
|
+
def initialize
|
|
15
|
+
@message = nil
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
def class
|
|
19
|
+
Struct.new(:name).new('ClassName')
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
def process(message)
|
|
23
|
+
@message = message
|
|
24
|
+
end
|
|
25
|
+
end.new
|
|
26
|
+
end
|
|
27
|
+
let(:message) { double(:message) }
|
|
28
|
+
|
|
29
|
+
before do
|
|
30
|
+
allow(::Datadog::Tracing).to receive(:trace).and_call_original
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
it 'uses Datadog tracer' do
|
|
34
|
+
handle
|
|
35
|
+
|
|
36
|
+
expect(::Datadog::Tracing).to have_received(:trace).with('ClassName',
|
|
37
|
+
hash_including(service: 'hutch', type: 'rabbitmq'))
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
it 'processes the message' do
|
|
41
|
+
expect {
|
|
42
|
+
handle
|
|
43
|
+
}.to change { klass.message }.from(nil).to(message)
|
|
44
|
+
end
|
|
45
|
+
end
|
|
46
|
+
end
|
data/spec/hutch/waiter_spec.rb
CHANGED
|
@@ -22,7 +22,7 @@ RSpec.describe Hutch::Waiter do
|
|
|
22
22
|
end
|
|
23
23
|
end
|
|
24
24
|
|
|
25
|
-
context 'a TERM signal is received' do
|
|
25
|
+
context 'a TERM signal is received', if: !defined?(JRUBY_VERSION) do
|
|
26
26
|
it 'logs that hutch is stopping' do
|
|
27
27
|
expect(Hutch::Logging.logger).to receive(:info)
|
|
28
28
|
.with('caught SIGTERM, stopping hutch...')
|
|
@@ -32,7 +32,7 @@ RSpec.describe Hutch::Waiter do
|
|
|
32
32
|
end
|
|
33
33
|
end
|
|
34
34
|
|
|
35
|
-
context 'a INT signal is received' do
|
|
35
|
+
context 'a INT signal is received', if: !defined?(JRUBY_VERSION) do
|
|
36
36
|
it 'logs that hutch is stopping' do
|
|
37
37
|
expect(Hutch::Logging.logger).to receive(:info)
|
|
38
38
|
.with('caught SIGINT, stopping hutch...')
|
data/spec/hutch/worker_spec.rb
CHANGED
|
@@ -4,7 +4,8 @@ require 'hutch/worker'
|
|
|
4
4
|
describe Hutch::Worker do
|
|
5
5
|
let(:consumer) { double('Consumer', routing_keys: %w( a b c ),
|
|
6
6
|
get_queue_name: 'consumer', get_arguments: {},
|
|
7
|
-
get_serializer: nil
|
|
7
|
+
get_options: {}, get_serializer: nil,
|
|
8
|
+
without_namespace?: false) }
|
|
8
9
|
let(:consumers) { [consumer, double('Consumer')] }
|
|
9
10
|
let(:broker) { Hutch::Broker.new }
|
|
10
11
|
let(:setup_procs) { Array.new(2) { Proc.new {} } }
|
|
@@ -32,20 +33,34 @@ describe Hutch::Worker do
|
|
|
32
33
|
|
|
33
34
|
describe '#setup_queue' do
|
|
34
35
|
let(:queue) { double('Queue', bind: nil, subscribe: nil) }
|
|
35
|
-
before { allow(broker).to receive_messages(queue: queue, bind_queue: nil) }
|
|
36
|
+
before { allow(broker).to receive_messages(queue: queue, bind_queue: nil, namespaced_queue_name: 'consumer') }
|
|
36
37
|
|
|
37
|
-
it 'creates a queue' do
|
|
38
|
-
expect(broker).to receive(:
|
|
38
|
+
it 'creates a queue with a namespaced name' do
|
|
39
|
+
expect(broker).to receive(:namespaced_queue_name).with('consumer').and_return('ns:consumer')
|
|
40
|
+
expect(broker).to receive(:queue).with('ns:consumer', consumer.get_options).and_return(queue)
|
|
39
41
|
worker.setup_queue(consumer)
|
|
40
42
|
end
|
|
41
43
|
|
|
44
|
+
context 'when the consumer uses without_namespace' do
|
|
45
|
+
let(:consumer) { double('Consumer', routing_keys: %w( a b c ),
|
|
46
|
+
get_queue_name: 'consumer', get_arguments: {},
|
|
47
|
+
get_options: {}, get_serializer: nil,
|
|
48
|
+
without_namespace?: true) }
|
|
49
|
+
|
|
50
|
+
it 'creates a queue without applying the namespace' do
|
|
51
|
+
expect(broker).not_to receive(:namespaced_queue_name)
|
|
52
|
+
expect(broker).to receive(:queue).with('consumer', consumer.get_options).and_return(queue)
|
|
53
|
+
worker.setup_queue(consumer)
|
|
54
|
+
end
|
|
55
|
+
end
|
|
56
|
+
|
|
42
57
|
it 'binds the queue to each of the routing keys' do
|
|
43
58
|
expect(broker).to receive(:bind_queue).with(queue, %w( a b c ))
|
|
44
59
|
worker.setup_queue(consumer)
|
|
45
60
|
end
|
|
46
61
|
|
|
47
62
|
it 'sets up a subscription' do
|
|
48
|
-
expect(queue).to receive(:subscribe).with(consumer_tag: %r(^hutch\-.{36}$), manual_ack: true)
|
|
63
|
+
expect(queue).to receive(:subscribe).with(consumer_tag: %r(^hutch\-.{36}$), manual_ack: true, on_cancellation: kind_of(Proc))
|
|
49
64
|
worker.setup_queue(consumer)
|
|
50
65
|
end
|
|
51
66
|
|
|
@@ -53,7 +68,7 @@ describe Hutch::Worker do
|
|
|
53
68
|
before { Hutch::Config.set(:consumer_tag_prefix, 'appname') }
|
|
54
69
|
|
|
55
70
|
it 'sets up a subscription with the configured tag prefix' do
|
|
56
|
-
expect(queue).to receive(:subscribe).with(consumer_tag: %r(^appname\-.{36}$), manual_ack: true)
|
|
71
|
+
expect(queue).to receive(:subscribe).with(consumer_tag: %r(^appname\-.{36}$), manual_ack: true, on_cancellation: kind_of(Proc))
|
|
57
72
|
worker.setup_queue(consumer)
|
|
58
73
|
end
|
|
59
74
|
end
|
|
@@ -61,6 +76,7 @@ describe Hutch::Worker do
|
|
|
61
76
|
context 'with a configured consumer tag prefix that is too long' do
|
|
62
77
|
let(:maximum_size) { 255 - SecureRandom.uuid.size - 1 }
|
|
63
78
|
before { Hutch::Config.set(:consumer_tag_prefix, 'a'.*(maximum_size + 1)) }
|
|
79
|
+
after { Hutch::Config.set(:consumer_tag_prefix, 'hutch') }
|
|
64
80
|
|
|
65
81
|
it 'raises an error' do
|
|
66
82
|
expect { worker.setup_queue(consumer) }.to raise_error(/Tag must be 255 bytes long at most/)
|
|
@@ -68,28 +84,87 @@ describe Hutch::Worker do
|
|
|
68
84
|
end
|
|
69
85
|
end
|
|
70
86
|
|
|
87
|
+
describe '#handle_cancellation' do
|
|
88
|
+
let(:cancelled_channel) { double('Channel') }
|
|
89
|
+
let(:new_channel) { double('Channel') }
|
|
90
|
+
let(:log) { StringIO.new }
|
|
91
|
+
|
|
92
|
+
before do
|
|
93
|
+
allow(Hutch::Logging).to receive(:logger).and_return(Logger.new(log))
|
|
94
|
+
allow(broker).to receive(:channel).and_return(cancelled_channel)
|
|
95
|
+
allow(broker).to receive(:replace_channel!) do
|
|
96
|
+
allow(broker).to receive(:channel).and_return(new_channel)
|
|
97
|
+
end
|
|
98
|
+
allow(worker).to receive(:setup_queues)
|
|
99
|
+
end
|
|
100
|
+
|
|
101
|
+
context 'when the queue still exists' do
|
|
102
|
+
before { allow(broker).to receive(:queue_exists?).with('consumer').and_return(true) }
|
|
103
|
+
|
|
104
|
+
it 'replaces the channel and re-subscribes' do
|
|
105
|
+
expect(broker).to receive(:replace_channel!)
|
|
106
|
+
expect(worker).to receive(:setup_queues)
|
|
107
|
+
|
|
108
|
+
worker.handle_cancellation('consumer', cancelled_channel).join
|
|
109
|
+
end
|
|
110
|
+
|
|
111
|
+
it 'replaces the channel once for all the consumers that shared it' do
|
|
112
|
+
expect(broker).to receive(:replace_channel!).once
|
|
113
|
+
|
|
114
|
+
[
|
|
115
|
+
worker.handle_cancellation('consumer', cancelled_channel),
|
|
116
|
+
worker.handle_cancellation('consumer', cancelled_channel)
|
|
117
|
+
].each(&:join)
|
|
118
|
+
end
|
|
119
|
+
|
|
120
|
+
it 'does not replace a channel that has already been replaced' do
|
|
121
|
+
expect(broker).not_to receive(:replace_channel!)
|
|
122
|
+
|
|
123
|
+
worker.handle_cancellation('consumer', double('An older channel')).join
|
|
124
|
+
end
|
|
125
|
+
end
|
|
126
|
+
|
|
127
|
+
context 'when the queue is gone' do
|
|
128
|
+
before { allow(broker).to receive(:queue_exists?).with('consumer').and_return(false) }
|
|
129
|
+
|
|
130
|
+
it 'does not re-subscribe' do
|
|
131
|
+
expect(broker).not_to receive(:replace_channel!)
|
|
132
|
+
|
|
133
|
+
worker.handle_cancellation('consumer', cancelled_channel)
|
|
134
|
+
|
|
135
|
+
log.rewind
|
|
136
|
+
expect(log.read).to match(/the queue no longer exists/)
|
|
137
|
+
end
|
|
138
|
+
end
|
|
139
|
+
end
|
|
140
|
+
|
|
71
141
|
describe '#handle_message' do
|
|
142
|
+
subject { worker.handle_message(consumer, delivery_info, properties, payload) }
|
|
72
143
|
let(:payload) { '{}' }
|
|
73
144
|
let(:consumer_instance) { double('Consumer instance') }
|
|
74
|
-
let(:delivery_info) { double('Delivery Info', routing_key: '',
|
|
145
|
+
let(:delivery_info) { double('Delivery Info', routing_key: '', channel: nil,
|
|
75
146
|
delivery_tag: 'dt') }
|
|
76
147
|
let(:properties) { double('Properties', message_id: nil, content_type: "application/json") }
|
|
148
|
+
let(:log) { StringIO.new }
|
|
77
149
|
before { allow(consumer).to receive_messages(new: consumer_instance) }
|
|
78
150
|
before { allow(broker).to receive(:ack) }
|
|
79
151
|
before { allow(broker).to receive(:nack) }
|
|
80
152
|
before { allow(consumer_instance).to receive(:broker=) }
|
|
81
153
|
before { allow(consumer_instance).to receive(:delivery_info=) }
|
|
154
|
+
before { allow(Hutch::Logging).to receive(:logger).and_return(Logger.new(log)) }
|
|
82
155
|
|
|
83
156
|
it 'passes the message to the consumer' do
|
|
84
157
|
expect(consumer_instance).to receive(:process).
|
|
85
158
|
with(an_instance_of(Hutch::Message))
|
|
86
|
-
|
|
159
|
+
expect(consumer_instance).to receive(:message_rejected?).and_return(false)
|
|
160
|
+
subject
|
|
87
161
|
end
|
|
88
162
|
|
|
89
163
|
it 'acknowledges the message' do
|
|
90
164
|
allow(consumer_instance).to receive(:process)
|
|
91
|
-
expect(broker).to receive(:ack).with(delivery_info.delivery_tag)
|
|
92
|
-
|
|
165
|
+
expect(broker).to receive(:ack).with(delivery_info.delivery_tag, channel: delivery_info.channel)
|
|
166
|
+
expect(consumer_instance).to receive(:message_rejected?).and_return(false)
|
|
167
|
+
subject
|
|
93
168
|
end
|
|
94
169
|
|
|
95
170
|
context 'when the consumer fails and a requeue is configured' do
|
|
@@ -106,47 +181,86 @@ describe Hutch::Worker do
|
|
|
106
181
|
expect(broker).to_not receive(:nack)
|
|
107
182
|
expect(broker).to receive(:requeue)
|
|
108
183
|
|
|
109
|
-
|
|
184
|
+
subject
|
|
110
185
|
end
|
|
111
186
|
end
|
|
112
187
|
|
|
113
188
|
|
|
114
189
|
context 'when the consumer raises an exception' do
|
|
190
|
+
let(:expected_log) { /ERROR .+ error in consumer .+ RuntimeError .+ backtrace:/m }
|
|
115
191
|
before { allow(consumer_instance).to receive(:process).and_raise('a consumer error') }
|
|
116
192
|
|
|
117
193
|
it 'logs the error' do
|
|
118
|
-
|
|
119
|
-
expect(backend).to receive(:handle)
|
|
120
|
-
end
|
|
121
|
-
worker.handle_message(consumer, delivery_info, properties, payload)
|
|
194
|
+
expect { subject }.to change { log.tap(&:rewind).read }.from("").to(expected_log)
|
|
122
195
|
end
|
|
123
196
|
|
|
124
197
|
it 'rejects the message' do
|
|
125
|
-
expect(broker).to receive(:nack).with(delivery_info.delivery_tag)
|
|
126
|
-
|
|
198
|
+
expect(broker).to receive(:nack).with(delivery_info.delivery_tag, channel: delivery_info.channel)
|
|
199
|
+
subject
|
|
200
|
+
end
|
|
201
|
+
|
|
202
|
+
context 'when a custom error handler supports delivery info' do
|
|
203
|
+
let(:error_handler) do
|
|
204
|
+
Class.new(Hutch::ErrorHandlers::Base) do
|
|
205
|
+
def handle(_properties, _payload, _consumer, _ex, delivery_info)
|
|
206
|
+
raise unless delivery_info.delivery_tag == 'dt'
|
|
207
|
+
puts 'handled!'
|
|
208
|
+
end
|
|
209
|
+
end
|
|
210
|
+
end
|
|
211
|
+
|
|
212
|
+
around do |example|
|
|
213
|
+
original = Hutch::Config[:error_handlers]
|
|
214
|
+
Hutch::Config[:error_handlers] = [error_handler.new]
|
|
215
|
+
example.run
|
|
216
|
+
Hutch::Config[:error_handlers] = original
|
|
217
|
+
end
|
|
218
|
+
|
|
219
|
+
it 'calls the custom handler with delivery info' do
|
|
220
|
+
expect { subject }.to output("handled!\n").to_stdout
|
|
221
|
+
end
|
|
222
|
+
end
|
|
223
|
+
|
|
224
|
+
context 'when a custom error handler does not support delivery info' do
|
|
225
|
+
let(:error_handler) do
|
|
226
|
+
Class.new(Hutch::ErrorHandlers::Base) do
|
|
227
|
+
def handle(_properties, _payload, _consumer, _ex)
|
|
228
|
+
puts 'handled!'
|
|
229
|
+
end
|
|
230
|
+
end
|
|
231
|
+
end
|
|
232
|
+
|
|
233
|
+
around do |example|
|
|
234
|
+
original = Hutch::Config[:error_handlers]
|
|
235
|
+
Hutch::Config[:error_handlers] = [error_handler.new]
|
|
236
|
+
example.run
|
|
237
|
+
Hutch::Config[:error_handlers] = original
|
|
238
|
+
end
|
|
239
|
+
|
|
240
|
+
it 'calls the custom handler with delivery info' do
|
|
241
|
+
expect { subject }.to output("handled!\n").to_stdout
|
|
242
|
+
end
|
|
127
243
|
end
|
|
128
244
|
end
|
|
129
245
|
|
|
130
246
|
context "when the payload is not valid json" do
|
|
131
247
|
let(:payload) { "Not Valid JSON" }
|
|
248
|
+
let(:expected_log) { /ERROR .+ error in consumer .+ JSON::ParserError .+ backtrace:/m }
|
|
132
249
|
|
|
133
250
|
it 'logs the error' do
|
|
134
|
-
|
|
135
|
-
expect(backend).to receive(:handle)
|
|
136
|
-
end
|
|
137
|
-
worker.handle_message(consumer, delivery_info, properties, payload)
|
|
251
|
+
expect { subject }.to change { log.tap(&:rewind).read }.from("").to(expected_log)
|
|
138
252
|
end
|
|
139
253
|
|
|
140
254
|
it 'rejects the message' do
|
|
141
|
-
expect(broker).to receive(:nack).with(delivery_info.delivery_tag)
|
|
142
|
-
|
|
255
|
+
expect(broker).to receive(:nack).with(delivery_info.delivery_tag, channel: delivery_info.channel)
|
|
256
|
+
subject
|
|
143
257
|
end
|
|
144
258
|
end
|
|
145
259
|
end
|
|
146
260
|
|
|
147
261
|
|
|
148
262
|
describe '#acknowledge_error' do
|
|
149
|
-
let(:delivery_info) { double('Delivery Info', routing_key: '',
|
|
263
|
+
let(:delivery_info) { double('Delivery Info', routing_key: '', channel: nil,
|
|
150
264
|
delivery_tag: 'dt') }
|
|
151
265
|
let(:properties) { double('Properties', message_id: 'abc123') }
|
|
152
266
|
|