freddy 2.11.2 → 3.0.0
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/.github/workflows/ci.yml +2 -2
- data/.github/workflows/publish.yml +4 -2
- data/Gemfile.lock +10 -6
- data/README.md +9 -0
- data/freddy.gemspec +1 -1
- data/lib/freddy/adapters/bunny_adapter.rb +26 -6
- data/lib/freddy/consumers/respond_to_consumer.rb +1 -1
- data/lib/freddy/consumers/tap_into_consumer.rb +4 -4
- data/lib/freddy/delivery.rb +3 -2
- data/lib/freddy/version.rb +1 -1
- data/spec/freddy/adapters/bunny_adapter/channel_spec.rb +58 -0
- data/spec/integration/connection_recovery_spec.rb +41 -0
- metadata +5 -3
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 2fee629805345ae363989fe96424d8ba0bcffd08ddd33f4e19327338ff5d0325
|
|
4
|
+
data.tar.gz: 4c1c4d92499a10b5e4cfd19adb390c8457a2ec873297a678fd755f70addd93e7
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: ad6991648e494845fde59d859068b42463959a81e2a846fdc5026556ac5ce7505bfa53370b78c6e9d30b101ad45b71faa67f83488d48a69a98aa5e5d6a104e90
|
|
7
|
+
data.tar.gz: 2786a37b993927f816e330846cc16a5aaaf07aff8d4c4c3f658c1088991f6ed9457d0931b1e6e253ee13e6ce85618e172747b801408100038f6b53f9d7ab57ee
|
data/.github/workflows/ci.yml
CHANGED
|
@@ -19,11 +19,11 @@ jobs:
|
|
|
19
19
|
- 5672:5672
|
|
20
20
|
options: --health-cmd "rabbitmqctl node_health_check" --health-interval 10s --health-timeout 5s --health-retries 5
|
|
21
21
|
steps:
|
|
22
|
-
- uses: actions/checkout@v2
|
|
22
|
+
- uses: actions/checkout@0717577d45739eb3c851188b29f50ed6c0b2194e # v2.8.0
|
|
23
23
|
with:
|
|
24
24
|
submodules: true
|
|
25
25
|
- name: Set up Ruby
|
|
26
|
-
uses: ruby/setup-ruby@v1
|
|
26
|
+
uses: ruby/setup-ruby@762794c140bbeda0f1224786aa33b4b46783a6c1 # v1.326.0
|
|
27
27
|
with:
|
|
28
28
|
ruby-version: ${{ matrix.ruby-version }}
|
|
29
29
|
bundler-cache: true
|
|
@@ -6,16 +6,18 @@ on:
|
|
|
6
6
|
- master
|
|
7
7
|
jobs:
|
|
8
8
|
build:
|
|
9
|
+
permissions:
|
|
10
|
+
contents: write
|
|
9
11
|
runs-on: ubuntu-latest
|
|
10
12
|
|
|
11
13
|
steps:
|
|
12
|
-
- uses: actions/checkout@v2
|
|
14
|
+
- uses: actions/checkout@0717577d45739eb3c851188b29f50ed6c0b2194e # v2.8.0
|
|
13
15
|
with:
|
|
14
16
|
fetch-depth: 2
|
|
15
17
|
|
|
16
18
|
|
|
17
19
|
- name: Release Gem
|
|
18
|
-
uses: discourse/publish-rubygems-action@4bd305c65315cb691bad1e8de97a87aaf29a0a85
|
|
20
|
+
uses: discourse/publish-rubygems-action@4bd305c65315cb691bad1e8de97a87aaf29a0a85 # v3
|
|
19
21
|
env:
|
|
20
22
|
RUBYGEMS_API_KEY: ${{secrets.RUBYGEMS_API_KEY}}
|
|
21
23
|
RELEASE_COMMAND: rake release
|
data/Gemfile.lock
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
PATH
|
|
2
2
|
remote: .
|
|
3
3
|
specs:
|
|
4
|
-
freddy (
|
|
5
|
-
bunny (~>
|
|
4
|
+
freddy (3.0.0)
|
|
5
|
+
bunny (~> 3.5)
|
|
6
6
|
concurrent-ruby (~> 1.0, >= 1.3.7)
|
|
7
7
|
oj (~> 3.6, >= 3.17.3)
|
|
8
8
|
opentelemetry-api (~> 1.0)
|
|
@@ -12,23 +12,27 @@ PATH
|
|
|
12
12
|
GEM
|
|
13
13
|
remote: https://rubygems.org/
|
|
14
14
|
specs:
|
|
15
|
-
amq-protocol (2.
|
|
15
|
+
amq-protocol (2.9.0)
|
|
16
16
|
ast (2.4.3)
|
|
17
17
|
bigdecimal (4.1.2)
|
|
18
|
-
bunny (
|
|
19
|
-
amq-protocol (~> 2.
|
|
18
|
+
bunny (3.5.0)
|
|
19
|
+
amq-protocol (~> 2.9)
|
|
20
|
+
logger (~> 1, >= 1.7)
|
|
21
|
+
openssl (>= 3.3)
|
|
20
22
|
sorted_set (~> 1, >= 1.0.2)
|
|
21
23
|
coderay (1.1.3)
|
|
22
24
|
concurrent-ruby (1.3.7)
|
|
23
25
|
diff-lcs (1.4.4)
|
|
24
26
|
hamster (3.0.0)
|
|
25
27
|
concurrent-ruby (~> 1.0)
|
|
26
|
-
json (2.
|
|
28
|
+
json (2.21.2)
|
|
27
29
|
language_server-protocol (3.17.0.3)
|
|
30
|
+
logger (1.7.0)
|
|
28
31
|
method_source (1.0.0)
|
|
29
32
|
oj (3.17.3)
|
|
30
33
|
bigdecimal (>= 3.0)
|
|
31
34
|
ostruct (>= 0.2)
|
|
35
|
+
openssl (4.0.2)
|
|
32
36
|
opentelemetry-api (1.5.0)
|
|
33
37
|
opentelemetry-common (0.19.6)
|
|
34
38
|
opentelemetry-api (~> 1.0)
|
data/README.md
CHANGED
|
@@ -15,6 +15,15 @@ freddy = Freddy.build(logger, host: 'localhost', port: 5672, user: 'guest', pass
|
|
|
15
15
|
|
|
16
16
|
A new version is created when a change is merged into the master branch that changes the version number in `freddy.gemspec`. A Github Action will create a tag for the version and push the `.gem` file to [rubygems.org](https://rubygems.org)
|
|
17
17
|
|
|
18
|
+
## Upgrading from 2.x to 3.0
|
|
19
|
+
|
|
20
|
+
Freddy 3.0 runs on Bunny 3.5 instead of 2.24, which fixes RPC channels left closed after a connection recovery.
|
|
21
|
+
|
|
22
|
+
* Change the Gemfile constraint to `gem 'freddy', '~> 3.0'`; Ruby 3.2 or later is required.
|
|
23
|
+
* Bunny 3.5 also installs the `openssl` and `logger` gems: check TLS connections in a deployed environment before production.
|
|
24
|
+
* Code that calls Bunny directly must follow the "Breaking Changes" of 3.0.0 in the [Bunny changelog](https://github.com/ruby-amqp/bunny/blob/main/ChangeLog.md), e.g. delivery tags are now plain integers.
|
|
25
|
+
* Code that reaches into Freddy internals must follow their new signatures: `Freddy::Delivery.new` takes the recovery count, and the adapter channel's `acknowledge`/`reject` take the delivery instead of its tag.
|
|
26
|
+
|
|
18
27
|
## Supported message queues
|
|
19
28
|
|
|
20
29
|
These message queues have been tested and are working with Freddy. Other queues can be added easily:
|
data/freddy.gemspec
CHANGED
|
@@ -17,7 +17,7 @@ Gem::Specification.new do |spec|
|
|
|
17
17
|
spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) }
|
|
18
18
|
spec.require_paths = ['lib']
|
|
19
19
|
|
|
20
|
-
spec.add_dependency 'bunny', '~>
|
|
20
|
+
spec.add_dependency 'bunny', '~> 3.5'
|
|
21
21
|
spec.add_dependency 'concurrent-ruby', '~> 1.0', '>= 1.3.7'
|
|
22
22
|
spec.add_dependency 'oj', '~> 3.6', '>= 3.17.3'
|
|
23
23
|
spec.add_dependency 'opentelemetry-api', '~> 1.0'
|
|
@@ -20,12 +20,14 @@ class Freddy
|
|
|
20
20
|
|
|
21
21
|
def initialize(bunny)
|
|
22
22
|
@bunny = bunny
|
|
23
|
+
@recovery_count = Concurrent::AtomicFixnum.new
|
|
24
|
+
@bunny.before_recovery_attempt_starts { @recovery_count.increment }
|
|
23
25
|
end
|
|
24
26
|
|
|
25
27
|
def create_channel(prefetch: nil)
|
|
26
28
|
bunny_channel = @bunny.create_channel
|
|
27
29
|
bunny_channel.prefetch(prefetch) if prefetch
|
|
28
|
-
Channel.new(bunny_channel)
|
|
30
|
+
Channel.new(bunny_channel, @recovery_count)
|
|
29
31
|
end
|
|
30
32
|
|
|
31
33
|
def close
|
|
@@ -37,14 +39,24 @@ class Freddy
|
|
|
37
39
|
|
|
38
40
|
NO_ROUTE = 312
|
|
39
41
|
|
|
40
|
-
def initialize(channel)
|
|
42
|
+
def initialize(channel, recovery_count)
|
|
41
43
|
@channel = channel
|
|
44
|
+
@recovery_count = recovery_count
|
|
42
45
|
end
|
|
43
46
|
|
|
44
|
-
def_delegators :@channel, :topic, :default_exchange, :consumers
|
|
47
|
+
def_delegators :@channel, :topic, :default_exchange, :consumers
|
|
45
48
|
|
|
46
49
|
def queue(*)
|
|
47
|
-
Queue.new(@channel.queue(*))
|
|
50
|
+
Queue.new(@channel.queue(*), @recovery_count)
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
# The server requeues deliveries from before a recovery; their stale tag would close this channel.
|
|
54
|
+
def acknowledge(delivery)
|
|
55
|
+
@channel.acknowledge(delivery.tag) if current?(delivery)
|
|
56
|
+
end
|
|
57
|
+
|
|
58
|
+
def reject(delivery, requeue: false)
|
|
59
|
+
@channel.reject(delivery.tag, requeue) if current?(delivery)
|
|
48
60
|
end
|
|
49
61
|
|
|
50
62
|
def on_no_route
|
|
@@ -52,11 +64,18 @@ class Freddy
|
|
|
52
64
|
yield(properties[:correlation_id]) if return_info[:reply_code] == NO_ROUTE
|
|
53
65
|
end
|
|
54
66
|
end
|
|
67
|
+
|
|
68
|
+
private
|
|
69
|
+
|
|
70
|
+
def current?(delivery)
|
|
71
|
+
delivery.recovery_count == @recovery_count.value && @channel.open?
|
|
72
|
+
end
|
|
55
73
|
end
|
|
56
74
|
|
|
57
75
|
class Queue
|
|
58
|
-
def initialize(queue)
|
|
76
|
+
def initialize(queue, recovery_count)
|
|
59
77
|
@queue = queue
|
|
78
|
+
@recovery_count = recovery_count
|
|
60
79
|
end
|
|
61
80
|
|
|
62
81
|
def bind(*)
|
|
@@ -76,7 +95,8 @@ class Freddy
|
|
|
76
95
|
@queue.subscribe(manual_ack: manual_ack) do |info, properties, payload|
|
|
77
96
|
parsed_payload = Payload.parse(payload, properties[:content_encoding])
|
|
78
97
|
delivery = Delivery.new(
|
|
79
|
-
parsed_payload, properties, info.routing_key, info.delivery_tag, info.exchange
|
|
98
|
+
parsed_payload, properties, info.routing_key, info.delivery_tag, info.exchange,
|
|
99
|
+
@recovery_count.value
|
|
80
100
|
)
|
|
81
101
|
yield(delivery)
|
|
82
102
|
end
|
|
@@ -47,16 +47,16 @@ class Freddy
|
|
|
47
47
|
@consume_thread_pool.post do
|
|
48
48
|
delivery.in_span do
|
|
49
49
|
yield delivery.payload, delivery.routing_key, delivery.timestamp
|
|
50
|
-
@channel.acknowledge(delivery
|
|
50
|
+
@channel.acknowledge(delivery)
|
|
51
51
|
end
|
|
52
52
|
rescue StandardError
|
|
53
53
|
case on_exception
|
|
54
54
|
when :reject
|
|
55
|
-
@channel.reject(delivery
|
|
55
|
+
@channel.reject(delivery)
|
|
56
56
|
when :requeue
|
|
57
|
-
@channel.reject(delivery
|
|
57
|
+
@channel.reject(delivery, requeue: true)
|
|
58
58
|
else
|
|
59
|
-
@channel.acknowledge(delivery
|
|
59
|
+
@channel.acknowledge(delivery)
|
|
60
60
|
end
|
|
61
61
|
|
|
62
62
|
raise
|
data/lib/freddy/delivery.rb
CHANGED
|
@@ -2,14 +2,15 @@
|
|
|
2
2
|
|
|
3
3
|
class Freddy
|
|
4
4
|
class Delivery
|
|
5
|
-
attr_reader :routing_key, :payload, :tag
|
|
5
|
+
attr_reader :routing_key, :payload, :tag, :recovery_count
|
|
6
6
|
|
|
7
|
-
def initialize(payload, metadata, routing_key, tag, exchange)
|
|
7
|
+
def initialize(payload, metadata, routing_key, tag, exchange, recovery_count) # rubocop:disable Metrics/ParameterLists
|
|
8
8
|
@payload = payload
|
|
9
9
|
@metadata = metadata
|
|
10
10
|
@routing_key = routing_key
|
|
11
11
|
@tag = tag
|
|
12
12
|
@exchange = exchange
|
|
13
|
+
@recovery_count = recovery_count
|
|
13
14
|
end
|
|
14
15
|
|
|
15
16
|
def correlation_id
|
data/lib/freddy/version.rb
CHANGED
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
require 'spec_helper'
|
|
2
|
+
|
|
3
|
+
describe Freddy::Adapters::BunnyAdapter::Channel do
|
|
4
|
+
let(:channel) { described_class.new(bunny_channel, recovery_count) }
|
|
5
|
+
let(:bunny_channel) { double(open?: channel_open, acknowledge: nil, reject: nil) }
|
|
6
|
+
let(:channel_open) { true }
|
|
7
|
+
let(:recovery_count) { Concurrent::AtomicFixnum.new }
|
|
8
|
+
let(:delivery) { Freddy::Delivery.new({}, {}, 'routing.key', 7, '', recovery_count.value) }
|
|
9
|
+
|
|
10
|
+
context 'when the delivery is from the current connection' do
|
|
11
|
+
it 'acknowledges it' do
|
|
12
|
+
channel.acknowledge(delivery)
|
|
13
|
+
|
|
14
|
+
expect(bunny_channel).to have_received(:acknowledge).with(7)
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
it 'rejects it' do
|
|
18
|
+
channel.reject(delivery, requeue: true)
|
|
19
|
+
|
|
20
|
+
expect(bunny_channel).to have_received(:reject).with(7, true)
|
|
21
|
+
end
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
context 'when the connection was recovered after the delivery' do
|
|
25
|
+
before do
|
|
26
|
+
delivery
|
|
27
|
+
recovery_count.increment
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
it 'does not acknowledge it' do
|
|
31
|
+
channel.acknowledge(delivery)
|
|
32
|
+
|
|
33
|
+
expect(bunny_channel).not_to have_received(:acknowledge)
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
it 'does not reject it' do
|
|
37
|
+
channel.reject(delivery)
|
|
38
|
+
|
|
39
|
+
expect(bunny_channel).not_to have_received(:reject)
|
|
40
|
+
end
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
context 'when the channel is closed' do
|
|
44
|
+
let(:channel_open) { false }
|
|
45
|
+
|
|
46
|
+
it 'does not acknowledge the delivery' do
|
|
47
|
+
channel.acknowledge(delivery)
|
|
48
|
+
|
|
49
|
+
expect(bunny_channel).not_to have_received(:acknowledge)
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
it 'does not reject the delivery' do
|
|
53
|
+
channel.reject(delivery)
|
|
54
|
+
|
|
55
|
+
expect(bunny_channel).not_to have_received(:reject)
|
|
56
|
+
end
|
|
57
|
+
end
|
|
58
|
+
end
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
require 'spec_helper'
|
|
2
|
+
|
|
3
|
+
describe 'Connection recovery' do
|
|
4
|
+
let(:freddy) { Freddy.build(logger, **config, network_recovery_interval: 0.5) }
|
|
5
|
+
let(:requester) { Freddy.build(logger, **config) }
|
|
6
|
+
let(:destination) { random_destination }
|
|
7
|
+
let(:bunny) { freddy.instance_variable_get(:@connection).instance_variable_get(:@bunny) }
|
|
8
|
+
|
|
9
|
+
after { [freddy, requester].each(&:close) }
|
|
10
|
+
|
|
11
|
+
it 'keeps responding when a message is processed across a recovery' do
|
|
12
|
+
processing = Queue.new
|
|
13
|
+
release = Queue.new
|
|
14
|
+
deliveries = Concurrent::AtomicFixnum.new
|
|
15
|
+
|
|
16
|
+
freddy.respond_to destination do |payload, msg_handler|
|
|
17
|
+
if deliveries.increment == 1
|
|
18
|
+
processing << true
|
|
19
|
+
release.pop
|
|
20
|
+
end
|
|
21
|
+
msg_handler.success(payload)
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
first_request = Thread.new { requester.deliver_with_response(destination, { id: 1 }, timeout: 10) }
|
|
25
|
+
processing.pop
|
|
26
|
+
|
|
27
|
+
bunny.transport.socket.shutdown(Socket::SHUT_RDWR)
|
|
28
|
+
wait_for_recovery
|
|
29
|
+
release << true
|
|
30
|
+
|
|
31
|
+
expect(first_request.value).to eq(id: 1)
|
|
32
|
+
expect(requester.deliver_with_response(destination, { id: 2 })).to eq(id: 2)
|
|
33
|
+
expect(requester.deliver_with_response(destination, { id: 3 })).to eq(id: 3)
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
def wait_for_recovery
|
|
37
|
+
sleep 0.1 while bunny.open?
|
|
38
|
+
sleep 0.1 until bunny.open?
|
|
39
|
+
sleep 0.5
|
|
40
|
+
end
|
|
41
|
+
end
|
metadata
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: freddy
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version:
|
|
4
|
+
version: 3.0.0
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Glia TechMovers
|
|
@@ -15,14 +15,14 @@ dependencies:
|
|
|
15
15
|
requirements:
|
|
16
16
|
- - "~>"
|
|
17
17
|
- !ruby/object:Gem::Version
|
|
18
|
-
version: '
|
|
18
|
+
version: '3.5'
|
|
19
19
|
type: :runtime
|
|
20
20
|
prerelease: false
|
|
21
21
|
version_requirements: !ruby/object:Gem::Requirement
|
|
22
22
|
requirements:
|
|
23
23
|
- - "~>"
|
|
24
24
|
- !ruby/object:Gem::Version
|
|
25
|
-
version: '
|
|
25
|
+
version: '3.5'
|
|
26
26
|
- !ruby/object:Gem::Dependency
|
|
27
27
|
name: concurrent-ruby
|
|
28
28
|
requirement: !ruby/object:Gem::Requirement
|
|
@@ -153,6 +153,7 @@ files:
|
|
|
153
153
|
- lib/freddy/tracing.rb
|
|
154
154
|
- lib/freddy/version.rb
|
|
155
155
|
- spec/.rubocop.yml
|
|
156
|
+
- spec/freddy/adapters/bunny_adapter/channel_spec.rb
|
|
156
157
|
- spec/freddy/consumers/respond_to_consumer_spec.rb
|
|
157
158
|
- spec/freddy/error_response_spec.rb
|
|
158
159
|
- spec/freddy/freddy_spec.rb
|
|
@@ -161,6 +162,7 @@ files:
|
|
|
161
162
|
- spec/freddy/responder_handler_spec.rb
|
|
162
163
|
- spec/freddy/sync_response_container_spec.rb
|
|
163
164
|
- spec/integration/concurrency_spec.rb
|
|
165
|
+
- spec/integration/connection_recovery_spec.rb
|
|
164
166
|
- spec/integration/reply_spec.rb
|
|
165
167
|
- spec/integration/tap_into_with_exchange_spec.rb
|
|
166
168
|
- spec/integration/tap_into_with_group_spec.rb
|