hutch 0.21.0 → 0.22.1

Sign up to get free protection for your applications and to get access to all the features.
@@ -1,4 +1,4 @@
1
1
  module Hutch
2
- VERSION = '0.21.0'.freeze
2
+ VERSION = '0.22.1'.freeze
3
3
  end
4
4
 
@@ -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
@@ -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
- # Set up signal handlers for graceful shutdown
25
- register_signal_handlers
27
+ Waiter.wait_until_signaled
26
28
 
27
- main_loop
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.info {
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
- def supported_shutdown_signals
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
@@ -0,0 +1,2 @@
1
+ YARD::Templates::Engine.register_template_path(File.dirname(__FILE__) + '/../../templates')
2
+ require File.join(File.dirname(__FILE__), 'handler') if RUBY19
@@ -2,7 +2,7 @@ require 'spec_helper'
2
2
  require 'hutch/broker'
3
3
 
4
4
  describe Hutch::Broker do
5
- let(:config) { deep_copy(Hutch::Config.user_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', rabbitmq: true do
57
- context 'with valid details' do
58
- before { broker.set_up_amqp_connection }
59
- after { broker.disconnect }
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
- describe '#connection', adapter: :bunny do
62
- subject { super().connection }
63
- it { is_expected.to be_a Hutch::Adapters::BunnyAdapter }
64
- end
62
+ broker.set_up_amqp_connection
63
+ end
64
+ end
65
65
 
66
- describe '#connection', adapter: :march_hare do
67
- subject { super().connection }
68
- it { is_expected.to be_a Hutch::Adapters::MarchHareAdapter }
69
- end
66
+ describe '#open_connection', rabbitmq: true do
67
+ describe 'return value' do
68
+ subject { broker.open_connection }
69
+ after { subject.close }
70
70
 
71
- describe '#channel', adapter: :bunny do
72
- subject { super().channel }
73
- it { is_expected.to be_a Bunny::Channel }
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
- describe '#channel', adapter: :march_hare do
77
- subject { super().channel }
78
- it { is_expected.to be_a MarchHare::Channel }
79
- end
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
- describe '#exchange', adapter: :bunny do
82
- subject { super().exchange }
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
- describe '#exchange', adapter: :march_hare do
87
- subject { super().exchange }
88
- it { is_expected.to be_a MarchHare::Exchange }
89
- end
83
+ expect(broker.connection).to be_nil
84
+
85
+ connection.close
90
86
  end
87
+ end
91
88
 
92
- context 'when given invalid details' do
93
- before { config[:mq_host] = 'notarealhost' }
94
- let(:set_up_amqp_connection) { ->{ broker.set_up_amqp_connection } }
89
+ describe '#open_connection!' do
90
+ it 'sets the #connection to #open_connection' do
91
+ connection = double('connection').as_null_object
95
92
 
96
- specify { expect(set_up_amqp_connection).to raise_error }
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
- to receive(:prefetch).with(prefetch_value)
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
- to receive(:prefetch=).with(prefetch_value)
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
- to receive(:confirm_select)
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
- to receive(:confirm_select)
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)
@@ -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