freddy 1.4.1 → 1.4.2

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 (43) hide show
  1. checksums.yaml +4 -4
  2. data/.rubocop.yml +49 -0
  3. data/.travis.yml +1 -1
  4. data/Gemfile +4 -2
  5. data/Rakefile +6 -4
  6. data/freddy.gemspec +21 -21
  7. data/lib/freddy.rb +7 -8
  8. data/lib/freddy/adapters.rb +2 -0
  9. data/lib/freddy/adapters/bunny_adapter.rb +7 -7
  10. data/lib/freddy/adapters/march_hare_adapter.rb +6 -4
  11. data/lib/freddy/consumers.rb +2 -0
  12. data/lib/freddy/consumers/respond_to_consumer.rb +14 -17
  13. data/lib/freddy/consumers/response_consumer.rb +4 -2
  14. data/lib/freddy/consumers/tap_into_consumer.rb +11 -14
  15. data/lib/freddy/delivery.rb +2 -0
  16. data/lib/freddy/error_response.rb +2 -0
  17. data/lib/freddy/invalid_request_error.rb +2 -0
  18. data/lib/freddy/message_handler.rb +3 -1
  19. data/lib/freddy/message_handler_adapaters.rb +2 -0
  20. data/lib/freddy/payload.rb +5 -3
  21. data/lib/freddy/producers.rb +2 -0
  22. data/lib/freddy/producers/reply_producer.rb +6 -6
  23. data/lib/freddy/producers/send_and_forget_producer.rb +8 -8
  24. data/lib/freddy/producers/send_and_wait_response_producer.rb +39 -31
  25. data/lib/freddy/request_manager.rb +7 -4
  26. data/lib/freddy/responder_handler.rb +2 -0
  27. data/lib/freddy/sync_response_container.rb +4 -4
  28. data/lib/freddy/timeout_error.rb +2 -0
  29. data/lib/freddy/trace_carrier.rb +4 -2
  30. data/spec/freddy/consumers/respond_to_consumer_spec.rb +1 -1
  31. data/spec/freddy/error_response_spec.rb +9 -9
  32. data/spec/freddy/freddy_spec.rb +38 -38
  33. data/spec/freddy/message_handler_spec.rb +2 -2
  34. data/spec/freddy/payload_spec.rb +5 -5
  35. data/spec/freddy/responder_handler_spec.rb +1 -1
  36. data/spec/freddy/sync_response_container_spec.rb +8 -8
  37. data/spec/freddy/trace_carrier_spec.rb +5 -5
  38. data/spec/integration/concurrency_spec.rb +9 -9
  39. data/spec/integration/reply_spec.rb +4 -4
  40. data/spec/integration/tap_into_with_group_spec.rb +2 -2
  41. data/spec/integration/tracing_spec.rb +19 -19
  42. data/spec/spec_helper.rb +6 -6
  43. metadata +9 -8
@@ -1 +1,3 @@
1
+ # frozen_string_literal: true
2
+
1
3
  Dir[File.dirname(__FILE__) + '/producers/*.rb'].each(&method(:require))
@@ -1,7 +1,9 @@
1
+ # frozen_string_literal: true
2
+
1
3
  class Freddy
2
4
  module Producers
3
5
  class ReplyProducer
4
- CONTENT_TYPE = 'application/json'.freeze
6
+ CONTENT_TYPE = 'application/json'
5
7
 
6
8
  def initialize(channel, logger)
7
9
  @logger = logger
@@ -9,11 +11,9 @@ class Freddy
9
11
  end
10
12
 
11
13
  def produce(destination, payload, properties)
12
- OpenTracing.active_span.log_kv(
13
- event: 'Sending response',
14
- queue: destination,
15
- payload: payload
16
- )
14
+ if (span = OpenTracing.active_span)
15
+ span.set_tag('message_bus.destination', destination)
16
+ end
17
17
 
18
18
  properties = properties.merge(
19
19
  routing_key: destination,
@@ -1,7 +1,9 @@
1
+ # frozen_string_literal: true
2
+
1
3
  class Freddy
2
4
  module Producers
3
5
  class SendAndForgetProducer
4
- CONTENT_TYPE = 'application/json'.freeze
6
+ CONTENT_TYPE = 'application/json'
5
7
 
6
8
  def initialize(channel, logger)
7
9
  @logger = logger
@@ -11,13 +13,11 @@ class Freddy
11
13
 
12
14
  def produce(destination, payload, properties)
13
15
  span = OpenTracing.start_span("freddy:notify:#{destination}",
14
- tags: {
15
- 'message_bus.destination': destination,
16
- 'component': 'freddy',
17
- 'span.kind': 'producer' # Message Bus
18
- }
19
- )
20
- span.log_kv event: 'Sending message', payload: payload
16
+ tags: {
17
+ 'message_bus.destination' => destination,
18
+ 'component' => 'freddy',
19
+ 'span.kind' => 'producer' # Message Bus
20
+ })
21
21
 
22
22
  properties = properties.merge(
23
23
  routing_key: destination,
@@ -1,7 +1,9 @@
1
+ # frozen_string_literal: true
2
+
1
3
  class Freddy
2
4
  module Producers
3
5
  class SendAndWaitResponseProducer
4
- CONTENT_TYPE = 'application/json'.freeze
6
+ CONTENT_TYPE = 'application/json'
5
7
 
6
8
  def initialize(channel, logger)
7
9
  @logger = logger
@@ -16,36 +18,36 @@ class Freddy
16
18
  @request_manager.no_route(correlation_id)
17
19
  end
18
20
 
19
- @response_queue = @channel.queue("", exclusive: true)
21
+ @response_queue = @channel.queue('', exclusive: true)
20
22
 
21
23
  @response_consumer = Consumers::ResponseConsumer.new(@logger)
22
24
  @response_consumer.consume(@channel, @response_queue, &method(:handle_response))
23
25
  end
24
26
 
25
27
  def produce(destination, payload, timeout_in_seconds:, delete_on_timeout:, **properties)
26
- span = OpenTracing.start_span("freddy:request:#{destination}",
27
- tags: {
28
- 'component': 'freddy',
29
- 'span.kind': 'client', # RPC
30
- 'payload.type': payload[:type] || 'unknown'
31
- }
32
- )
33
-
34
28
  correlation_id = SecureRandom.uuid
35
29
 
30
+ span = OpenTracing.start_span("freddy:request:#{destination}",
31
+ tags: {
32
+ 'component' => 'freddy',
33
+ 'span.kind' => 'client', # RPC
34
+ 'payload.type' => payload[:type] || 'unknown',
35
+ 'message_bus.destination' => destination,
36
+ 'message_bus.response_queue' => @response_queue.name,
37
+ 'message_bus.correlation_id' => correlation_id,
38
+ 'freddy.timeout_in_seconds' => timeout_in_seconds
39
+ })
40
+
36
41
  container = SyncResponseContainer.new(
37
42
  on_timeout(correlation_id, destination, timeout_in_seconds, span)
38
43
  )
39
44
 
40
45
  @request_manager.store(correlation_id,
41
- callback: container,
42
- trace: span,
43
- destination: destination
44
- )
46
+ callback: container,
47
+ span: span,
48
+ destination: destination)
45
49
 
46
- if delete_on_timeout
47
- properties[:expiration] = (timeout_in_seconds * 1000).to_i
48
- end
50
+ properties[:expiration] = (timeout_in_seconds * 1000).to_i if delete_on_timeout
49
51
 
50
52
  properties = properties.merge(
51
53
  routing_key: destination, content_type: CONTENT_TYPE,
@@ -55,13 +57,6 @@ class Freddy
55
57
  OpenTracing.global_tracer.inject(span.context, OpenTracing::FORMAT_TEXT_MAP, TraceCarrier.new(properties))
56
58
  json_payload = Payload.dump(payload)
57
59
 
58
- span.log_kv(
59
- event: 'Publishing request',
60
- payload: payload,
61
- response_queue: @response_queue.name,
62
- correlation_id: correlation_id
63
- )
64
-
65
60
  # Connection adapters handle thread safety for #publish themselves. No
66
61
  # need to lock these.
67
62
  @topic_exchange.publish json_payload, properties.dup
@@ -73,11 +68,11 @@ class Freddy
73
68
  def handle_response(delivery)
74
69
  correlation_id = delivery.correlation_id
75
70
 
76
- if request = @request_manager.delete(correlation_id)
71
+ if (request = @request_manager.delete(correlation_id))
77
72
  process_response(request, delivery)
78
73
  else
79
74
  message = "Got rpc response for correlation_id #{correlation_id} "\
80
- "but there is no requester"
75
+ 'but there is no requester'
81
76
  @logger.warn message
82
77
  end
83
78
  end
@@ -86,17 +81,30 @@ class Freddy
86
81
  @logger.debug "Got response for request to #{request[:destination]} "\
87
82
  "with correlation_id #{delivery.correlation_id}"
88
83
  request[:callback].call(delivery.payload, delivery)
84
+ rescue InvalidRequestError => e
85
+ request[:span].set_tag('error', true)
86
+ request[:span].log_kv(
87
+ event: 'invalid request',
88
+ message: e.message,
89
+ 'error.object': e
90
+ )
91
+ raise e
89
92
  ensure
90
- request[:trace].finish
93
+ request[:span].finish
91
94
  end
92
95
 
93
- def on_timeout(correlation_id, destination, timeout_in_seconds, trace)
94
- Proc.new do
96
+ def on_timeout(correlation_id, destination, timeout_in_seconds, span)
97
+ proc do
95
98
  @logger.warn "Request timed out waiting response from #{destination}"\
96
- ", correlation id #{correlation_id}"
99
+ ", correlation id #{correlation_id}, timeout #{timeout_in_seconds}s"
97
100
 
98
101
  @request_manager.delete(correlation_id)
99
- trace.finish
102
+ span.set_tag('error', true)
103
+ span.log_kv(
104
+ event: 'timed out',
105
+ message: "Timed out waiting response from #{destination}"
106
+ )
107
+ span.finish
100
108
  end
101
109
  end
102
110
  end
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  class Freddy
2
4
  class RequestManager
3
5
  def initialize(logger)
@@ -6,10 +8,11 @@ class Freddy
6
8
  end
7
9
 
8
10
  def no_route(correlation_id)
9
- if request = @requests[correlation_id]
10
- delete(correlation_id)
11
- request[:callback].call({error: 'Specified queue does not exist'}, nil)
12
- end
11
+ request = @requests[correlation_id]
12
+ return unless request
13
+
14
+ delete(correlation_id)
15
+ request[:callback].call({ error: 'Specified queue does not exist' }, nil)
13
16
  end
14
17
 
15
18
  def store(correlation_id, opts)
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  class Freddy
2
4
  class ResponderHandler
3
5
  def initialize(consumer, consume_thread_pool)
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require 'timeout'
2
4
 
3
5
  class Freddy
@@ -9,9 +11,7 @@ class Freddy
9
11
  end
10
12
 
11
13
  def call(response, delivery)
12
- if response.nil?
13
- raise StandardError, 'unexpected nil value for response'
14
- end
14
+ raise StandardError, 'unexpected nil value for response' if response.nil?
15
15
 
16
16
  @response = response
17
17
  @delivery = delivery
@@ -28,7 +28,7 @@ class Freddy
28
28
  message: 'Timed out waiting for response'
29
29
  )
30
30
  elsif !@delivery || @delivery.type == 'error'
31
- raise InvalidRequestError.new(@response)
31
+ raise InvalidRequestError, @response
32
32
  else
33
33
  @response
34
34
  end
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require_relative 'error_response'
2
4
 
3
5
  class Freddy
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  class Freddy
2
4
  # Carrier for rabbitmq following OpenTracing API
3
5
  # See https://github.com/opentracing/opentracing-ruby/blob/master/lib/opentracing/carrier.rb
@@ -18,8 +20,8 @@ class Freddy
18
20
  def each(&block)
19
21
  Hash[
20
22
  (@properties.headers || {})
21
- .select {|key, _| key =~ /^x-trace/}
22
- .map {|key, value| [key.sub(/x-trace-/, ''), value]}
23
+ .select { |key, _| key =~ /^x-trace/ }
24
+ .map { |key, value| [key.sub(/x-trace-/, ''), value] }
23
25
  ].each(&block)
24
26
  end
25
27
  end
@@ -12,7 +12,7 @@ describe Freddy::Consumers::RespondToConsumer do
12
12
 
13
13
  let(:connection) { Freddy::Adapters.determine.connect(config) }
14
14
  let(:destination) { random_destination }
15
- let(:payload) { {pay: 'load'} }
15
+ let(:payload) { { pay: 'load' } }
16
16
  let(:msg_handler_adapter_factory) { double(for: msg_handler_adapter) }
17
17
  let(:msg_handler_adapter) { Freddy::MessageHandlerAdapters::NoOpHandler.new }
18
18
  let(:prefetch_buffer_size) { 2 }
@@ -4,55 +4,55 @@ describe Freddy::ErrorResponse do
4
4
  subject(:error) { described_class.new(input) }
5
5
 
6
6
  context 'with an error type' do
7
- let(:input) { {error: 'SomeError'} }
7
+ let(:input) { { error: 'SomeError' } }
8
8
 
9
9
  describe '#response' do
10
10
  subject { error.response }
11
11
 
12
- it { should eq(input) }
12
+ it { is_expected.to eq(input) }
13
13
  end
14
14
 
15
15
  describe '#message' do
16
16
  subject { error.message }
17
17
 
18
18
  it 'uses error type as a message' do
19
- should eq('SomeError')
19
+ is_expected.to eq('SomeError')
20
20
  end
21
21
  end
22
22
  end
23
23
 
24
24
  context 'with an error type and message' do
25
- let(:input) { {error: 'SomeError', message: 'extra info'} }
25
+ let(:input) { { error: 'SomeError', message: 'extra info' } }
26
26
 
27
27
  describe '#response' do
28
28
  subject { error.response }
29
29
 
30
- it { should eq(input) }
30
+ it { is_expected.to eq(input) }
31
31
  end
32
32
 
33
33
  describe '#message' do
34
34
  subject { error.message }
35
35
 
36
36
  it 'uses error type as a message' do
37
- should eq('SomeError: extra info')
37
+ is_expected.to eq('SomeError: extra info')
38
38
  end
39
39
  end
40
40
  end
41
41
 
42
42
  context 'without an error type' do
43
- let(:input) { {something: 'else'} }
43
+ let(:input) { { something: 'else' } }
44
44
 
45
45
  describe '#response' do
46
46
  subject { error.response }
47
47
 
48
- it { should eq(input) }
48
+ it { is_expected.to eq(input) }
49
49
  end
50
50
 
51
51
  describe '#message' do
52
52
  subject { error.message }
53
53
 
54
54
  it 'uses default error message as a message' do
55
- should eq('Use #response to get the error response')
55
+ is_expected.to eq('Use #response to get the error response')
56
56
  end
57
57
  end
58
58
  end
@@ -5,7 +5,7 @@ describe Freddy do
5
5
 
6
6
  let(:destination) { random_destination }
7
7
  let(:destination2) { random_destination }
8
- let(:payload) { {pay: 'load'} }
8
+ let(:payload) { { pay: 'load' } }
9
9
 
10
10
  after { freddy.close }
11
11
 
@@ -18,7 +18,7 @@ describe Freddy do
18
18
  it 'removes the message from the queue after the timeout' do
19
19
  # Assume that there already is a queue. Otherwise will get an early
20
20
  # return.
21
- consumer = freddy.respond_to(destination) { }
21
+ consumer = freddy.respond_to(destination) {}
22
22
  consumer.shutdown
23
23
 
24
24
  freddy.deliver(destination, {}, timeout: 0.1)
@@ -36,7 +36,7 @@ describe Freddy do
36
36
  it 'keeps the message in the queue' do
37
37
  # Assume that there already is a queue. Otherwise will get an early
38
38
  # return.
39
- consumer = freddy.respond_to(destination) { }
39
+ consumer = freddy.respond_to(destination) {}
40
40
  consumer.shutdown
41
41
 
42
42
  freddy.deliver(destination, {})
@@ -53,50 +53,50 @@ describe Freddy do
53
53
 
54
54
  context 'when making a synchronized request' do
55
55
  it 'returns response as soon as possible' do
56
- respond_to { |payload, msg_handler| msg_handler.success(res: 'yey') }
57
- response = freddy.deliver_with_response(destination, {a: 'b'})
56
+ respond_to { |_payload, msg_handler| msg_handler.success(res: 'yey') }
57
+ response = freddy.deliver_with_response(destination, a: 'b')
58
58
 
59
59
  expect(response).to eq(res: 'yey')
60
60
  end
61
61
 
62
62
  it 'raises an error if the message was errored' do
63
- respond_to { |payload, msg_handler| msg_handler.error(error: 'not today') }
63
+ respond_to { |_payload, msg_handler| msg_handler.error(error: 'not today') }
64
64
 
65
- expect {
65
+ expect do
66
66
  freddy.deliver_with_response(destination, payload)
67
- }.to raise_error(Freddy::InvalidRequestError) {|error|
67
+ end.to raise_error(Freddy::InvalidRequestError) { |error|
68
68
  expect(error.response).to eq(error: 'not today')
69
69
  }
70
70
  end
71
71
 
72
72
  it 'responds to the correct requester' do
73
- respond_to { |payload, msg_handler| msg_handler.success(res: 'yey') }
73
+ respond_to { |_payload, msg_handler| msg_handler.success(res: 'yey') }
74
74
 
75
75
  response = freddy.deliver_with_response(destination, payload)
76
76
  expect(response).to eq(res: 'yey')
77
77
 
78
- expect {
78
+ expect do
79
79
  freddy.deliver_with_response(destination2, payload)
80
- }.to raise_error(Freddy::InvalidRequestError)
80
+ end.to raise_error(Freddy::InvalidRequestError)
81
81
  end
82
82
 
83
83
  context 'when queue does not exist' do
84
84
  it 'gives a no route error' do
85
- expect {
86
- freddy.deliver_with_response(destination, {a: 'b'}, timeout: 1)
87
- }.to raise_error(Freddy::InvalidRequestError) {|error|
85
+ expect do
86
+ freddy.deliver_with_response(destination, { a: 'b' }, timeout: 1)
87
+ end.to raise_error(Freddy::InvalidRequestError) { |error|
88
88
  expect(error.response).to eq(error: 'Specified queue does not exist')
89
89
  }
90
90
  end
91
91
  end
92
92
 
93
- context 'on timeout' do
93
+ context 'when timeout' do
94
94
  it 'gives timeout error' do
95
- respond_to { |payload, msg_handler| sleep 0.2 }
95
+ respond_to { |_payload, _msg_handler| sleep 0.2 }
96
96
 
97
- expect {
98
- freddy.deliver_with_response(destination, {a: 'b'}, timeout: 0.1)
99
- }.to raise_error(Freddy::TimeoutError) {|error|
97
+ expect do
98
+ freddy.deliver_with_response(destination, { a: 'b' }, timeout: 0.1)
99
+ end.to raise_error(Freddy::TimeoutError) { |error|
100
100
  expect(error.response).to eq(error: 'RequestTimeout', message: 'Timed out waiting for response')
101
101
  }
102
102
  end
@@ -105,12 +105,12 @@ describe Freddy do
105
105
  it 'removes the message from the queue' do
106
106
  # Assume that there already is a queue. Otherwise will get an early
107
107
  # return.
108
- consumer = freddy.respond_to(destination) { }
108
+ consumer = freddy.respond_to(destination) {}
109
109
  consumer.shutdown
110
110
 
111
- expect {
111
+ expect do
112
112
  freddy.deliver_with_response(destination, {}, timeout: 0.1)
113
- }.to raise_error(Freddy::TimeoutError)
113
+ end.to raise_error(Freddy::TimeoutError)
114
114
  default_sleep # to ensure everything is properly cleaned
115
115
 
116
116
  processed_after_timeout = false
@@ -125,12 +125,12 @@ describe Freddy do
125
125
  it 'removes the message from the queue' do
126
126
  # Assume that there already is a queue. Otherwise will get an early
127
127
  # return.
128
- consumer = freddy.respond_to(destination) { }
128
+ consumer = freddy.respond_to(destination) {}
129
129
  consumer.shutdown
130
130
 
131
- expect {
131
+ expect do
132
132
  freddy.deliver_with_response(destination, {}, timeout: 0.1, delete_on_timeout: false)
133
- }.to raise_error(Freddy::TimeoutError)
133
+ end.to raise_error(Freddy::TimeoutError)
134
134
  default_sleep # to ensure everything is properly cleaned
135
135
 
136
136
  processed_after_timeout = false
@@ -149,7 +149,7 @@ describe Freddy do
149
149
  end
150
150
 
151
151
  it 'receives messages' do
152
- tap {|msg| @tapped_message = msg }
152
+ tap { |msg| @tapped_message = msg }
153
153
  deliver
154
154
 
155
155
  wait_for { @tapped_message }
@@ -157,13 +157,13 @@ describe Freddy do
157
157
  end
158
158
 
159
159
  it 'has the destination' do
160
- tap "somebody.*.love" do |message, destination|
160
+ tap 'somebody.*.love' do |_message, destination|
161
161
  @destination = destination
162
162
  end
163
- deliver "somebody.to.love"
163
+ deliver 'somebody.to.love'
164
164
 
165
165
  wait_for { @destination }
166
- expect(@destination).to eq("somebody.to.love")
166
+ expect(@destination).to eq('somebody.to.love')
167
167
  end
168
168
 
169
169
  it "doesn't consume the message" do
@@ -178,28 +178,28 @@ describe Freddy do
178
178
  expect(@message_received).to be(true)
179
179
  end
180
180
 
181
- it "allows * wildcard" do
182
- tap("somebody.*.love") { @tapped = true }
181
+ it 'allows * wildcard' do
182
+ tap('somebody.*.love') { @tapped = true }
183
183
 
184
- deliver "somebody.to.love"
184
+ deliver 'somebody.to.love'
185
185
 
186
186
  wait_for { @tapped }
187
187
  expect(@tapped).to be(true)
188
188
  end
189
189
 
190
- it "* matches only one word" do
191
- tap("somebody.*.love") { @tapped = true }
190
+ it '* matches only one word' do
191
+ tap('somebody.*.love') { @tapped = true }
192
192
 
193
- deliver "somebody.not.to.love"
193
+ deliver 'somebody.not.to.love'
194
194
 
195
195
  default_sleep
196
196
  expect(@tapped).to be_falsy
197
197
  end
198
198
 
199
- it "allows # wildcard" do
200
- tap("i.#.free") { @tapped = true }
199
+ it 'allows # wildcard' do
200
+ tap('i.#.free') { @tapped = true }
201
201
 
202
- deliver "i.want.to.break.free"
202
+ deliver 'i.want.to.break.free'
203
203
 
204
204
  wait_for { @tapped }
205
205
  expect(@tapped).to be(true)