hutch 0.21.0 → 0.22.1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.gitignore +2 -0
- data/.yardopts +5 -0
- data/CHANGELOG.md +55 -1
- data/Gemfile +1 -1
- data/README.md +2 -2
- data/Rakefile +8 -1
- data/hutch.gemspec +4 -1
- data/lib/hutch.rb +11 -8
- data/lib/hutch/adapters/march_hare.rb +1 -1
- data/lib/hutch/broker.rb +89 -110
- data/lib/hutch/cli.rb +34 -10
- data/lib/hutch/config.rb +192 -55
- data/lib/hutch/error_handlers/sentry.rb +1 -1
- data/lib/hutch/logging.rb +1 -0
- data/lib/hutch/publisher.rb +75 -0
- data/lib/hutch/version.rb +1 -1
- data/lib/hutch/waiter.rb +41 -0
- data/lib/hutch/worker.rb +8 -60
- data/lib/yard-settings/handler.rb +38 -0
- data/lib/yard-settings/yard-settings.rb +2 -0
- data/spec/hutch/broker_spec.rb +107 -64
- data/spec/hutch/cli_spec.rb +3 -3
- data/spec/hutch/config_spec.rb +60 -22
- data/spec/hutch/error_handlers/sentry_spec.rb +1 -1
- data/spec/hutch/logger_spec.rb +12 -6
- data/spec/hutch/waiter_spec.rb +33 -0
- data/spec/hutch/worker_spec.rb +13 -2
- data/spec/spec_helper.rb +7 -5
- data/templates/default/class/html/settings.erb +0 -0
- data/templates/default/class/setup.rb +4 -0
- data/templates/default/fulldoc/html/css/hutch.css +13 -0
- data/templates/default/layout/html/setup.rb +7 -0
- data/templates/default/method_details/html/settings.erb +5 -0
- data/templates/default/method_details/setup.rb +4 -0
- data/templates/default/method_details/text/settings.erb +0 -0
- data/templates/default/module/html/settings.erb +40 -0
- data/templates/default/module/setup.rb +4 -0
- metadata +63 -5
data/lib/hutch/version.rb
CHANGED
data/lib/hutch/waiter.rb
ADDED
@@ -0,0 +1,41 @@
|
|
1
|
+
require 'hutch/logging'
|
2
|
+
|
3
|
+
module Hutch
|
4
|
+
class Waiter
|
5
|
+
include Logging
|
6
|
+
|
7
|
+
SHUTDOWN_SIGNALS = %w(QUIT TERM INT)
|
8
|
+
|
9
|
+
def self.wait_until_signaled
|
10
|
+
new.wait_until_signaled
|
11
|
+
end
|
12
|
+
|
13
|
+
def wait_until_signaled
|
14
|
+
self.sig_read, self.sig_write = IO.pipe
|
15
|
+
|
16
|
+
register_signal_handlers
|
17
|
+
wait_for_signal
|
18
|
+
|
19
|
+
sig = sig_read.gets.strip.downcase
|
20
|
+
logger.info "caught sig#{sig}, stopping hutch..."
|
21
|
+
end
|
22
|
+
|
23
|
+
private
|
24
|
+
|
25
|
+
attr_accessor :sig_read, :sig_write
|
26
|
+
|
27
|
+
def wait_for_signal
|
28
|
+
IO.select([sig_read])
|
29
|
+
end
|
30
|
+
|
31
|
+
def register_signal_handlers
|
32
|
+
SHUTDOWN_SIGNALS.each do |sig|
|
33
|
+
# This needs to be reentrant, so we queue up signals to be handled
|
34
|
+
# in the run loop, rather than acting on signals here
|
35
|
+
trap(sig) do
|
36
|
+
sig_write.puts(sig)
|
37
|
+
end
|
38
|
+
end
|
39
|
+
end
|
40
|
+
end
|
41
|
+
end
|
data/lib/hutch/worker.rb
CHANGED
@@ -2,6 +2,7 @@ require 'hutch/message'
|
|
2
2
|
require 'hutch/logging'
|
3
3
|
require 'hutch/broker'
|
4
4
|
require 'hutch/acknowledgements/nack_on_all_failures'
|
5
|
+
require 'hutch/waiter'
|
5
6
|
require 'carrot-top'
|
6
7
|
|
7
8
|
module Hutch
|
@@ -10,9 +11,10 @@ module Hutch
|
|
10
11
|
|
11
12
|
SHUTDOWN_SIGNALS = %w(QUIT TERM INT)
|
12
13
|
|
13
|
-
def initialize(broker, consumers)
|
14
|
+
def initialize(broker, consumers, setup_procs)
|
14
15
|
@broker = broker
|
15
16
|
self.consumers = consumers
|
17
|
+
self.setup_procs = setup_procs
|
16
18
|
end
|
17
19
|
|
18
20
|
# Run the main event loop. The consumers will be set up with queues, and
|
@@ -20,46 +22,11 @@ module Hutch
|
|
20
22
|
# never returns.
|
21
23
|
def run
|
22
24
|
setup_queues
|
25
|
+
setup_procs.each(&:call)
|
23
26
|
|
24
|
-
|
25
|
-
register_signal_handlers
|
27
|
+
Waiter.wait_until_signaled
|
26
28
|
|
27
|
-
|
28
|
-
end
|
29
|
-
|
30
|
-
def main_loop
|
31
|
-
if defined?(JRUBY_VERSION)
|
32
|
-
# Binds shutdown listener to notify main thread if channel was closed
|
33
|
-
bind_shutdown_handler
|
34
|
-
|
35
|
-
handle_signals until shutdown_not_called?(0.1)
|
36
|
-
else
|
37
|
-
# Take a break from Thread#join every 0.1 seconds to check if we've
|
38
|
-
# been sent any signals
|
39
|
-
handle_signals until @broker.wait_on_threads(0.1)
|
40
|
-
end
|
41
|
-
end
|
42
|
-
|
43
|
-
# Register handlers for SIG{QUIT,TERM,INT} to shut down the worker
|
44
|
-
# gracefully. Forceful shutdowns are very bad!
|
45
|
-
def register_signal_handlers
|
46
|
-
Thread.main[:signal_queue] = []
|
47
|
-
supported_shutdown_signals.each do |sig|
|
48
|
-
# This needs to be reentrant, so we queue up signals to be handled
|
49
|
-
# in the run loop, rather than acting on signals here
|
50
|
-
trap(sig) do
|
51
|
-
Thread.main[:signal_queue] << sig
|
52
|
-
end
|
53
|
-
end
|
54
|
-
end
|
55
|
-
|
56
|
-
# Handle any pending signals
|
57
|
-
def handle_signals
|
58
|
-
signal = Thread.main[:signal_queue].shift
|
59
|
-
if signal
|
60
|
-
logger.info "caught sig#{signal.downcase}, stopping hutch..."
|
61
|
-
stop
|
62
|
-
end
|
29
|
+
stop
|
63
30
|
end
|
64
31
|
|
65
32
|
# Stop a running worker by killing all subscriber threads.
|
@@ -67,23 +34,6 @@ module Hutch
|
|
67
34
|
@broker.stop
|
68
35
|
end
|
69
36
|
|
70
|
-
# Binds shutdown handler, called if channel is closed or network Failed
|
71
|
-
def bind_shutdown_handler
|
72
|
-
@broker.channel.on_shutdown do
|
73
|
-
Thread.main[:shutdown_received] = true
|
74
|
-
end
|
75
|
-
end
|
76
|
-
|
77
|
-
# Checks if shutdown handler was called, then sleeps for interval
|
78
|
-
def shutdown_not_called?(interval)
|
79
|
-
if Thread.main[:shutdown_received]
|
80
|
-
true
|
81
|
-
else
|
82
|
-
sleep(interval)
|
83
|
-
false
|
84
|
-
end
|
85
|
-
end
|
86
|
-
|
87
37
|
# Set up the queues for each of the worker's consumers.
|
88
38
|
def setup_queues
|
89
39
|
logger.info 'setting up queues'
|
@@ -106,7 +56,7 @@ module Hutch
|
|
106
56
|
# for wrapping up the message and passing it to the consumer.
|
107
57
|
def handle_message(consumer, delivery_info, properties, payload)
|
108
58
|
serializer = consumer.get_serializer || Hutch::Config[:serializer]
|
109
|
-
logger.
|
59
|
+
logger.debug {
|
110
60
|
spec = serializer.binary? ? "#{payload.bytesize} bytes" : "#{payload}"
|
111
61
|
"message(#{properties.message_id || '-'}): " +
|
112
62
|
"routing key: #{delivery_info.routing_key}, " +
|
@@ -154,8 +104,6 @@ module Hutch
|
|
154
104
|
|
155
105
|
private
|
156
106
|
|
157
|
-
|
158
|
-
SHUTDOWN_SIGNALS.keep_if { |s| Signal.list.keys.include? s }.map(&:to_sym)
|
159
|
-
end
|
107
|
+
attr_accessor :setup_procs
|
160
108
|
end
|
161
109
|
end
|
@@ -0,0 +1,38 @@
|
|
1
|
+
# :nodoc:
|
2
|
+
class SettingsHandlerBase < YARD::Handlers::Ruby::Base
|
3
|
+
handles method_call :string_setting
|
4
|
+
handles method_call :number_setting
|
5
|
+
handles method_call :boolean_setting
|
6
|
+
|
7
|
+
namespace_only
|
8
|
+
|
9
|
+
def process
|
10
|
+
name = statement.parameters.first.jump(:tstring_content, :ident).source
|
11
|
+
object = YARD::CodeObjects::MethodObject.new(namespace, name)
|
12
|
+
register(object)
|
13
|
+
|
14
|
+
# Modify the code object for the new instance method
|
15
|
+
object.dynamic = true
|
16
|
+
# Add custom metadata to the object
|
17
|
+
object['custom_field'] = '(Found using method_missing)'
|
18
|
+
|
19
|
+
# Module-level configuration notes
|
20
|
+
hutch_config = YARD::CodeObjects::ModuleObject.new(:root, "Hutch::Config")
|
21
|
+
collection_name = statement.first.first
|
22
|
+
default_value = statement.parameters[1].jump(:tstring_content, :ident).source
|
23
|
+
|
24
|
+
(hutch_config['setting_rows'] ||= []) << {
|
25
|
+
name: name,
|
26
|
+
default_value: default_value,
|
27
|
+
type: collection_name.sub('_setting', '').capitalize,
|
28
|
+
description: object.docstring,
|
29
|
+
first_line_of_description: first_line_of_description(object)
|
30
|
+
}
|
31
|
+
end
|
32
|
+
|
33
|
+
def first_line_of_description(object)
|
34
|
+
return '' if object.docstring.blank?
|
35
|
+
|
36
|
+
object.docstring.lines.first
|
37
|
+
end
|
38
|
+
end
|
data/spec/hutch/broker_spec.rb
CHANGED
@@ -2,7 +2,7 @@ require 'spec_helper'
|
|
2
2
|
require 'hutch/broker'
|
3
3
|
|
4
4
|
describe Hutch::Broker do
|
5
|
-
let(:config) {
|
5
|
+
let(:config) { Hutch::Config.initialize(client_logger: Hutch::Logging.logger) }
|
6
6
|
subject(:broker) { Hutch::Broker.new(config) }
|
7
7
|
|
8
8
|
describe '#connect' do
|
@@ -53,86 +53,142 @@ describe Hutch::Broker do
|
|
53
53
|
end
|
54
54
|
end
|
55
55
|
|
56
|
-
describe '#set_up_amqp_connection'
|
57
|
-
|
58
|
-
|
59
|
-
|
56
|
+
describe '#set_up_amqp_connection' do
|
57
|
+
it 'opens a connection, channel and declares an exchange' do
|
58
|
+
expect(broker).to receive(:open_connection!).ordered
|
59
|
+
expect(broker).to receive(:open_channel!).ordered
|
60
|
+
expect(broker).to receive(:declare_exchange!).ordered
|
60
61
|
|
61
|
-
|
62
|
-
|
63
|
-
|
64
|
-
end
|
62
|
+
broker.set_up_amqp_connection
|
63
|
+
end
|
64
|
+
end
|
65
65
|
|
66
|
-
|
67
|
-
|
68
|
-
|
69
|
-
|
66
|
+
describe '#open_connection', rabbitmq: true do
|
67
|
+
describe 'return value' do
|
68
|
+
subject { broker.open_connection }
|
69
|
+
after { subject.close }
|
70
70
|
|
71
|
-
|
72
|
-
|
73
|
-
|
74
|
-
end
|
71
|
+
it(nil, adapter: :bunny) { is_expected.to be_a Hutch::Adapters::BunnyAdapter }
|
72
|
+
it(nil, adapter: :march_hare) { is_expected.to be_a Hutch::Adapters::MarchHareAdapter }
|
73
|
+
end
|
75
74
|
|
76
|
-
|
77
|
-
|
78
|
-
|
79
|
-
|
75
|
+
context 'when given invalid details' do
|
76
|
+
before { config[:mq_host] = 'notarealhost' }
|
77
|
+
it { expect { broker.open_connection }.to raise_error(StandardError) }
|
78
|
+
end
|
80
79
|
|
81
|
-
|
82
|
-
|
83
|
-
it { is_expected.to be_a Bunny::Exchange }
|
84
|
-
end
|
80
|
+
it 'does not set #connection' do
|
81
|
+
connection = broker.open_connection
|
85
82
|
|
86
|
-
|
87
|
-
|
88
|
-
|
89
|
-
end
|
83
|
+
expect(broker.connection).to be_nil
|
84
|
+
|
85
|
+
connection.close
|
90
86
|
end
|
87
|
+
end
|
91
88
|
|
92
|
-
|
93
|
-
|
94
|
-
|
89
|
+
describe '#open_connection!' do
|
90
|
+
it 'sets the #connection to #open_connection' do
|
91
|
+
connection = double('connection').as_null_object
|
95
92
|
|
96
|
-
|
93
|
+
expect(broker).to receive(:open_connection).and_return(connection)
|
94
|
+
|
95
|
+
broker.open_connection!
|
96
|
+
|
97
|
+
expect(broker.connection).to eq(connection)
|
98
|
+
end
|
99
|
+
end
|
100
|
+
|
101
|
+
describe '#open_channel', rabbitmq: true do
|
102
|
+
before { broker.open_connection! }
|
103
|
+
after { broker.disconnect }
|
104
|
+
|
105
|
+
describe 'return value' do
|
106
|
+
subject { broker.open_channel }
|
107
|
+
|
108
|
+
it(nil, adapter: :bunny) { is_expected.to be_a Bunny::Channel }
|
109
|
+
it(nil, adapter: :march_hare) { is_expected.to be_a MarchHare::Channel }
|
110
|
+
end
|
111
|
+
|
112
|
+
it 'does not set #channel' do
|
113
|
+
broker.open_channel
|
114
|
+
expect(broker.channel).to be_nil
|
97
115
|
end
|
98
116
|
|
99
117
|
context 'with channel_prefetch set' do
|
100
118
|
let(:prefetch_value) { 1 }
|
101
119
|
before { config[:channel_prefetch] = prefetch_value }
|
102
|
-
after { broker.disconnect }
|
103
120
|
|
104
121
|
it "set's channel's prefetch", adapter: :bunny do
|
105
|
-
expect_any_instance_of(Bunny::Channel).
|
106
|
-
|
107
|
-
broker.set_up_amqp_connection
|
122
|
+
expect_any_instance_of(Bunny::Channel).to receive(:prefetch).with(prefetch_value)
|
123
|
+
broker.open_channel
|
108
124
|
end
|
109
125
|
|
110
126
|
it "set's channel's prefetch", adapter: :march_hare do
|
111
|
-
expect_any_instance_of(MarchHare::Channel).
|
112
|
-
|
113
|
-
broker.set_up_amqp_connection
|
127
|
+
expect_any_instance_of(MarchHare::Channel).to receive(:prefetch=).with(prefetch_value)
|
128
|
+
broker.open_channel
|
114
129
|
end
|
115
130
|
end
|
116
131
|
|
117
132
|
context 'with force_publisher_confirms set' do
|
118
133
|
let(:force_publisher_confirms_value) { true }
|
119
134
|
before { config[:force_publisher_confirms] = force_publisher_confirms_value }
|
120
|
-
after { broker.disconnect }
|
121
135
|
|
122
136
|
it 'waits for confirmation', adapter: :bunny do
|
123
|
-
expect_any_instance_of(Bunny::Channel).
|
124
|
-
|
125
|
-
broker.set_up_amqp_connection
|
137
|
+
expect_any_instance_of(Bunny::Channel).to receive(:confirm_select)
|
138
|
+
broker.open_channel
|
126
139
|
end
|
127
140
|
|
128
141
|
it 'waits for confirmation', adapter: :march_hare do
|
129
|
-
expect_any_instance_of(MarchHare::Channel).
|
130
|
-
|
131
|
-
broker.set_up_amqp_connection
|
142
|
+
expect_any_instance_of(MarchHare::Channel).to receive(:confirm_select)
|
143
|
+
broker.open_channel
|
132
144
|
end
|
133
145
|
end
|
134
146
|
end
|
135
147
|
|
148
|
+
describe '#open_channel!' do
|
149
|
+
it 'sets the #channel to #open_channel' do
|
150
|
+
channel = double('channel').as_null_object
|
151
|
+
|
152
|
+
expect(broker).to receive(:open_channel).and_return(channel)
|
153
|
+
|
154
|
+
broker.open_channel!
|
155
|
+
|
156
|
+
expect(broker.channel).to eq(channel)
|
157
|
+
end
|
158
|
+
end
|
159
|
+
|
160
|
+
describe '#declare_exchange' do
|
161
|
+
before do
|
162
|
+
broker.open_connection!
|
163
|
+
broker.open_channel!
|
164
|
+
end
|
165
|
+
after { broker.disconnect }
|
166
|
+
|
167
|
+
describe 'return value' do
|
168
|
+
subject { broker.declare_exchange }
|
169
|
+
|
170
|
+
it(nil, adapter: :bunny) { is_expected.to be_a Bunny::Exchange }
|
171
|
+
it(nil, adapter: :march_hare) { is_expected.to be_a MarchHare::Exchange }
|
172
|
+
end
|
173
|
+
|
174
|
+
it 'does not set #exchange' do
|
175
|
+
broker.declare_exchange
|
176
|
+
expect(broker.exchange).to be_nil
|
177
|
+
end
|
178
|
+
end
|
179
|
+
|
180
|
+
describe '#declare_exchange!' do
|
181
|
+
it 'sets the #exchange to #declare_exchange' do
|
182
|
+
exchange = double('exchange').as_null_object
|
183
|
+
|
184
|
+
expect(broker).to receive(:declare_exchange).and_return(exchange)
|
185
|
+
|
186
|
+
broker.declare_exchange!
|
187
|
+
|
188
|
+
expect(broker.exchange).to eq(exchange)
|
189
|
+
end
|
190
|
+
end
|
191
|
+
|
136
192
|
describe '#set_up_api_connection', rabbitmq: true do
|
137
193
|
context 'with valid details' do
|
138
194
|
before { broker.set_up_api_connection }
|
@@ -149,7 +205,7 @@ describe Hutch::Broker do
|
|
149
205
|
after { broker.disconnect }
|
150
206
|
let(:set_up_api_connection) { ->{ broker.set_up_api_connection } }
|
151
207
|
|
152
|
-
specify { expect(set_up_api_connection).to raise_error }
|
208
|
+
specify { expect(set_up_api_connection).to raise_error(StandardError) }
|
153
209
|
end
|
154
210
|
end
|
155
211
|
|
@@ -192,7 +248,7 @@ describe Hutch::Broker do
|
|
192
248
|
|
193
249
|
describe '#bind_queue' do
|
194
250
|
|
195
|
-
around { |example| broker.connect { example.run } }
|
251
|
+
around { |example| broker.connect(host: "127.0.0.1") { example.run } }
|
196
252
|
|
197
253
|
let(:routing_keys) { %w( a b c ) }
|
198
254
|
let(:queue) { double('Queue', bind: nil, unbind: nil, name: 'consumer') }
|
@@ -225,21 +281,6 @@ describe Hutch::Broker do
|
|
225
281
|
end
|
226
282
|
end
|
227
283
|
|
228
|
-
describe '#wait_on_threads' do
|
229
|
-
let(:thread) { double('Thread') }
|
230
|
-
before { allow(broker).to receive(:work_pool_threads).and_return(threads) }
|
231
|
-
|
232
|
-
context 'when all threads finish within the timeout' do
|
233
|
-
let(:threads) { [double(join: thread), double(join: thread)] }
|
234
|
-
specify { expect(broker.wait_on_threads(1)).to be_truthy }
|
235
|
-
end
|
236
|
-
|
237
|
-
context 'when timeout expires for one thread' do
|
238
|
-
let(:threads) { [double(join: thread), double(join: nil)] }
|
239
|
-
specify { expect(broker.wait_on_threads(1)).to be_falsey }
|
240
|
-
end
|
241
|
-
end
|
242
|
-
|
243
284
|
describe '#stop', adapter: :bunny do
|
244
285
|
let(:thread_1) { double('Thread') }
|
245
286
|
let(:thread_2) { double('Thread') }
|
@@ -363,6 +404,8 @@ describe Hutch::Broker do
|
|
363
404
|
end
|
364
405
|
|
365
406
|
context 'without a valid connection' do
|
407
|
+
before { broker.set_up_amqp_connection; broker.disconnect }
|
408
|
+
|
366
409
|
it 'raises an exception' do
|
367
410
|
expect { broker.publish('test.key', 'message') }.
|
368
411
|
to raise_exception(Hutch::PublishError)
|
data/spec/hutch/cli_spec.rb
CHANGED
@@ -13,7 +13,7 @@ describe Hutch::CLI do
|
|
13
13
|
it "bails" do
|
14
14
|
expect {
|
15
15
|
cli.parse_options(["--config=#{file}"])
|
16
|
-
}.to raise_error SystemExit
|
16
|
+
}.to raise_error SystemExit, "Config file '/path/to/nonexistant/file' not found"
|
17
17
|
end
|
18
18
|
end
|
19
19
|
|
@@ -37,7 +37,7 @@ describe Hutch::CLI do
|
|
37
37
|
it "bails" do
|
38
38
|
expect {
|
39
39
|
cli.parse_options(["--mq-tls-key=#{file}"])
|
40
|
-
}.to raise_error SystemExit
|
40
|
+
}.to raise_error SystemExit, "Private key file '/path/to/nonexistant/file' not found"
|
41
41
|
end
|
42
42
|
end
|
43
43
|
|
@@ -61,7 +61,7 @@ describe Hutch::CLI do
|
|
61
61
|
it "bails" do
|
62
62
|
expect {
|
63
63
|
cli.parse_options(["--mq-tls-cert=#{file}"])
|
64
|
-
}.to raise_error SystemExit
|
64
|
+
}.to raise_error SystemExit, "Certificate file '/path/to/nonexistant/file' not found"
|
65
65
|
end
|
66
66
|
end
|
67
67
|
|