freddy 0.7.0 → 0.7.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/README.md +1 -4
- data/freddy.gemspec +1 -1
- data/lib/freddy/adapters/bunny_adapter.rb +11 -6
- data/lib/freddy/adapters/march_hare_adapter.rb +11 -6
- data/lib/freddy/adapters.rb +4 -0
- data/lib/freddy/consumers/respond_to_consumer.rb +20 -9
- data/lib/freddy/consumers/response_consumer.rb +6 -10
- data/lib/freddy/consumers/tap_into_consumer.rb +25 -13
- data/lib/freddy/delivery.rb +3 -2
- data/lib/freddy/producers/send_and_wait_response_producer.rb +1 -1
- data/lib/freddy.rb +21 -10
- data/spec/freddy/consumers/respond_to_consumer_spec.rb +70 -10
- data/spec/freddy/responder_handler_spec.rb +2 -2
- metadata +3 -3
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 157ccf48c23813d9fdb90dd43aa3fc0e6ee8f1f0
|
4
|
+
data.tar.gz: 108c3d3d1f11e56fc09d71898d82a2339dffbdbe
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: e41fa3d55fbe4a56f796000e547b2c8764a6ad26e3520a42b5a139d22c60c84625d0659ae94071ea0ae5ddacbf55fb90c492499fefe73f40a9cbee709b214485
|
7
|
+
data.tar.gz: 44f2cc1f17506c03691cdf9cd429ddaba6e057c75e88c4375608be2dc05e9e7ba621e619f85763022913c9b48146f28145f192c6660ec2bc4ce70547806cfce3
|
data/README.md
CHANGED
@@ -138,10 +138,7 @@ responder_handler.shutdown
|
|
138
138
|
|
139
139
|
## Notes about concurrency
|
140
140
|
|
141
|
-
The
|
142
|
-
To resolve this problem *freddy* uses a thread pool for running concurrent responders.
|
143
|
-
The thread pool is shared between *tap_into* and *respond_to* callbacks and the default size is 4.
|
144
|
-
The thread pool size can be configured by passing the configuration option *max_concurrency*.
|
141
|
+
*freddy* uses a thread pool to run concurrent responders. The thread pool is unique for each *tap_into* and *respond_to* responder. Thread pool size can be configured by passing the configuration option *max_concurrency*. Its default value is 4. e.g. If your application has 2 *respond_to* responders and 1 *tap_into* responder with *max_concurrency* set to 3 then your application may process up to 9 messages in parallel.
|
145
142
|
|
146
143
|
|
147
144
|
Note that while it is possible to use *deliver_with_response* inside a *respond_to* block,
|
data/freddy.gemspec
CHANGED
@@ -13,8 +13,10 @@ class Freddy
|
|
13
13
|
@bunny = bunny
|
14
14
|
end
|
15
15
|
|
16
|
-
def create_channel
|
17
|
-
|
16
|
+
def create_channel(prefetch: nil)
|
17
|
+
bunny_channel = @bunny.create_channel
|
18
|
+
bunny_channel.prefetch(prefetch) if prefetch
|
19
|
+
Channel.new(bunny_channel)
|
18
20
|
end
|
19
21
|
|
20
22
|
def close
|
@@ -30,7 +32,7 @@ class Freddy
|
|
30
32
|
@channel = channel
|
31
33
|
end
|
32
34
|
|
33
|
-
def_delegators :@channel, :topic, :default_exchange, :consumers
|
35
|
+
def_delegators :@channel, :topic, :default_exchange, :consumers, :acknowledge
|
34
36
|
|
35
37
|
def queue(*args)
|
36
38
|
Queue.new(@channel.queue(*args))
|
@@ -46,10 +48,13 @@ class Freddy
|
|
46
48
|
end
|
47
49
|
|
48
50
|
class Queue < Shared::Queue
|
49
|
-
def subscribe(&block)
|
50
|
-
@queue.subscribe do |info, properties, payload|
|
51
|
+
def subscribe(manual_ack: false, &block)
|
52
|
+
@queue.subscribe(manual_ack: manual_ack) do |info, properties, payload|
|
51
53
|
parsed_payload = Payload.parse(payload)
|
52
|
-
|
54
|
+
delivery = Delivery.new(
|
55
|
+
parsed_payload, properties, info.routing_key, info.delivery_tag
|
56
|
+
)
|
57
|
+
block.call(delivery)
|
53
58
|
end
|
54
59
|
end
|
55
60
|
end
|
@@ -12,8 +12,10 @@ class Freddy
|
|
12
12
|
@hare = hare
|
13
13
|
end
|
14
14
|
|
15
|
-
def create_channel
|
16
|
-
|
15
|
+
def create_channel(prefetch: nil)
|
16
|
+
hare_channel = @hare.create_channel
|
17
|
+
hare_channel.basic_qos(prefetch) if prefetch
|
18
|
+
Channel.new(hare_channel)
|
17
19
|
end
|
18
20
|
|
19
21
|
def close
|
@@ -29,7 +31,7 @@ class Freddy
|
|
29
31
|
@channel = channel
|
30
32
|
end
|
31
33
|
|
32
|
-
def_delegators :@channel, :topic, :default_exchange, :consumers
|
34
|
+
def_delegators :@channel, :topic, :default_exchange, :consumers, :acknowledge
|
33
35
|
|
34
36
|
def queue(*args)
|
35
37
|
Queue.new(@channel.queue(*args))
|
@@ -45,10 +47,13 @@ class Freddy
|
|
45
47
|
end
|
46
48
|
|
47
49
|
class Queue < Shared::Queue
|
48
|
-
def subscribe(&block)
|
49
|
-
@queue.subscribe do |meta, payload|
|
50
|
+
def subscribe(manual_ack: false, &block)
|
51
|
+
@queue.subscribe(manual_ack: manual_ack) do |meta, payload|
|
50
52
|
parsed_payload = Payload.parse(payload)
|
51
|
-
|
53
|
+
delivery = Delivery.new(
|
54
|
+
parsed_payload, meta, meta.routing_key, meta.delivery_tag
|
55
|
+
)
|
56
|
+
block.call(delivery)
|
52
57
|
end
|
53
58
|
end
|
54
59
|
end
|
data/lib/freddy/adapters.rb
CHANGED
@@ -1,16 +1,23 @@
|
|
1
1
|
class Freddy
|
2
2
|
module Consumers
|
3
3
|
class RespondToConsumer
|
4
|
-
def
|
5
|
-
|
4
|
+
def self.consume(*attrs, &block)
|
5
|
+
new(*attrs).consume(&block)
|
6
|
+
end
|
7
|
+
|
8
|
+
def initialize(logger:, thread_pool:, destination:, channel:, handler_adapter_factory:)
|
6
9
|
@logger = logger
|
10
|
+
@consume_thread_pool = thread_pool
|
11
|
+
@destination = destination
|
12
|
+
@channel = channel
|
13
|
+
@handler_adapter_factory = handler_adapter_factory
|
7
14
|
end
|
8
15
|
|
9
|
-
def consume(
|
10
|
-
consumer = consume_from_destination
|
11
|
-
Consumers.log_receive_event(@logger, destination, delivery)
|
16
|
+
def consume(&block)
|
17
|
+
consumer = consume_from_destination do |delivery|
|
18
|
+
Consumers.log_receive_event(@logger, @destination, delivery)
|
12
19
|
|
13
|
-
adapter = handler_adapter_factory.for(delivery)
|
20
|
+
adapter = @handler_adapter_factory.for(delivery)
|
14
21
|
|
15
22
|
msg_handler = MessageHandler.new(adapter, delivery)
|
16
23
|
block.call(delivery.payload, msg_handler)
|
@@ -21,15 +28,19 @@ class Freddy
|
|
21
28
|
|
22
29
|
private
|
23
30
|
|
24
|
-
def consume_from_destination(
|
25
|
-
channel.queue(destination).subscribe do |delivery|
|
31
|
+
def consume_from_destination(&block)
|
32
|
+
@channel.queue(@destination).subscribe(manual_ack: true) do |delivery|
|
26
33
|
process_message(delivery, &block)
|
27
34
|
end
|
28
35
|
end
|
29
36
|
|
30
37
|
def process_message(delivery, &block)
|
31
38
|
@consume_thread_pool.process do
|
32
|
-
|
39
|
+
begin
|
40
|
+
block.call(delivery)
|
41
|
+
ensure
|
42
|
+
@channel.acknowledge(delivery.tag, false)
|
43
|
+
end
|
33
44
|
end
|
34
45
|
end
|
35
46
|
end
|
@@ -3,24 +3,20 @@ class Freddy
|
|
3
3
|
class ResponseConsumer
|
4
4
|
def initialize(logger)
|
5
5
|
@logger = logger
|
6
|
-
@dedicated_thread_pool = Thread.pool(1)
|
7
6
|
end
|
8
7
|
|
9
|
-
def consume(queue, &block)
|
8
|
+
def consume(channel, queue, &block)
|
10
9
|
@logger.debug "Consuming messages on #{queue.name}"
|
11
|
-
|
12
|
-
process_message(queue, delivery, &block)
|
10
|
+
queue.subscribe do |delivery|
|
11
|
+
process_message(channel, queue, delivery, &block)
|
13
12
|
end
|
14
|
-
ResponderHandler.new(consumer, @dedicated_thread_pool)
|
15
13
|
end
|
16
14
|
|
17
15
|
private
|
18
16
|
|
19
|
-
def process_message(queue, delivery, &block)
|
20
|
-
@
|
21
|
-
|
22
|
-
block.call(delivery)
|
23
|
-
end
|
17
|
+
def process_message(channel, queue, delivery, &block)
|
18
|
+
Consumers.log_receive_event(@logger, queue.name, delivery)
|
19
|
+
block.call(delivery)
|
24
20
|
end
|
25
21
|
end
|
26
22
|
end
|
@@ -1,15 +1,22 @@
|
|
1
1
|
class Freddy
|
2
2
|
module Consumers
|
3
3
|
class TapIntoConsumer
|
4
|
-
def
|
4
|
+
def self.consume(*attrs, &block)
|
5
|
+
new(*attrs).consume(&block)
|
6
|
+
end
|
7
|
+
|
8
|
+
def initialize(logger:, thread_pool:, pattern:, channel:, options:)
|
5
9
|
@logger = logger
|
6
|
-
@consume_thread_pool =
|
10
|
+
@consume_thread_pool = thread_pool
|
11
|
+
@pattern = pattern
|
12
|
+
@channel = channel
|
13
|
+
@options = options
|
7
14
|
end
|
8
15
|
|
9
|
-
def consume(
|
10
|
-
queue = create_queue
|
16
|
+
def consume(&block)
|
17
|
+
queue = create_queue
|
11
18
|
|
12
|
-
consumer = queue.subscribe do |delivery|
|
19
|
+
consumer = queue.subscribe(manual_ack: true) do |delivery|
|
13
20
|
process_message(queue, delivery, &block)
|
14
21
|
end
|
15
22
|
|
@@ -18,24 +25,29 @@ class Freddy
|
|
18
25
|
|
19
26
|
private
|
20
27
|
|
21
|
-
def create_queue
|
22
|
-
topic_exchange = channel.topic(Freddy::FREDDY_TOPIC_EXCHANGE_NAME)
|
28
|
+
def create_queue
|
29
|
+
topic_exchange = @channel.topic(Freddy::FREDDY_TOPIC_EXCHANGE_NAME)
|
30
|
+
group = @options.fetch(:group, nil)
|
23
31
|
|
24
32
|
if group
|
25
|
-
channel
|
33
|
+
@channel
|
26
34
|
.queue("groups.#{group}")
|
27
|
-
.bind(topic_exchange, routing_key: pattern)
|
35
|
+
.bind(topic_exchange, routing_key: @pattern)
|
28
36
|
else
|
29
|
-
channel
|
37
|
+
@channel
|
30
38
|
.queue('', exclusive: true)
|
31
|
-
.bind(topic_exchange, routing_key: pattern)
|
39
|
+
.bind(topic_exchange, routing_key: @pattern)
|
32
40
|
end
|
33
41
|
end
|
34
42
|
|
35
43
|
def process_message(queue, delivery, &block)
|
36
44
|
@consume_thread_pool.process do
|
37
|
-
|
38
|
-
|
45
|
+
begin
|
46
|
+
Consumers.log_receive_event(@logger, queue.name, delivery)
|
47
|
+
block.call delivery.payload, delivery.routing_key
|
48
|
+
ensure
|
49
|
+
@channel.acknowledge(delivery.tag, false)
|
50
|
+
end
|
39
51
|
end
|
40
52
|
end
|
41
53
|
end
|
data/lib/freddy/delivery.rb
CHANGED
@@ -1,11 +1,12 @@
|
|
1
1
|
class Freddy
|
2
2
|
class Delivery
|
3
|
-
attr_reader :routing_key, :payload
|
3
|
+
attr_reader :routing_key, :payload, :tag
|
4
4
|
|
5
|
-
def initialize(payload, metadata, routing_key)
|
5
|
+
def initialize(payload, metadata, routing_key, tag)
|
6
6
|
@payload = payload
|
7
7
|
@metadata = metadata
|
8
8
|
@routing_key = routing_key
|
9
|
+
@tag = tag
|
9
10
|
end
|
10
11
|
|
11
12
|
def correlation_id
|
@@ -19,7 +19,7 @@ class Freddy
|
|
19
19
|
@response_queue = @channel.queue("", exclusive: true)
|
20
20
|
|
21
21
|
@response_consumer = Consumers::ResponseConsumer.new(@logger)
|
22
|
-
@response_consumer.consume(@response_queue, &method(:handle_response))
|
22
|
+
@response_consumer.consume(@channel, @response_queue, &method(:handle_response))
|
23
23
|
end
|
24
24
|
|
25
25
|
def produce(destination, payload, timeout_in_seconds:, delete_on_timeout:, **properties)
|
data/lib/freddy.rb
CHANGED
@@ -17,6 +17,7 @@ class Freddy
|
|
17
17
|
# @option config [Integer] :port (5672)
|
18
18
|
# @option config [String] :user ('guest')
|
19
19
|
# @option config [String] :pass ('guest')
|
20
|
+
# @option config [Integer] :max_concurrency (4)
|
20
21
|
#
|
21
22
|
# @return [Freddy]
|
22
23
|
#
|
@@ -24,17 +25,14 @@ class Freddy
|
|
24
25
|
# Freddy.build(Logger.new(STDOUT), user: 'thumper', pass: 'howdy')
|
25
26
|
def self.build(logger = Logger.new(STDOUT), max_concurrency: DEFAULT_MAX_CONCURRENCY, **config)
|
26
27
|
connection = Adapters.determine.connect(config)
|
27
|
-
consume_thread_pool = Thread.pool(max_concurrency)
|
28
28
|
|
29
|
-
new(connection, logger,
|
29
|
+
new(connection, logger, max_concurrency)
|
30
30
|
end
|
31
31
|
|
32
|
-
def initialize(connection, logger,
|
32
|
+
def initialize(connection, logger, max_concurrency)
|
33
33
|
@connection = connection
|
34
34
|
@logger = logger
|
35
|
-
|
36
|
-
@tap_into_consumer = Consumers::TapIntoConsumer.new(consume_thread_pool, @logger)
|
37
|
-
@respond_to_consumer = Consumers::RespondToConsumer.new(consume_thread_pool, @logger)
|
35
|
+
@prefetch_buffer_size = max_concurrency
|
38
36
|
|
39
37
|
@send_and_forget_producer = Producers::SendAndForgetProducer.new(
|
40
38
|
connection.create_channel, logger
|
@@ -72,12 +70,17 @@ class Freddy
|
|
72
70
|
def respond_to(destination, &callback)
|
73
71
|
@logger.info "Listening for requests on #{destination}"
|
74
72
|
|
75
|
-
channel = @connection.create_channel
|
73
|
+
channel = @connection.create_channel(prefetch: @prefetch_buffer_size)
|
76
74
|
producer = Producers::ReplyProducer.new(channel, @logger)
|
77
75
|
handler_adapter_factory = MessageHandlerAdapters::Factory.new(producer)
|
78
76
|
|
79
|
-
|
80
|
-
|
77
|
+
Consumers::RespondToConsumer.consume(
|
78
|
+
logger: @logger,
|
79
|
+
thread_pool: Thread.pool(@prefetch_buffer_size),
|
80
|
+
destination: destination,
|
81
|
+
channel: channel,
|
82
|
+
handler_adapter_factory: handler_adapter_factory,
|
83
|
+
&callback
|
81
84
|
)
|
82
85
|
end
|
83
86
|
|
@@ -105,7 +108,15 @@ class Freddy
|
|
105
108
|
# end
|
106
109
|
def tap_into(pattern, options = {}, &callback)
|
107
110
|
@logger.debug "Tapping into messages that match #{pattern}"
|
108
|
-
|
111
|
+
|
112
|
+
Consumers::TapIntoConsumer.consume(
|
113
|
+
logger: @logger,
|
114
|
+
thread_pool: Thread.pool(@prefetch_buffer_size),
|
115
|
+
pattern: pattern,
|
116
|
+
channel: @connection.create_channel(prefetch: @prefetch_buffer_size),
|
117
|
+
options: options,
|
118
|
+
&callback
|
119
|
+
)
|
109
120
|
end
|
110
121
|
|
111
122
|
# Sends a message to given destination
|
@@ -1,24 +1,84 @@
|
|
1
1
|
require 'spec_helper'
|
2
2
|
|
3
3
|
describe Freddy::Consumers::RespondToConsumer do
|
4
|
-
let(:consumer)
|
4
|
+
let(:consumer) do
|
5
|
+
described_class.new(
|
6
|
+
logger: logger,
|
7
|
+
thread_pool: thread_pool,
|
8
|
+
destination: destination,
|
9
|
+
channel: channel,
|
10
|
+
handler_adapter_factory: msg_handler_adapter_factory
|
11
|
+
)
|
12
|
+
end
|
5
13
|
|
6
|
-
let(:connection)
|
7
|
-
let(:thread_pool) { Thread.pool(1) }
|
14
|
+
let(:connection) { Freddy::Adapters.determine.connect(config) }
|
8
15
|
let(:destination) { random_destination }
|
9
|
-
let(:payload)
|
10
|
-
let(:
|
16
|
+
let(:payload) { {pay: 'load'} }
|
17
|
+
let(:msg_handler_adapter_factory) { double(for: msg_handler_adapter) }
|
18
|
+
let(:msg_handler_adapter) { Freddy::MessageHandlerAdapters::NoOpHandler.new }
|
19
|
+
let(:prefetch_buffer_size) { 2 }
|
20
|
+
let(:thread_pool) { Thread.pool(prefetch_buffer_size) }
|
11
21
|
|
12
22
|
after do
|
13
23
|
connection.close
|
14
24
|
end
|
15
25
|
|
16
|
-
|
17
|
-
|
18
|
-
|
26
|
+
context 'when no messages' do
|
27
|
+
let(:channel) { connection.create_channel }
|
28
|
+
|
29
|
+
it "doesn't call passed block" do
|
30
|
+
consumer.consume do
|
31
|
+
@message_received = true
|
32
|
+
end
|
33
|
+
default_sleep
|
34
|
+
|
35
|
+
expect(@message_received).to be_falsy
|
36
|
+
end
|
37
|
+
end
|
38
|
+
|
39
|
+
context 'when thread pool is full' do
|
40
|
+
let(:prefetch_buffer_size) { 1 }
|
41
|
+
let(:msg_count) { prefetch_buffer_size + 1 }
|
42
|
+
let(:channel) { connection.create_channel(prefetch: prefetch_buffer_size) }
|
43
|
+
let(:mutex) { Mutex.new }
|
44
|
+
let(:consume_lock) { ConditionVariable.new }
|
45
|
+
let(:queue) { channel.queue(destination) }
|
46
|
+
|
47
|
+
after do
|
48
|
+
# Release the final queued message before finishing the test to avoid
|
49
|
+
# bunny warnings.
|
50
|
+
process_message
|
19
51
|
end
|
20
|
-
default_sleep
|
21
52
|
|
22
|
-
|
53
|
+
it 'does not consume more messages' do
|
54
|
+
consumer.consume do
|
55
|
+
wait_until_released
|
56
|
+
end
|
57
|
+
|
58
|
+
msg_count.times { deliver_message }
|
59
|
+
|
60
|
+
sleep default_sleep
|
61
|
+
expect(queue.message_count).to eq(msg_count - prefetch_buffer_size)
|
62
|
+
|
63
|
+
process_message
|
64
|
+
expect(queue.message_count).to eq(0)
|
65
|
+
end
|
66
|
+
|
67
|
+
def process_message
|
68
|
+
release_consume_lock
|
69
|
+
sleep default_sleep
|
70
|
+
end
|
71
|
+
|
72
|
+
def deliver_message
|
73
|
+
channel.default_exchange.publish('{}', routing_key: destination)
|
74
|
+
end
|
75
|
+
|
76
|
+
def wait_until_released
|
77
|
+
mutex.synchronize { consume_lock.wait(mutex) }
|
78
|
+
end
|
79
|
+
|
80
|
+
def release_consume_lock
|
81
|
+
mutex.synchronize { consume_lock.broadcast }
|
82
|
+
end
|
23
83
|
end
|
24
84
|
end
|
@@ -13,12 +13,12 @@ describe Freddy::ResponderHandler do
|
|
13
13
|
count = 0
|
14
14
|
|
15
15
|
consumer_handler = freddy.respond_to destination do
|
16
|
-
sleep 0.
|
16
|
+
sleep 0.3
|
17
17
|
count += 1
|
18
18
|
end
|
19
19
|
deliver
|
20
20
|
|
21
|
-
sleep 0.
|
21
|
+
sleep 0.15
|
22
22
|
consumer_handler.shutdown
|
23
23
|
|
24
24
|
expect(count).to eq(1)
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: freddy
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.7.
|
4
|
+
version: 0.7.1
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Urmas Talimaa
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date:
|
11
|
+
date: 2017-01-29 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: bundler
|
@@ -151,7 +151,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
151
151
|
version: '0'
|
152
152
|
requirements: []
|
153
153
|
rubyforge_project:
|
154
|
-
rubygems_version: 2.
|
154
|
+
rubygems_version: 2.6.8
|
155
155
|
signing_key:
|
156
156
|
specification_version: 4
|
157
157
|
summary: API for inter-application messaging supporting acknowledgements and request-response
|