freddy-jruby 0.4.3 → 0.4.9
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/README.md +8 -9
- data/freddy.gemspec +3 -2
- data/lib/freddy.rb +82 -72
- data/lib/freddy/adapters.rb +13 -0
- data/lib/freddy/adapters/bunny_adapter.rb +67 -0
- data/lib/freddy/adapters/march_hare_adapter.rb +68 -0
- data/lib/freddy/consumer.rb +16 -60
- data/lib/freddy/consumers/respond_to_consumer.rb +47 -0
- data/lib/freddy/consumers/response_consumer.rb +34 -0
- data/lib/freddy/consumers/tap_into_consumer.rb +35 -0
- data/lib/freddy/delivery.rb +11 -2
- data/lib/freddy/error_response.rb +21 -0
- data/lib/freddy/invalid_request_error.rb +4 -0
- data/lib/freddy/message_handler.rb +5 -5
- data/lib/freddy/message_handlers.rb +5 -5
- data/lib/freddy/payload.rb +46 -0
- data/lib/freddy/producer.rb +1 -17
- data/lib/freddy/request.rb +14 -50
- data/lib/freddy/request_manager.rb +1 -1
- data/lib/freddy/responder_handler.rb +15 -12
- data/lib/freddy/sync_response_container.rb +13 -4
- data/lib/freddy/timeout_error.rb +4 -0
- data/lib/freddy/utils.rb +32 -0
- data/spec/freddy/consumer_spec.rb +1 -5
- data/spec/freddy/message_handler_spec.rb +1 -3
- data/spec/freddy/request_spec.rb +0 -4
- data/spec/freddy/responder_handler_spec.rb +42 -8
- data/spec/freddy/sync_response_container_spec.rb +13 -0
- data/spec/integration/concurrency_spec.rb +22 -0
- data/spec/integration/logging_spec.rb +0 -1
- metadata +15 -3
- data/lib/freddy/adaptive_queue.rb +0 -34
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 0e34103e16c546862a8431d62287eb20955795c4
|
4
|
+
data.tar.gz: 88d5d9f3ee6e6334c8ff3a2dbd4676bc0fe31528
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: d6b8a1de5e524b4bfd2481152255e37dfcd5df68c7fb1f8849f60ac5581aff7b6e8fa84f1b76db3dc5cbb3884533a79e27e7cd16e1a993654c509e7e949828e6
|
7
|
+
data.tar.gz: 915a84bb6418a6d8cd8609734e7138aaa230bb1dd44a3ec303668a87817e7d31ea02f0d14b61442b7541dc0fb863f2a63d51cd28c3e93fd24bf6f0f290c90947
|
data/README.md
CHANGED
@@ -1,6 +1,6 @@
|
|
1
1
|
# Messaging API supporting acknowledgements and request-response
|
2
2
|
|
3
|
-
[![Build Status](https://travis-ci.org/salemove/
|
3
|
+
[![Build Status](https://travis-ci.org/salemove/freddy.svg?branch=master)](https://travis-ci.org/salemove/freddy)
|
4
4
|
[![Code Climate](https://codeclimate.com/github/salemove/freddy/badges/gpa.svg)](https://codeclimate.com/github/salemove/freddy)
|
5
5
|
|
6
6
|
## Setup
|
@@ -12,6 +12,12 @@ logger = Logger.new(STDOUT)
|
|
12
12
|
freddy = Freddy.build(logger, host: 'localhost', port: 5672, user: 'guest', pass: 'guest')
|
13
13
|
```
|
14
14
|
|
15
|
+
## Supported message queues
|
16
|
+
|
17
|
+
These message queues have been tested and are working with Freddy. Other queues can be added easily:
|
18
|
+
|
19
|
+
* [RabbitMQ](https://www.rabbitmq.com/)
|
20
|
+
|
15
21
|
## Delivering messages
|
16
22
|
|
17
23
|
### Simple delivery
|
@@ -125,16 +131,9 @@ The following operations are supported:
|
|
125
131
|
|
126
132
|
* stop responding
|
127
133
|
```ruby
|
128
|
-
responder_handler.
|
134
|
+
responder_handler.shutdown
|
129
135
|
```
|
130
136
|
|
131
|
-
* delete the destination
|
132
|
-
```ruby
|
133
|
-
responder_handler.destroy_destination
|
134
|
-
```
|
135
|
-
|
136
|
-
* Primary use case is in tests to not leave dangling destinations. It deletes the destination even if there are responders for the same destination in other parts of the system. Use with caution in production code.
|
137
|
-
|
138
137
|
|
139
138
|
## Notes about concurrency
|
140
139
|
|
data/freddy.gemspec
CHANGED
@@ -8,7 +8,7 @@ Gem::Specification.new do |spec|
|
|
8
8
|
else
|
9
9
|
spec.name = "freddy"
|
10
10
|
end
|
11
|
-
spec.version = '0.4.
|
11
|
+
spec.version = '0.4.9'
|
12
12
|
spec.authors = ["Urmas Talimaa"]
|
13
13
|
spec.email = ["urmas.talimaa@gmail.com"]
|
14
14
|
spec.description = %q{Messaging API}
|
@@ -25,11 +25,12 @@ Gem::Specification.new do |spec|
|
|
25
25
|
|
26
26
|
if RUBY_PLATFORM == 'java'
|
27
27
|
spec.add_dependency 'march_hare', '~> 2.12.0'
|
28
|
+
spec.add_dependency 'symbolizer'
|
28
29
|
else
|
29
30
|
spec.add_dependency "bunny", "2.2.0"
|
31
|
+
spec.add_dependency "oj", "~> 2.13"
|
30
32
|
end
|
31
33
|
|
32
|
-
spec.add_dependency "symbolizer"
|
33
34
|
spec.add_dependency "hamster", "~> 1.0.1.pre.rc3"
|
34
35
|
spec.add_dependency "thread", "~> 0.2"
|
35
36
|
end
|
data/lib/freddy.rb
CHANGED
@@ -1,83 +1,36 @@
|
|
1
|
-
if RUBY_PLATFORM == 'java'
|
2
|
-
require 'march_hare'
|
3
|
-
else
|
4
|
-
require 'bunny'
|
5
|
-
end
|
6
|
-
|
7
1
|
require 'json'
|
8
|
-
require 'symbolizer'
|
9
2
|
require 'thread/pool'
|
10
3
|
|
11
|
-
require_relative 'freddy/
|
4
|
+
require_relative 'freddy/adapters'
|
12
5
|
require_relative 'freddy/consumer'
|
13
6
|
require_relative 'freddy/producer'
|
14
7
|
require_relative 'freddy/request'
|
8
|
+
require_relative 'freddy/payload'
|
9
|
+
require_relative 'freddy/error_response'
|
10
|
+
require_relative 'freddy/invalid_request_error'
|
11
|
+
require_relative 'freddy/timeout_error'
|
12
|
+
require_relative 'freddy/utils'
|
15
13
|
|
16
14
|
class Freddy
|
17
|
-
class ErrorResponse < StandardError
|
18
|
-
DEFAULT_ERROR_MESSAGE = 'Use #response to get the error response'
|
19
|
-
|
20
|
-
attr_reader :response
|
21
|
-
|
22
|
-
def initialize(response)
|
23
|
-
@response = response
|
24
|
-
super(format_message(response) || DEFAULT_ERROR_MESSAGE)
|
25
|
-
end
|
26
|
-
|
27
|
-
private
|
28
|
-
|
29
|
-
def format_message(response)
|
30
|
-
return unless response.is_a?(Hash)
|
31
|
-
|
32
|
-
message = [response[:error], response[:message]].compact.join(': ')
|
33
|
-
message.empty? ? nil : message
|
34
|
-
end
|
35
|
-
end
|
36
|
-
|
37
|
-
class InvalidRequestError < ErrorResponse
|
38
|
-
end
|
39
|
-
|
40
|
-
class TimeoutError < ErrorResponse
|
41
|
-
end
|
42
|
-
|
43
15
|
FREDDY_TOPIC_EXCHANGE_NAME = 'freddy-topic'.freeze
|
44
16
|
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
-
|
54
|
-
|
55
|
-
|
56
|
-
|
57
|
-
|
58
|
-
|
59
|
-
|
60
|
-
|
61
|
-
|
62
|
-
parameters: parameters
|
63
|
-
})
|
64
|
-
end
|
65
|
-
end
|
66
|
-
|
67
|
-
def self.notify_exception(exception, parameters={})
|
68
|
-
if defined? Airbrake
|
69
|
-
Airbrake.notify_or_ignore(exception, cgi_data: ENV.to_hash, parameters: parameters)
|
70
|
-
end
|
71
|
-
end
|
72
|
-
|
73
|
-
def self.build(logger = Logger.new(STDOUT), config)
|
74
|
-
if RUBY_PLATFORM == 'java'
|
75
|
-
connection = MarchHare.connect(config)
|
76
|
-
else
|
77
|
-
connection = Bunny.new(config)
|
78
|
-
connection.start
|
79
|
-
connection
|
80
|
-
end
|
17
|
+
# Creates a new freddy instance
|
18
|
+
#
|
19
|
+
# @param [Logger] logger
|
20
|
+
# instance of a logger, defaults to the STDOUT logger
|
21
|
+
# @param [Hash] config
|
22
|
+
# rabbitmq connection information
|
23
|
+
# @option config [String] :host ('localhost')
|
24
|
+
# @option config [Integer] :port (5672)
|
25
|
+
# @option config [String] :user ('guest')
|
26
|
+
# @option config [String] :pass ('guest')
|
27
|
+
#
|
28
|
+
# @return [Freddy]
|
29
|
+
#
|
30
|
+
# @example
|
31
|
+
# Freddy.build(Logger.new(STDOUT), user: 'thumper', pass: 'howdy')
|
32
|
+
def self.build(logger = Logger.new(STDOUT), config = {})
|
33
|
+
connection = Adapters.determine.connect(config)
|
81
34
|
|
82
35
|
new(connection, logger, config.fetch(:max_concurrency, 4))
|
83
36
|
end
|
@@ -88,19 +41,41 @@ class Freddy
|
|
88
41
|
@connection = connection
|
89
42
|
@channel = connection.create_channel
|
90
43
|
@consume_thread_pool = Thread.pool(max_concurrency)
|
91
|
-
@consumer = Consumer.new channel, logger, @consume_thread_pool
|
92
44
|
@producer = Producer.new channel, logger
|
45
|
+
@consumer = Consumer.new logger, @consume_thread_pool, @producer, @connection
|
93
46
|
@request = Request.new channel, logger, @producer, @consumer
|
94
47
|
end
|
48
|
+
private :initialize
|
95
49
|
|
96
50
|
def respond_to(destination, &callback)
|
97
|
-
@
|
51
|
+
@consumer.respond_to destination, &callback
|
98
52
|
end
|
99
53
|
|
100
54
|
def tap_into(pattern, &callback)
|
101
55
|
@consumer.tap_into pattern, &callback
|
102
56
|
end
|
103
57
|
|
58
|
+
# Sends a message to given destination
|
59
|
+
#
|
60
|
+
# This is *send and forget* type of delivery. It sends a message to given
|
61
|
+
# destination and does not wait for response. This is useful when there are
|
62
|
+
# multiple consumers that are using #tap_into or you just do not care about
|
63
|
+
# the response.
|
64
|
+
#
|
65
|
+
# @param [String] destination
|
66
|
+
# the queue name
|
67
|
+
# @param [Hash] payload
|
68
|
+
# the payload that can be serialized to json
|
69
|
+
# @param [Hash] options
|
70
|
+
# the options for delivery
|
71
|
+
# @option options [Integer] :timeout (0)
|
72
|
+
# discards the message after given seconds if nobody consumes it. Message
|
73
|
+
# won't be discarded if timeout it set to 0 (default).
|
74
|
+
#
|
75
|
+
# @return [void]
|
76
|
+
#
|
77
|
+
# @example
|
78
|
+
# freddy.deliver 'Metrics', user_id: 5, metric: 'signed_in'
|
104
79
|
def deliver(destination, payload, options = {})
|
105
80
|
timeout = options.fetch(:timeout, 0)
|
106
81
|
opts = {}
|
@@ -109,6 +84,35 @@ class Freddy
|
|
109
84
|
@producer.produce destination, payload, opts
|
110
85
|
end
|
111
86
|
|
87
|
+
# Sends a message and waits for the response
|
88
|
+
#
|
89
|
+
# @param [String] destination
|
90
|
+
# the queue name
|
91
|
+
# @param [Hash] payload
|
92
|
+
# the payload that can be serialized to json
|
93
|
+
# @param [Hash] options
|
94
|
+
# the options for delivery
|
95
|
+
# @option options [Integer] :timeout (3)
|
96
|
+
# throws a time out exception after given seconds when there is no response
|
97
|
+
# @option options [Boolean] :delete_on_timeout (true)
|
98
|
+
# discards the message when timeout error is raised
|
99
|
+
#
|
100
|
+
# @raise [Freddy::TimeoutError]
|
101
|
+
# if nobody responded to the request
|
102
|
+
# @raise [Freddy::InvalidRequestError]
|
103
|
+
# if the responder responded with an error response
|
104
|
+
#
|
105
|
+
# @return [Hash] the response
|
106
|
+
#
|
107
|
+
# @example
|
108
|
+
# begin
|
109
|
+
# response = freddy.deliver_with_response 'Users', type: 'fetch_all'
|
110
|
+
# puts "Got response #{response}"
|
111
|
+
# rescue Freddy::TimeoutError
|
112
|
+
# puts "Service unavailable"
|
113
|
+
# rescue Freddy::InvalidRequestError => e
|
114
|
+
# puts "Got error response: #{e.response}"
|
115
|
+
# end
|
112
116
|
def deliver_with_response(destination, payload, options = {})
|
113
117
|
timeout = options.fetch(:timeout, 3)
|
114
118
|
delete_on_timeout = options.fetch(:delete_on_timeout, true)
|
@@ -118,6 +122,12 @@ class Freddy
|
|
118
122
|
}
|
119
123
|
end
|
120
124
|
|
125
|
+
# Closes the connection with message queue
|
126
|
+
#
|
127
|
+
# @return [void]
|
128
|
+
#
|
129
|
+
# @example
|
130
|
+
# freddy.close
|
121
131
|
def close
|
122
132
|
@connection.close
|
123
133
|
end
|
@@ -0,0 +1,67 @@
|
|
1
|
+
require 'bunny'
|
2
|
+
|
3
|
+
class Freddy
|
4
|
+
module Adapters
|
5
|
+
class BunnyAdapter
|
6
|
+
def self.connect(config)
|
7
|
+
bunny = Bunny.new(config)
|
8
|
+
bunny.start
|
9
|
+
new(bunny)
|
10
|
+
end
|
11
|
+
|
12
|
+
def initialize(bunny)
|
13
|
+
@bunny = bunny
|
14
|
+
end
|
15
|
+
|
16
|
+
def create_channel
|
17
|
+
Channel.new(@bunny.create_channel)
|
18
|
+
end
|
19
|
+
|
20
|
+
def close
|
21
|
+
@bunny.close
|
22
|
+
end
|
23
|
+
|
24
|
+
class Channel
|
25
|
+
extend Forwardable
|
26
|
+
|
27
|
+
def initialize(channel)
|
28
|
+
@channel = channel
|
29
|
+
end
|
30
|
+
|
31
|
+
def_delegators :@channel, :topic, :default_exchange, :consumers
|
32
|
+
|
33
|
+
def queue(*args)
|
34
|
+
Queue.new(@channel.queue(*args))
|
35
|
+
end
|
36
|
+
|
37
|
+
def on_return(&block)
|
38
|
+
default_exchange.on_return do |return_info, properties, content|
|
39
|
+
block.call(return_info[:reply_code], properties[:correlation_id])
|
40
|
+
end
|
41
|
+
end
|
42
|
+
end
|
43
|
+
|
44
|
+
class Queue
|
45
|
+
def initialize(queue)
|
46
|
+
@queue = queue
|
47
|
+
end
|
48
|
+
|
49
|
+
def subscribe(&block)
|
50
|
+
@queue.subscribe do |info, properties, payload|
|
51
|
+
parsed_payload = Payload.parse(payload)
|
52
|
+
block.call(Delivery.new(parsed_payload, properties, info.routing_key))
|
53
|
+
end
|
54
|
+
end
|
55
|
+
|
56
|
+
def bind(*args)
|
57
|
+
@queue.bind(*args)
|
58
|
+
self
|
59
|
+
end
|
60
|
+
|
61
|
+
def name
|
62
|
+
@queue.name
|
63
|
+
end
|
64
|
+
end
|
65
|
+
end
|
66
|
+
end
|
67
|
+
end
|
@@ -0,0 +1,68 @@
|
|
1
|
+
require 'march_hare'
|
2
|
+
|
3
|
+
class Freddy
|
4
|
+
module Adapters
|
5
|
+
class MarchHareAdapter
|
6
|
+
def self.connect(config)
|
7
|
+
hare = MarchHare.connect(config)
|
8
|
+
new(hare)
|
9
|
+
end
|
10
|
+
|
11
|
+
def initialize(hare)
|
12
|
+
@hare = hare
|
13
|
+
end
|
14
|
+
|
15
|
+
def create_channel
|
16
|
+
Channel.new(@hare.create_channel)
|
17
|
+
end
|
18
|
+
|
19
|
+
def close
|
20
|
+
@hare.close
|
21
|
+
end
|
22
|
+
|
23
|
+
class Channel
|
24
|
+
extend Forwardable
|
25
|
+
|
26
|
+
def initialize(channel)
|
27
|
+
@channel = channel
|
28
|
+
end
|
29
|
+
|
30
|
+
def_delegators :@channel, :topic, :default_exchange, :consumers
|
31
|
+
|
32
|
+
def queue(*args)
|
33
|
+
Queue.new(@channel.queue(*args))
|
34
|
+
end
|
35
|
+
|
36
|
+
def on_return(&block)
|
37
|
+
@channel.on_return do |reply_code, _, exchange_name, _, properties|
|
38
|
+
if exchange_name != Freddy::FREDDY_TOPIC_EXCHANGE_NAME
|
39
|
+
block.call(reply_code, properties.correlation_id)
|
40
|
+
end
|
41
|
+
end
|
42
|
+
end
|
43
|
+
end
|
44
|
+
|
45
|
+
class Queue
|
46
|
+
def initialize(queue)
|
47
|
+
@queue = queue
|
48
|
+
end
|
49
|
+
|
50
|
+
def subscribe(&block)
|
51
|
+
@queue.subscribe do |meta, payload|
|
52
|
+
parsed_payload = Payload.parse(payload)
|
53
|
+
block.call(Delivery.new(parsed_payload, meta, meta.routing_key))
|
54
|
+
end
|
55
|
+
end
|
56
|
+
|
57
|
+
def bind(*args)
|
58
|
+
@queue.bind(*args)
|
59
|
+
self
|
60
|
+
end
|
61
|
+
|
62
|
+
def name
|
63
|
+
@queue.name
|
64
|
+
end
|
65
|
+
end
|
66
|
+
end
|
67
|
+
end
|
68
|
+
end
|
data/lib/freddy/consumer.rb
CHANGED
@@ -1,77 +1,33 @@
|
|
1
1
|
require_relative 'responder_handler'
|
2
2
|
require_relative 'message_handler'
|
3
|
-
require_relative 'request'
|
4
3
|
require_relative 'delivery'
|
4
|
+
require_relative 'consumers/tap_into_consumer'
|
5
|
+
require_relative 'consumers/respond_to_consumer'
|
6
|
+
require_relative 'consumers/response_consumer'
|
5
7
|
|
6
8
|
class Freddy
|
7
9
|
class Consumer
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
@
|
14
|
-
@topic_exchange = @channel.topic Freddy::FREDDY_TOPIC_EXCHANGE_NAME
|
15
|
-
@consume_thread_pool = consume_thread_pool
|
16
|
-
@dedicated_thread_pool = Thread.pool(1) # used only internally
|
17
|
-
end
|
18
|
-
|
19
|
-
def consume(destination, options = {}, &block)
|
20
|
-
raise EmptyConsumer unless block
|
21
|
-
consume_from_queue create_queue(destination), options, &block
|
22
|
-
end
|
23
|
-
|
24
|
-
def consume_from_queue(queue, options = {}, &block)
|
25
|
-
consume_using_pool(queue, options, @consume_thread_pool, &block)
|
10
|
+
def initialize(logger, consume_thread_pool, producer, connection)
|
11
|
+
@logger = logger
|
12
|
+
@connection = connection
|
13
|
+
@tap_into_consumer = Consumers::TapIntoConsumer.new(consume_thread_pool)
|
14
|
+
@respond_to_consumer = Consumers::RespondToConsumer.new(consume_thread_pool, producer, @logger)
|
15
|
+
@response_consumer = Consumers::ResponseConsumer.new(@logger)
|
26
16
|
end
|
27
17
|
|
28
|
-
def
|
29
|
-
|
18
|
+
def response_consume(queue, &block)
|
19
|
+
@logger.debug "Consuming messages on #{queue.name}"
|
20
|
+
@response_consumer.consume(queue, &block)
|
30
21
|
end
|
31
22
|
|
32
23
|
def tap_into(pattern, &block)
|
33
|
-
queue = create_queue('', exclusive: true).bind(@topic_exchange, routing_key: pattern)
|
34
|
-
consumer = queue.subscribe do |payload, delivery|
|
35
|
-
@consume_thread_pool.process do
|
36
|
-
block.call parse_payload(payload), delivery.routing_key
|
37
|
-
end
|
38
|
-
end
|
39
24
|
@logger.debug "Tapping into messages that match #{pattern}"
|
40
|
-
|
41
|
-
end
|
42
|
-
|
43
|
-
private
|
44
|
-
|
45
|
-
def consume_using_pool(queue, options, pool, &block)
|
46
|
-
consumer = queue.subscribe do |payload, delivery|
|
47
|
-
pool.process do
|
48
|
-
parsed_payload = parse_payload(payload)
|
49
|
-
log_receive_event(queue.name, parsed_payload, delivery.correlation_id)
|
50
|
-
block.call parsed_payload, delivery
|
51
|
-
end
|
52
|
-
end
|
53
|
-
@logger.debug "Consuming messages on #{queue.name}"
|
54
|
-
ResponderHandler.new consumer, @channel
|
55
|
-
end
|
56
|
-
|
57
|
-
def parse_payload(payload)
|
58
|
-
if payload == 'null'
|
59
|
-
{}
|
60
|
-
else
|
61
|
-
Symbolizer.symbolize(JSON(payload))
|
62
|
-
end
|
63
|
-
end
|
64
|
-
|
65
|
-
def create_queue(destination, options={})
|
66
|
-
AdaptiveQueue.new(@channel.queue(destination, options))
|
25
|
+
@tap_into_consumer.consume(pattern, @connection.create_channel, &block)
|
67
26
|
end
|
68
27
|
|
69
|
-
def
|
70
|
-
|
71
|
-
|
72
|
-
else
|
73
|
-
@logger.debug "Received message on #{queue_name} with payload #{payload} with correlation_id #{correlation_id}"
|
74
|
-
end
|
28
|
+
def respond_to(destination, &block)
|
29
|
+
@logger.info "Listening for requests on #{destination}"
|
30
|
+
@respond_to_consumer.consume(destination, @connection.create_channel, &block)
|
75
31
|
end
|
76
32
|
end
|
77
33
|
end
|
@@ -0,0 +1,47 @@
|
|
1
|
+
class Freddy
|
2
|
+
module Consumers
|
3
|
+
class RespondToConsumer
|
4
|
+
def initialize(consume_thread_pool, producer, logger)
|
5
|
+
@consume_thread_pool = consume_thread_pool
|
6
|
+
@producer = producer
|
7
|
+
@logger = logger
|
8
|
+
end
|
9
|
+
|
10
|
+
def consume(destination, channel, &block)
|
11
|
+
consumer = consume_from_destination(destination, channel) do |delivery|
|
12
|
+
log_receive_event(destination, delivery)
|
13
|
+
|
14
|
+
handler_class = MessageHandlers.for_type(delivery.type)
|
15
|
+
handler = handler_class.new(@producer, destination, @logger)
|
16
|
+
|
17
|
+
msg_handler = MessageHandler.new(handler, delivery)
|
18
|
+
handler.handle_message delivery.payload, msg_handler, &block
|
19
|
+
end
|
20
|
+
|
21
|
+
ResponderHandler.new(consumer, @consume_thread_pool)
|
22
|
+
end
|
23
|
+
|
24
|
+
private
|
25
|
+
|
26
|
+
def consume_from_destination(destination, channel, &block)
|
27
|
+
channel.queue(destination).subscribe do |delivery|
|
28
|
+
process_message(delivery, &block)
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
32
|
+
def process_message(delivery, &block)
|
33
|
+
@consume_thread_pool.process do
|
34
|
+
block.call(delivery)
|
35
|
+
end
|
36
|
+
end
|
37
|
+
|
38
|
+
def log_receive_event(destination, delivery)
|
39
|
+
if defined?(Logasm) && @logger.is_a?(Logasm)
|
40
|
+
@logger.debug "Received message", queue: destination, payload: delivery.payload, correlation_id: delivery.correlation_id
|
41
|
+
else
|
42
|
+
@logger.debug "Received message on #{destination} with payload #{delivery.payload} with correlation_id #{delivery.correlation_id}"
|
43
|
+
end
|
44
|
+
end
|
45
|
+
end
|
46
|
+
end
|
47
|
+
end
|
@@ -0,0 +1,34 @@
|
|
1
|
+
class Freddy
|
2
|
+
module Consumers
|
3
|
+
class ResponseConsumer
|
4
|
+
def initialize(logger)
|
5
|
+
@logger = logger
|
6
|
+
@dedicated_thread_pool = Thread.pool(1)
|
7
|
+
end
|
8
|
+
|
9
|
+
def consume(queue, &block)
|
10
|
+
consumer = queue.subscribe do |delivery|
|
11
|
+
process_message(queue, delivery, &block)
|
12
|
+
end
|
13
|
+
ResponderHandler.new(consumer, @dedicated_thread_pool)
|
14
|
+
end
|
15
|
+
|
16
|
+
private
|
17
|
+
|
18
|
+
def process_message(queue, delivery, &block)
|
19
|
+
@dedicated_thread_pool.process do
|
20
|
+
log_receive_event(queue.name, delivery)
|
21
|
+
block.call(delivery)
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
def log_receive_event(queue_name, delivery)
|
26
|
+
if defined?(Logasm) && @logger.is_a?(Logasm)
|
27
|
+
@logger.debug "Received message", queue: queue_name, payload: delivery.payload, correlation_id: delivery.correlation_id
|
28
|
+
else
|
29
|
+
@logger.debug "Received message on #{queue_name} with payload #{delivery.payload} with correlation_id #{delivery.correlation_id}"
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
@@ -0,0 +1,35 @@
|
|
1
|
+
class Freddy
|
2
|
+
module Consumers
|
3
|
+
class TapIntoConsumer
|
4
|
+
def initialize(consume_thread_pool)
|
5
|
+
@consume_thread_pool = consume_thread_pool
|
6
|
+
end
|
7
|
+
|
8
|
+
def consume(pattern, channel, &block)
|
9
|
+
queue = create_queue(pattern, channel)
|
10
|
+
|
11
|
+
consumer = queue.subscribe do |delivery|
|
12
|
+
process_message(delivery, &block)
|
13
|
+
end
|
14
|
+
|
15
|
+
ResponderHandler.new(consumer, @consume_thread_pool)
|
16
|
+
end
|
17
|
+
|
18
|
+
private
|
19
|
+
|
20
|
+
def create_queue(pattern, channel)
|
21
|
+
topic_exchange = channel.topic(Freddy::FREDDY_TOPIC_EXCHANGE_NAME)
|
22
|
+
|
23
|
+
channel
|
24
|
+
.queue('', exclusive: true)
|
25
|
+
.bind(topic_exchange, routing_key: pattern)
|
26
|
+
end
|
27
|
+
|
28
|
+
def process_message(delivery, &block)
|
29
|
+
@consume_thread_pool.process do
|
30
|
+
block.call delivery.payload, delivery.routing_key
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
data/lib/freddy/delivery.rb
CHANGED
@@ -1,8 +1,9 @@
|
|
1
1
|
class Freddy
|
2
2
|
class Delivery
|
3
|
-
attr_reader :
|
3
|
+
attr_reader :routing_key, :payload
|
4
4
|
|
5
|
-
def initialize(metadata, routing_key)
|
5
|
+
def initialize(payload, metadata, routing_key)
|
6
|
+
@payload = payload
|
6
7
|
@metadata = metadata
|
7
8
|
@routing_key = routing_key
|
8
9
|
end
|
@@ -10,5 +11,13 @@ class Freddy
|
|
10
11
|
def correlation_id
|
11
12
|
@metadata.correlation_id
|
12
13
|
end
|
14
|
+
|
15
|
+
def type
|
16
|
+
@metadata.type
|
17
|
+
end
|
18
|
+
|
19
|
+
def reply_to
|
20
|
+
@metadata.reply_to
|
21
|
+
end
|
13
22
|
end
|
14
23
|
end
|
@@ -0,0 +1,21 @@
|
|
1
|
+
class Freddy
|
2
|
+
class ErrorResponse < StandardError
|
3
|
+
DEFAULT_ERROR_MESSAGE = 'Use #response to get the error response'
|
4
|
+
|
5
|
+
attr_reader :response
|
6
|
+
|
7
|
+
def initialize(response)
|
8
|
+
@response = response
|
9
|
+
super(format_message(response) || DEFAULT_ERROR_MESSAGE)
|
10
|
+
end
|
11
|
+
|
12
|
+
private
|
13
|
+
|
14
|
+
def format_message(response)
|
15
|
+
return unless response.is_a?(Hash)
|
16
|
+
|
17
|
+
message = [response[:error], response[:message]].compact.join(': ')
|
18
|
+
message.empty? ? nil : message
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
@@ -1,19 +1,19 @@
|
|
1
1
|
class Freddy
|
2
2
|
class MessageHandler
|
3
|
-
attr_reader :
|
3
|
+
attr_reader :correlation_id
|
4
4
|
|
5
5
|
def initialize(adapter, delivery)
|
6
6
|
@adapter = adapter
|
7
|
-
@
|
8
|
-
@correlation_id = @
|
7
|
+
@delivery = delivery
|
8
|
+
@correlation_id = @delivery.correlation_id
|
9
9
|
end
|
10
10
|
|
11
11
|
def success(response = nil)
|
12
|
-
@adapter.success(@
|
12
|
+
@adapter.success(@delivery.reply_to, response)
|
13
13
|
end
|
14
14
|
|
15
15
|
def error(error = {error: "Couldn't process message"})
|
16
|
-
@adapter.error(@
|
16
|
+
@adapter.error(@delivery.reply_to, error)
|
17
17
|
end
|
18
18
|
end
|
19
19
|
end
|
@@ -14,8 +14,8 @@ class Freddy
|
|
14
14
|
def handle_message(payload, msg_handler, &block)
|
15
15
|
block.call payload, msg_handler
|
16
16
|
rescue Exception => e
|
17
|
-
@logger.error "Exception occured while processing message from #{
|
18
|
-
|
17
|
+
@logger.error "Exception occured while processing message from #{Utils.format_exception(e)}"
|
18
|
+
Utils.notify_exception(e, destination: @destination)
|
19
19
|
end
|
20
20
|
|
21
21
|
def success(*)
|
@@ -39,13 +39,13 @@ class Freddy
|
|
39
39
|
|
40
40
|
if !@correlation_id
|
41
41
|
@logger.error "Received request without correlation_id"
|
42
|
-
|
42
|
+
Utils.notify_exception(e)
|
43
43
|
else
|
44
44
|
block.call payload, msg_handler
|
45
45
|
end
|
46
46
|
rescue Exception => e
|
47
|
-
@logger.error "Exception occured while handling the request with correlation_id #{@correlation_id}: #{
|
48
|
-
|
47
|
+
@logger.error "Exception occured while handling the request with correlation_id #{@correlation_id}: #{Utils.format_exception(e)}"
|
48
|
+
Utils.notify_exception(e, correlation_id: @correlation_id, destination: @destination)
|
49
49
|
end
|
50
50
|
|
51
51
|
def success(reply_to, response)
|
@@ -0,0 +1,46 @@
|
|
1
|
+
begin
|
2
|
+
require 'oj'
|
3
|
+
rescue LoadError
|
4
|
+
require 'symbolizer'
|
5
|
+
require 'json'
|
6
|
+
end
|
7
|
+
|
8
|
+
class Freddy
|
9
|
+
class Payload
|
10
|
+
def self.parse(payload)
|
11
|
+
return {} if payload == 'null'
|
12
|
+
|
13
|
+
json_handler.parse(payload)
|
14
|
+
end
|
15
|
+
|
16
|
+
def self.dump(payload)
|
17
|
+
json_handler.dump(payload)
|
18
|
+
end
|
19
|
+
|
20
|
+
def self.json_handler
|
21
|
+
@_json_handler ||= defined?(Oj) ? OjAdapter : JsonAdapter
|
22
|
+
end
|
23
|
+
|
24
|
+
class OjAdapter
|
25
|
+
def self.parse(payload)
|
26
|
+
Oj.strict_load(payload, symbol_keys: true)
|
27
|
+
end
|
28
|
+
|
29
|
+
def self.dump(payload)
|
30
|
+
Oj.dump(payload, mode: :compat)
|
31
|
+
end
|
32
|
+
end
|
33
|
+
|
34
|
+
class JsonAdapter
|
35
|
+
def self.parse(payload)
|
36
|
+
# MRI has :symbolize_keys, but JRuby does not. Not adding it at the
|
37
|
+
# moment.
|
38
|
+
Symbolizer.symbolize(JSON.parse(payload))
|
39
|
+
end
|
40
|
+
|
41
|
+
def self.dump(payload)
|
42
|
+
JSON.dump(payload)
|
43
|
+
end
|
44
|
+
end
|
45
|
+
end
|
46
|
+
end
|
data/lib/freddy/producer.rb
CHANGED
@@ -17,26 +17,10 @@ class Freddy
|
|
17
17
|
@logger.debug "Producing message #{payload.inspect} to #{destination}"
|
18
18
|
|
19
19
|
properties = properties.merge(routing_key: destination, content_type: CONTENT_TYPE)
|
20
|
-
json_payload = payload
|
20
|
+
json_payload = Payload.dump(payload)
|
21
21
|
|
22
22
|
@topic_exchange.publish json_payload, properties.dup
|
23
23
|
@exchange.publish json_payload, properties.dup
|
24
24
|
end
|
25
|
-
|
26
|
-
def on_return(&block)
|
27
|
-
if @exchange.respond_to? :on_return # Bunny
|
28
|
-
@exchange.on_return do |return_info, properties, content|
|
29
|
-
block.call(return_info[:reply_code], properties[:correlation_id])
|
30
|
-
end
|
31
|
-
elsif @channel.respond_to? :on_return # Hare
|
32
|
-
@channel.on_return do |reply_code, _, exchange_name, _, properties|
|
33
|
-
if exchange_name != Freddy::FREDDY_TOPIC_EXCHANGE_NAME
|
34
|
-
block.call(reply_code, properties.correlation_id)
|
35
|
-
end
|
36
|
-
end
|
37
|
-
else
|
38
|
-
raise OnReturnNotImplemented.new "AMQP implementation doesn't implement on_return"
|
39
|
-
end
|
40
|
-
end
|
41
25
|
end
|
42
26
|
end
|
data/lib/freddy/request.rb
CHANGED
@@ -11,26 +11,19 @@ class Freddy
|
|
11
11
|
class Request
|
12
12
|
NO_ROUTE = 312
|
13
13
|
|
14
|
-
class EmptyRequest < Exception
|
15
|
-
end
|
16
|
-
|
17
|
-
class EmptyResponder < Exception
|
18
|
-
end
|
19
|
-
|
20
14
|
def initialize(channel, logger, producer, consumer)
|
21
15
|
@channel, @logger = channel, logger
|
22
16
|
@producer, @consumer = producer, consumer
|
23
17
|
@request_map = Hamster.mutable_hash
|
24
18
|
@request_manager = RequestManager.new @request_map, @logger
|
25
19
|
|
26
|
-
@
|
20
|
+
@channel.on_return do |reply_code, correlation_id|
|
27
21
|
if reply_code == NO_ROUTE
|
28
22
|
@request_manager.no_route(correlation_id)
|
29
23
|
end
|
30
24
|
end
|
31
25
|
|
32
26
|
@listening_for_responses_lock = Mutex.new
|
33
|
-
@response_queue_lock = Mutex.new
|
34
27
|
end
|
35
28
|
|
36
29
|
def sync_request(destination, payload, opts)
|
@@ -63,61 +56,32 @@ class Freddy
|
|
63
56
|
)
|
64
57
|
end
|
65
58
|
|
66
|
-
def respond_to(destination, &block)
|
67
|
-
raise EmptyResponder unless block
|
68
|
-
|
69
|
-
ensure_response_queue_exists
|
70
|
-
@logger.info "Listening for requests on #{destination}"
|
71
|
-
responder_handler = @consumer.consume destination do |payload, delivery|
|
72
|
-
handler = MessageHandlers.for_type(delivery.metadata.type).new(@producer, destination, @logger)
|
73
|
-
|
74
|
-
msg_handler = MessageHandler.new(handler, delivery)
|
75
|
-
handler.handle_message payload, msg_handler, &block
|
76
|
-
end
|
77
|
-
responder_handler
|
78
|
-
end
|
79
|
-
|
80
59
|
private
|
81
60
|
|
82
|
-
def
|
83
|
-
|
84
|
-
end
|
61
|
+
def handle_response(delivery)
|
62
|
+
correlation_id = delivery.correlation_id
|
85
63
|
|
86
|
-
|
87
|
-
correlation_id = delivery.metadata.correlation_id
|
88
|
-
request = @request_map[correlation_id]
|
89
|
-
if request
|
64
|
+
if request = @request_map.delete(correlation_id)
|
90
65
|
@logger.debug "Got response for request to #{request[:destination]} with correlation_id #{correlation_id}"
|
91
|
-
|
92
|
-
request[:callback].call payload, delivery
|
66
|
+
request[:callback].call delivery.payload, delivery
|
93
67
|
else
|
94
68
|
@logger.warn "Got rpc response for correlation_id #{correlation_id} but there is no requester"
|
95
|
-
|
69
|
+
Utils.notify 'NoRequesterForResponse', "Got rpc response but there is no requester", correlation_id: correlation_id
|
96
70
|
end
|
97
71
|
rescue Exception => e
|
98
72
|
destination_report = request ? "to #{request[:destination]}" : ''
|
99
|
-
@logger.error "Exception occured while handling the response of request made #{destination_report} with correlation_id #{correlation_id}: #{
|
100
|
-
|
101
|
-
end
|
102
|
-
|
103
|
-
def ensure_response_queue_exists
|
104
|
-
@response_queue_lock.synchronize do
|
105
|
-
@response_queue ||= create_response_queue
|
106
|
-
end
|
73
|
+
@logger.error "Exception occured while handling the response of request made #{destination_report} with correlation_id #{correlation_id}: #{Utils.format_exception e}"
|
74
|
+
Utils.notify_exception(e, destination: request[:destination], correlation_id: correlation_id)
|
107
75
|
end
|
108
76
|
|
109
77
|
def ensure_listening_to_responses
|
78
|
+
return @listening_for_responses if defined?(@listening_for_responses)
|
79
|
+
|
110
80
|
@listening_for_responses_lock.synchronize do
|
111
|
-
|
112
|
-
|
113
|
-
|
114
|
-
|
115
|
-
@request_manager.start
|
116
|
-
@consumer.dedicated_consume @response_queue do |payload, delivery|
|
117
|
-
handle_response payload, delivery
|
118
|
-
end
|
119
|
-
@listening_for_responses = true
|
120
|
-
end
|
81
|
+
@response_queue ||= @channel.queue("", exclusive: true)
|
82
|
+
@request_manager.start
|
83
|
+
@consumer.response_consume(@response_queue, &method(:handle_response))
|
84
|
+
@listening_for_responses = true
|
121
85
|
end
|
122
86
|
end
|
123
87
|
end
|
@@ -33,7 +33,7 @@ class Freddy
|
|
33
33
|
@requests.delete correlation_id
|
34
34
|
|
35
35
|
@logger.warn "Request timed out waiting response from #{request[:destination]}, correlation id #{correlation_id}"
|
36
|
-
|
36
|
+
Utils.notify 'RequestTimeout', "Request timed out waiting for response from #{request[:destination]}", {
|
37
37
|
correlation_id: correlation_id,
|
38
38
|
destination: request[:destination],
|
39
39
|
timeout: request[:timeout]
|
@@ -1,21 +1,24 @@
|
|
1
1
|
class Freddy
|
2
2
|
class ResponderHandler
|
3
|
-
|
4
|
-
def initialize(consumer, channel)
|
3
|
+
def initialize(consumer, consume_thread_pool)
|
5
4
|
@consumer = consumer
|
6
|
-
@
|
5
|
+
@consume_thread_pool = consume_thread_pool
|
7
6
|
end
|
8
7
|
|
9
|
-
|
8
|
+
# Shutdown responder
|
9
|
+
#
|
10
|
+
# Stop responding to messages immediately, Waits until all workers are
|
11
|
+
# finished and then returns.
|
12
|
+
#
|
13
|
+
# @return [void]
|
14
|
+
#
|
15
|
+
# @example
|
16
|
+
# responder = freddy.respond_to 'Queue' do |msg, handler|
|
17
|
+
# end
|
18
|
+
# responder.shutdown
|
19
|
+
def shutdown
|
10
20
|
@consumer.cancel
|
11
|
-
|
12
|
-
|
13
|
-
def queue
|
14
|
-
@consumer.queue
|
15
|
-
end
|
16
|
-
|
17
|
-
def destroy_destination
|
18
|
-
@consumer.queue.delete
|
21
|
+
@consume_thread_pool.wait(:done)
|
19
22
|
end
|
20
23
|
end
|
21
24
|
end
|
@@ -1,20 +1,29 @@
|
|
1
|
+
require 'thread'
|
1
2
|
require 'timeout'
|
2
3
|
|
3
4
|
class Freddy
|
4
5
|
class SyncResponseContainer
|
6
|
+
def initialize
|
7
|
+
@mutex = Mutex.new
|
8
|
+
end
|
9
|
+
|
5
10
|
def call(response, delivery)
|
6
11
|
@response = response
|
7
12
|
@delivery = delivery
|
13
|
+
@mutex.synchronize { @waiting.wakeup }
|
8
14
|
end
|
9
15
|
|
10
16
|
def wait_for_response(timeout)
|
11
|
-
|
12
|
-
|
17
|
+
@mutex.synchronize do
|
18
|
+
@waiting = Thread.current
|
19
|
+
@mutex.sleep(timeout)
|
13
20
|
end
|
14
21
|
|
15
|
-
if @response
|
22
|
+
if @response.nil?
|
23
|
+
raise Timeout::Error, 'execution expired'
|
24
|
+
elsif @response[:error] == 'RequestTimeout'
|
16
25
|
raise TimeoutError.new(@response)
|
17
|
-
elsif !@delivery || @delivery.
|
26
|
+
elsif !@delivery || @delivery.type == 'error'
|
18
27
|
raise InvalidRequestError.new(@response)
|
19
28
|
else
|
20
29
|
@response
|
data/lib/freddy/utils.rb
ADDED
@@ -0,0 +1,32 @@
|
|
1
|
+
class Freddy
|
2
|
+
class Utils
|
3
|
+
def self.format_exception(exception)
|
4
|
+
backtrace = exception.backtrace.map do |x|
|
5
|
+
x.match(/^(.+?):(\d+)(|:in `(.+)')$/);
|
6
|
+
[$1, $2, $4]
|
7
|
+
end.join("\n")
|
8
|
+
|
9
|
+
"#{exception.exception}\n#{backtrace}"
|
10
|
+
end
|
11
|
+
|
12
|
+
def self.notify(name, message, parameters={})
|
13
|
+
return unless defined?(Airbrake)
|
14
|
+
|
15
|
+
Airbrake.notify_or_ignore(
|
16
|
+
error_class: name,
|
17
|
+
error_message: message,
|
18
|
+
cgi_data: ENV.to_hash,
|
19
|
+
parameters: parameters
|
20
|
+
)
|
21
|
+
end
|
22
|
+
|
23
|
+
def self.notify_exception(exception, parameters={})
|
24
|
+
return unless defined?(Airbrake)
|
25
|
+
|
26
|
+
Airbrake.notify_or_ignore(exception,
|
27
|
+
cgi_data: ENV.to_hash,
|
28
|
+
parameters: parameters
|
29
|
+
)
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
@@ -10,12 +10,8 @@ describe Freddy::Consumer do
|
|
10
10
|
|
11
11
|
after { freddy.close }
|
12
12
|
|
13
|
-
it 'raises exception when no consumer is provided' do
|
14
|
-
expect { consumer.consume destination }.to raise_error described_class::EmptyConsumer
|
15
|
-
end
|
16
|
-
|
17
13
|
it "doesn't call passed block without any messages" do
|
18
|
-
consumer.
|
14
|
+
consumer.respond_to destination do
|
19
15
|
@message_received = true
|
20
16
|
end
|
21
17
|
default_sleep
|
@@ -4,9 +4,7 @@ describe Freddy::MessageHandler do
|
|
4
4
|
subject(:handler) { described_class.new(adapter, delivery) }
|
5
5
|
|
6
6
|
let(:adapter) { double }
|
7
|
-
let(:delivery) { double(
|
8
|
-
let(:metadata) { double(reply_to: reply_to, correlation_id: 'abc') }
|
9
|
-
|
7
|
+
let(:delivery) { double(reply_to: reply_to, correlation_id: 'abc') }
|
10
8
|
let(:reply_to) { double }
|
11
9
|
|
12
10
|
describe '#success' do
|
data/spec/freddy/request_spec.rb
CHANGED
@@ -10,10 +10,6 @@ describe Freddy::Request do
|
|
10
10
|
|
11
11
|
after { freddy.close }
|
12
12
|
|
13
|
-
it 'raises empty responder exception when responding without callback' do
|
14
|
-
expect {@responder = request.respond_to destination }.to raise_error described_class::EmptyResponder
|
15
|
-
end
|
16
|
-
|
17
13
|
context 'requesting from multiple threads' do
|
18
14
|
let(:nr_of_threads) { 50 }
|
19
15
|
|
@@ -8,15 +8,49 @@ describe Freddy::ResponderHandler do
|
|
8
8
|
|
9
9
|
after { freddy.close }
|
10
10
|
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
11
|
+
describe '#shutdown' do
|
12
|
+
it 'lets ongoing workers to finish' do
|
13
|
+
count = 0
|
14
|
+
|
15
|
+
consumer_handler = freddy.respond_to destination do
|
16
|
+
sleep 0.1
|
17
|
+
count += 1
|
18
|
+
end
|
19
|
+
deliver
|
20
|
+
|
21
|
+
sleep 0.05
|
22
|
+
consumer_handler.shutdown
|
23
|
+
|
24
|
+
expect(count).to eq(1)
|
25
|
+
end
|
26
|
+
|
27
|
+
it 'does not accept new jobs' do
|
28
|
+
count = 0
|
29
|
+
|
30
|
+
consumer_handler = freddy.respond_to destination do
|
31
|
+
count += 1
|
32
|
+
end
|
33
|
+
|
34
|
+
consumer_handler.shutdown
|
35
|
+
deliver
|
36
|
+
|
37
|
+
expect(count).to eq(0)
|
15
38
|
end
|
16
|
-
deliver
|
17
|
-
consumer_handler.cancel
|
18
|
-
deliver
|
19
39
|
|
20
|
-
|
40
|
+
it 'does not touch other handlers' do
|
41
|
+
count = 0
|
42
|
+
|
43
|
+
freddy.respond_to destination do
|
44
|
+
count += 1
|
45
|
+
end
|
46
|
+
|
47
|
+
consumer_handler2 = freddy.respond_to random_destination do
|
48
|
+
count += 1
|
49
|
+
end
|
50
|
+
consumer_handler2.shutdown
|
51
|
+
|
52
|
+
deliver
|
53
|
+
expect(count).to eq(1)
|
54
|
+
end
|
21
55
|
end
|
22
56
|
end
|
@@ -0,0 +1,13 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe Freddy::SyncResponseContainer do
|
4
|
+
let(:container) { described_class.new }
|
5
|
+
|
6
|
+
context 'when timeout' do
|
7
|
+
subject { container.wait_for_response(0.01) }
|
8
|
+
|
9
|
+
it 'raises timeout error' do
|
10
|
+
expect { subject }.to raise_error(Timeout::Error, 'execution expired')
|
11
|
+
end
|
12
|
+
end
|
13
|
+
end
|
@@ -62,4 +62,26 @@ describe 'Concurrency' do
|
|
62
62
|
expect(received1).to be(true)
|
63
63
|
expect(received2).to be(true)
|
64
64
|
end
|
65
|
+
|
66
|
+
it 'supports adding multiple #tap_into listeners' do
|
67
|
+
results = 10.times.map do |id|
|
68
|
+
Thread.new do
|
69
|
+
freddy1.tap_into "tap_into.listener.#{id}" do
|
70
|
+
end
|
71
|
+
end
|
72
|
+
end.map(&:join)
|
73
|
+
|
74
|
+
expect(results.count).to eq(10)
|
75
|
+
end
|
76
|
+
|
77
|
+
it 'supports adding multiple #respond_to listeners' do
|
78
|
+
results = 10.times.map do |id|
|
79
|
+
Thread.new do
|
80
|
+
freddy1.respond_to "respond_to.listener.#{id}" do
|
81
|
+
end
|
82
|
+
end
|
83
|
+
end.map(&:join)
|
84
|
+
|
85
|
+
expect(results.count).to eq(10)
|
86
|
+
end
|
65
87
|
end
|
@@ -23,7 +23,6 @@ describe 'Logging' do
|
|
23
23
|
|
24
24
|
it 'logs all consumed messages' do
|
25
25
|
expect(logger1).to have_received(:info).with(/Listening for requests on \S+/)
|
26
|
-
expect(logger1).to have_received(:debug).with(/Consuming messages on \S+/)
|
27
26
|
expect(logger1).to have_received(:debug).with(/Received message on \S+ with payload {:pay=>"load"}/)
|
28
27
|
end
|
29
28
|
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: freddy-jruby
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.4.
|
4
|
+
version: 0.4.9
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Urmas Talimaa
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2015-
|
11
|
+
date: 2015-12-16 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
requirement: !ruby/object:Gem::Requirement
|
@@ -113,22 +113,33 @@ files:
|
|
113
113
|
- Rakefile
|
114
114
|
- freddy.gemspec
|
115
115
|
- lib/freddy.rb
|
116
|
-
- lib/freddy/
|
116
|
+
- lib/freddy/adapters.rb
|
117
|
+
- lib/freddy/adapters/bunny_adapter.rb
|
118
|
+
- lib/freddy/adapters/march_hare_adapter.rb
|
117
119
|
- lib/freddy/consumer.rb
|
120
|
+
- lib/freddy/consumers/respond_to_consumer.rb
|
121
|
+
- lib/freddy/consumers/response_consumer.rb
|
122
|
+
- lib/freddy/consumers/tap_into_consumer.rb
|
118
123
|
- lib/freddy/delivery.rb
|
124
|
+
- lib/freddy/error_response.rb
|
125
|
+
- lib/freddy/invalid_request_error.rb
|
119
126
|
- lib/freddy/message_handler.rb
|
120
127
|
- lib/freddy/message_handlers.rb
|
128
|
+
- lib/freddy/payload.rb
|
121
129
|
- lib/freddy/producer.rb
|
122
130
|
- lib/freddy/request.rb
|
123
131
|
- lib/freddy/request_manager.rb
|
124
132
|
- lib/freddy/responder_handler.rb
|
125
133
|
- lib/freddy/sync_response_container.rb
|
134
|
+
- lib/freddy/timeout_error.rb
|
135
|
+
- lib/freddy/utils.rb
|
126
136
|
- spec/freddy/consumer_spec.rb
|
127
137
|
- spec/freddy/error_response_spec.rb
|
128
138
|
- spec/freddy/freddy_spec.rb
|
129
139
|
- spec/freddy/message_handler_spec.rb
|
130
140
|
- spec/freddy/request_spec.rb
|
131
141
|
- spec/freddy/responder_handler_spec.rb
|
142
|
+
- spec/freddy/sync_response_container_spec.rb
|
132
143
|
- spec/integration/concurrency_spec.rb
|
133
144
|
- spec/integration/logging_spec.rb
|
134
145
|
- spec/spec_helper.rb
|
@@ -163,6 +174,7 @@ test_files:
|
|
163
174
|
- spec/freddy/message_handler_spec.rb
|
164
175
|
- spec/freddy/request_spec.rb
|
165
176
|
- spec/freddy/responder_handler_spec.rb
|
177
|
+
- spec/freddy/sync_response_container_spec.rb
|
166
178
|
- spec/integration/concurrency_spec.rb
|
167
179
|
- spec/integration/logging_spec.rb
|
168
180
|
- spec/spec_helper.rb
|
@@ -1,34 +0,0 @@
|
|
1
|
-
class Freddy
|
2
|
-
class AdaptiveQueue
|
3
|
-
def initialize(queue)
|
4
|
-
@queue = queue
|
5
|
-
end
|
6
|
-
|
7
|
-
def subscribe(&block)
|
8
|
-
if hare?
|
9
|
-
@queue.subscribe do |meta, payload|
|
10
|
-
block.call(payload, Delivery.new(meta, meta.routing_key))
|
11
|
-
end
|
12
|
-
else
|
13
|
-
@queue.subscribe do |info, properties, payload|
|
14
|
-
block.call(payload, Delivery.new(properties, info.routing_key))
|
15
|
-
end
|
16
|
-
end
|
17
|
-
end
|
18
|
-
|
19
|
-
def bind(*args)
|
20
|
-
@queue.bind(*args)
|
21
|
-
self
|
22
|
-
end
|
23
|
-
|
24
|
-
def name
|
25
|
-
@queue.name
|
26
|
-
end
|
27
|
-
|
28
|
-
private
|
29
|
-
|
30
|
-
def hare?
|
31
|
-
RUBY_PLATFORM == 'java'
|
32
|
-
end
|
33
|
-
end
|
34
|
-
end
|