ears 0.19.0 → 0.21.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/build.yml +2 -2
- data/.rubocop.yml +2 -2
- data/.rubocop_todo.yml +1 -1
- data/.tool-versions +2 -2
- data/CHANGELOG.md +9 -0
- data/Gemfile.lock +57 -48
- data/README.md +265 -2
- data/ears.gemspec +2 -1
- data/lib/ears/configuration.rb +46 -0
- data/lib/ears/publisher.rb +108 -0
- data/lib/ears/publisher_channel_pool.rb +55 -0
- data/lib/ears/publisher_retry_handler.rb +104 -0
- data/lib/ears/version.rb +1 -1
- data/lib/ears.rb +4 -2
- data/package-lock.json +7 -7
- data/package.json +1 -1
- metadata +21 -4
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: a7786b24328603a3d9b231e06400194e69ab38b00de05ec0b0ee5424f0ec3a7b
|
4
|
+
data.tar.gz: 386cedcd1ed08364678da108e1c44e4142d5ba915478476b2d95b14afec3eff3
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 841d1b043e68ebbcf850fab367e770e145cf3519d15e33f613db18c41e8df72d2c33d0337b50ba3c18d866894defe32d4584230d78447192eb2ac71b6dea134f
|
7
|
+
data.tar.gz: 7a2bb015f0f4175499c07609cc0ab923af8ecd2157df7a6a57c5c1233c68cfc2d5813ac2452c571551efb83658bfbb5642cf4373bb0efde0a6e9cd88a0530b31
|
data/.github/workflows/build.yml
CHANGED
@@ -12,7 +12,7 @@ jobs:
|
|
12
12
|
runs-on: ubuntu-latest
|
13
13
|
strategy:
|
14
14
|
matrix:
|
15
|
-
ruby-version: ['3.
|
15
|
+
ruby-version: ['3.2', '3.3', '3.4']
|
16
16
|
|
17
17
|
steps:
|
18
18
|
- uses: actions/checkout@v4
|
@@ -23,7 +23,7 @@ jobs:
|
|
23
23
|
bundler-cache: true
|
24
24
|
- uses: actions/setup-node@v4
|
25
25
|
with:
|
26
|
-
node-version: '
|
26
|
+
node-version: '24'
|
27
27
|
- name: Run prettier
|
28
28
|
run: npm ci && npm run lint
|
29
29
|
- name: Run Rubocop
|
data/.rubocop.yml
CHANGED
@@ -13,7 +13,7 @@ AllCops:
|
|
13
13
|
- Guardfile
|
14
14
|
- bin/*
|
15
15
|
- tmp/**/*
|
16
|
-
TargetRubyVersion: 3.
|
16
|
+
TargetRubyVersion: 3.2
|
17
17
|
NewCops: enable
|
18
18
|
|
19
19
|
Style:
|
@@ -66,7 +66,7 @@ Metrics/ModuleLength:
|
|
66
66
|
- 'spec/**/*.rb'
|
67
67
|
- 'test/**/*.rb'
|
68
68
|
|
69
|
-
|
69
|
+
plugins:
|
70
70
|
- rubocop-rspec
|
71
71
|
- rubocop-rake
|
72
72
|
|
data/.rubocop_todo.yml
CHANGED
@@ -1,6 +1,6 @@
|
|
1
1
|
# This configuration was generated by
|
2
2
|
# `rubocop --auto-gen-config`
|
3
|
-
# on 2025-
|
3
|
+
# on 2025-06-02 12:38:15 UTC using RuboCop version 1.75.8.
|
4
4
|
# The point is for the user to remove these configuration records
|
5
5
|
# one by one as the offenses are removed from the code base.
|
6
6
|
# Note that changes in the inspected code, or installation of new
|
data/.tool-versions
CHANGED
@@ -1,2 +1,2 @@
|
|
1
|
-
ruby 3.
|
2
|
-
nodejs
|
1
|
+
ruby 3.2.9
|
2
|
+
nodejs 24.4.1
|
data/CHANGELOG.md
CHANGED
@@ -1,5 +1,14 @@
|
|
1
1
|
# Changelog
|
2
2
|
|
3
|
+
## 0.21.0 (2025-09-08)
|
4
|
+
|
5
|
+
- Introduce Ears::Publisher with thread-safe channel pooling
|
6
|
+
- Introduce configurable logger
|
7
|
+
|
8
|
+
## 0.20.0 (2025-06-02)
|
9
|
+
|
10
|
+
- Drop support for Ruby 3.1
|
11
|
+
|
3
12
|
## 0.19.0 (2025-03-10)
|
4
13
|
|
5
14
|
- Enhance Consumer#configure method and Ears.setup_consumers to accept `prefetch` option, which will be passed to Ears::Setup.
|
data/Gemfile.lock
CHANGED
@@ -1,48 +1,51 @@
|
|
1
1
|
PATH
|
2
2
|
remote: .
|
3
3
|
specs:
|
4
|
-
ears (0.
|
4
|
+
ears (0.21.0)
|
5
5
|
bunny (>= 2.22.0)
|
6
|
+
connection_pool (~> 2.4)
|
6
7
|
multi_json
|
7
8
|
|
8
9
|
GEM
|
9
10
|
remote: https://rubygems.org/
|
10
11
|
specs:
|
11
|
-
amq-protocol (2.3.
|
12
|
-
ast (2.4.
|
13
|
-
bunny (2.
|
14
|
-
amq-protocol (~> 2.3
|
12
|
+
amq-protocol (2.3.4)
|
13
|
+
ast (2.4.3)
|
14
|
+
bunny (2.24.0)
|
15
|
+
amq-protocol (~> 2.3)
|
15
16
|
sorted_set (~> 1, >= 1.0.2)
|
16
|
-
|
17
|
+
connection_pool (2.5.3)
|
18
|
+
diff-lcs (1.6.2)
|
17
19
|
docile (1.4.1)
|
18
|
-
json (2.
|
19
|
-
language_server-protocol (3.17.0.
|
20
|
+
json (2.13.2)
|
21
|
+
language_server-protocol (3.17.0.5)
|
20
22
|
lint_roller (1.1.0)
|
21
|
-
multi_json (1.
|
22
|
-
parallel (1.
|
23
|
-
parser (3.3.
|
23
|
+
multi_json (1.17.0)
|
24
|
+
parallel (1.27.0)
|
25
|
+
parser (3.3.9.0)
|
24
26
|
ast (~> 2.4.1)
|
25
27
|
racc
|
26
28
|
prettier_print (1.2.1)
|
29
|
+
prism (1.4.0)
|
27
30
|
racc (1.8.1)
|
28
31
|
rainbow (3.1.1)
|
29
|
-
rake (13.
|
32
|
+
rake (13.3.0)
|
30
33
|
rbtree (0.4.6)
|
31
34
|
regexp_parser (2.10.0)
|
32
|
-
rspec (3.13.
|
35
|
+
rspec (3.13.1)
|
33
36
|
rspec-core (~> 3.13.0)
|
34
37
|
rspec-expectations (~> 3.13.0)
|
35
38
|
rspec-mocks (~> 3.13.0)
|
36
|
-
rspec-core (3.13.
|
39
|
+
rspec-core (3.13.5)
|
37
40
|
rspec-support (~> 3.13.0)
|
38
|
-
rspec-expectations (3.13.
|
41
|
+
rspec-expectations (3.13.5)
|
39
42
|
diff-lcs (>= 1.2.0, < 2.0)
|
40
43
|
rspec-support (~> 3.13.0)
|
41
|
-
rspec-mocks (3.13.
|
44
|
+
rspec-mocks (3.13.5)
|
42
45
|
diff-lcs (>= 1.2.0, < 2.0)
|
43
46
|
rspec-support (~> 3.13.0)
|
44
|
-
rspec-support (3.13.
|
45
|
-
rubocop (1.
|
47
|
+
rspec-support (3.13.4)
|
48
|
+
rubocop (1.79.0)
|
46
49
|
json (~> 2.3)
|
47
50
|
language_server-protocol (~> 3.17.0.2)
|
48
51
|
lint_roller (~> 1.1.0)
|
@@ -50,30 +53,33 @@ GEM
|
|
50
53
|
parser (>= 3.3.0.2)
|
51
54
|
rainbow (>= 2.2.2, < 4.0)
|
52
55
|
regexp_parser (>= 2.9.3, < 3.0)
|
53
|
-
rubocop-ast (>= 1.
|
56
|
+
rubocop-ast (>= 1.46.0, < 2.0)
|
54
57
|
ruby-progressbar (~> 1.7)
|
58
|
+
tsort (>= 0.2.0)
|
55
59
|
unicode-display_width (>= 2.4.0, < 4.0)
|
56
|
-
rubocop-ast (1.
|
57
|
-
parser (>= 3.3.
|
60
|
+
rubocop-ast (1.46.0)
|
61
|
+
parser (>= 3.3.7.2)
|
62
|
+
prism (~> 1.4)
|
58
63
|
rubocop-rake (0.7.1)
|
59
64
|
lint_roller (~> 1.1)
|
60
65
|
rubocop (>= 1.72.1)
|
61
|
-
rubocop-rspec (3.
|
66
|
+
rubocop-rspec (3.6.0)
|
62
67
|
lint_roller (~> 1.1)
|
63
68
|
rubocop (~> 1.72, >= 1.72.1)
|
64
69
|
ruby-progressbar (1.13.0)
|
65
|
-
set (1.1.
|
70
|
+
set (1.1.2)
|
66
71
|
simplecov (0.22.0)
|
67
72
|
docile (~> 1.1)
|
68
73
|
simplecov-html (~> 0.11)
|
69
74
|
simplecov_json_formatter (~> 0.1)
|
70
|
-
simplecov-html (0.13.
|
75
|
+
simplecov-html (0.13.2)
|
71
76
|
simplecov_json_formatter (0.1.4)
|
72
77
|
sorted_set (1.0.3)
|
73
78
|
rbtree
|
74
79
|
set (~> 1.0)
|
75
|
-
syntax_tree (6.
|
80
|
+
syntax_tree (6.3.0)
|
76
81
|
prettier_print (>= 1.2.0)
|
82
|
+
tsort (0.2.0)
|
77
83
|
unicode-display_width (3.1.4)
|
78
84
|
unicode-emoji (~> 4.0, >= 4.0.4)
|
79
85
|
unicode-emoji (4.0.4)
|
@@ -101,43 +107,46 @@ DEPENDENCIES
|
|
101
107
|
yard
|
102
108
|
|
103
109
|
CHECKSUMS
|
104
|
-
amq-protocol (2.3.
|
105
|
-
ast (2.4.
|
106
|
-
bunny (2.
|
107
|
-
|
110
|
+
amq-protocol (2.3.4) sha256=98be5b9244e28dc66acc8351a254dbf45d996c5a0b7d49ab3ff8b72b0d2e6308
|
111
|
+
ast (2.4.3) sha256=954615157c1d6a382bc27d690d973195e79db7f55e9765ac7c481c60bdb4d383
|
112
|
+
bunny (2.24.0) sha256=072fe4ae98eaa9c95a17e4d166204f710bba8a9a7070b73a8c3b023f439d1682
|
113
|
+
connection_pool (2.5.3) sha256=cfd74a82b9b094d1ce30c4f1a346da23ee19dc8a062a16a85f58eab1ced4305b
|
114
|
+
diff-lcs (1.6.2) sha256=9ae0d2cba7d4df3075fe8cd8602a8604993efc0dfa934cff568969efb1909962
|
108
115
|
docile (1.4.1) sha256=96159be799bfa73cdb721b840e9802126e4e03dfc26863db73647204c727f21e
|
109
|
-
ears (0.
|
110
|
-
json (2.
|
111
|
-
language_server-protocol (3.17.0.
|
116
|
+
ears (0.21.0)
|
117
|
+
json (2.13.2) sha256=02e1f118d434c6b230a64ffa5c8dee07e3ec96244335c392eaed39e1199dbb68
|
118
|
+
language_server-protocol (3.17.0.5) sha256=fd1e39a51a28bf3eec959379985a72e296e9f9acfce46f6a79d31ca8760803cc
|
112
119
|
lint_roller (1.1.0) sha256=2c0c845b632a7d172cb849cc90c1bce937a28c5c8ccccb50dfd46a485003cc87
|
113
|
-
multi_json (1.
|
114
|
-
parallel (1.
|
115
|
-
parser (3.3.
|
120
|
+
multi_json (1.17.0) sha256=76581f6c96aebf2e85f8a8b9854829e0988f335e8671cd1a56a1036eb75e4a1b
|
121
|
+
parallel (1.27.0) sha256=4ac151e1806b755fb4e2dc2332cbf0e54f2e24ba821ff2d3dcf86bf6dc4ae130
|
122
|
+
parser (3.3.9.0) sha256=94d6929354b1a6e3e1f89d79d4d302cc8f5aa814431a6c9c7e0623335d7687f2
|
116
123
|
prettier_print (1.2.1) sha256=a72838b5f23facff21f90a5423cdcdda19e4271092b41f4ea7f50b83929e6ff9
|
124
|
+
prism (1.4.0) sha256=dc0e3e00e93160213dc2a65519d9002a4a1e7b962db57d444cf1a71565bb703e
|
117
125
|
racc (1.8.1) sha256=4a7f6929691dbec8b5209a0b373bc2614882b55fc5d2e447a21aaa691303d62f
|
118
126
|
rainbow (3.1.1) sha256=039491aa3a89f42efa1d6dec2fc4e62ede96eb6acd95e52f1ad581182b79bc6a
|
119
|
-
rake (13.
|
127
|
+
rake (13.3.0) sha256=96f5092d786ff412c62fde76f793cc0541bd84d2eb579caa529aa8a059934493
|
120
128
|
rbtree (0.4.6) sha256=14eea4469b24fd2472542e5f3eb105d6344c8ccf36f0b56d55fdcfeb4e0f10fc
|
121
129
|
regexp_parser (2.10.0) sha256=cb6f0ddde88772cd64bff1dbbf68df66d376043fe2e66a9ef77fcb1b0c548c61
|
122
|
-
rspec (3.13.
|
123
|
-
rspec-core (3.13.
|
124
|
-
rspec-expectations (3.13.
|
125
|
-
rspec-mocks (3.13.
|
126
|
-
rspec-support (3.13.
|
127
|
-
rubocop (1.
|
128
|
-
rubocop-ast (1.
|
130
|
+
rspec (3.13.1) sha256=b9f9a58fa915b8d94a1d6b3195fe6dd28c4c34836a6097015142c4a9ace72140
|
131
|
+
rspec-core (3.13.5) sha256=ab3f682897c6131c67f9a17cfee5022a597f283aebe654d329a565f9937a4fa3
|
132
|
+
rspec-expectations (3.13.5) sha256=33a4d3a1d95060aea4c94e9f237030a8f9eae5615e9bd85718fe3a09e4b58836
|
133
|
+
rspec-mocks (3.13.5) sha256=e4338a6f285ada9fe56f5893f5457783af8194f5d08884d17a87321d5195ea81
|
134
|
+
rspec-support (3.13.4) sha256=184b1814f6a968102b57df631892c7f1990a91c9a3b9e80ef892a0fc2a71a3f7
|
135
|
+
rubocop (1.79.0) sha256=c709e83b16f9fced295d83d190a3a5bbcc46c419d8f9b85f259b99ba6faf5bbe
|
136
|
+
rubocop-ast (1.46.0) sha256=0da7f6ad5b98614f89b74f11873c191059c823eae07d6ffd40a42a3338f2232b
|
129
137
|
rubocop-rake (0.7.1) sha256=3797f2b6810c3e9df7376c26d5f44f3475eda59eb1adc38e6f62ecf027cbae4d
|
130
|
-
rubocop-rspec (3.
|
138
|
+
rubocop-rspec (3.6.0) sha256=c0e4205871776727e54dee9cc91af5fd74578001551ba40e1fe1a1ab4b404479
|
131
139
|
ruby-progressbar (1.13.0) sha256=80fc9c47a9b640d6834e0dc7b3c94c9df37f08cb072b7761e4a71e22cff29b33
|
132
|
-
set (1.1.
|
140
|
+
set (1.1.2) sha256=ca33a60d202e788041d94a5d4c12315b1639875576f1a266f3a10913646d8ef1
|
133
141
|
simplecov (0.22.0) sha256=fe2622c7834ff23b98066bb0a854284b2729a569ac659f82621fc22ef36213a5
|
134
|
-
simplecov-html (0.13.
|
142
|
+
simplecov-html (0.13.2) sha256=bd0b8e54e7c2d7685927e8d6286466359b6f16b18cb0df47b508e8d73c777246
|
135
143
|
simplecov_json_formatter (0.1.4) sha256=529418fbe8de1713ac2b2d612aa3daa56d316975d307244399fa4838c601b428
|
136
144
|
sorted_set (1.0.3) sha256=4f2b8bee6e8c59cbd296228c0f1f81679357177a8b6859dcc2a99e86cce6372f
|
137
|
-
syntax_tree (6.
|
145
|
+
syntax_tree (6.3.0) sha256=56e25a9692c798ec94c5442fe94c5e94af76bef91edc8bb02052cbdecf35f13d
|
146
|
+
tsort (0.2.0) sha256=9650a793f6859a43b6641671278f79cfead60ac714148aabe4e3f0060480089f
|
138
147
|
unicode-display_width (3.1.4) sha256=8caf2af1c0f2f07ec89ef9e18c7d88c2790e217c482bfc78aaa65eadd5415ac1
|
139
148
|
unicode-emoji (4.0.4) sha256=2c2c4ef7f353e5809497126285a50b23056cc6e61b64433764a35eff6c36532a
|
140
149
|
yard (0.9.37) sha256=a6e910399e78e613f80ba9add9ba7c394b1a935f083cccbef82903a3d2a26992
|
141
150
|
|
142
151
|
BUNDLED WITH
|
143
|
-
2.
|
152
|
+
2.7.1
|
data/README.md
CHANGED
@@ -1,6 +1,6 @@
|
|
1
1
|
# Ears
|
2
2
|
|
3
|
-
`Ears` is a small, simple library for writing RabbitMQ consumers.
|
3
|
+
`Ears` is a small, simple library for writing RabbitMQ consumers and publishers.
|
4
4
|
|
5
5
|
[](https://github.com/ivx/ears/actions/workflows/codeql.yml)
|
6
6
|
|
@@ -22,7 +22,182 @@ Or install it yourself as:
|
|
22
22
|
|
23
23
|
## Usage
|
24
24
|
|
25
|
-
###
|
25
|
+
### Publishing Messages
|
26
|
+
|
27
|
+
`Ears` provides a thread-safe publisher for sending messages to RabbitMQ exchanges with automatic retry and connection recovery capabilities.
|
28
|
+
|
29
|
+
#### Basic Publisher Usage
|
30
|
+
|
31
|
+
To publish messages, create an `Ears::Publisher` instance and call `publish`:
|
32
|
+
|
33
|
+
```ruby
|
34
|
+
require 'ears'
|
35
|
+
|
36
|
+
# Configure Ears (same configuration is shared by consumers and publishers)
|
37
|
+
Ears.configure do |config|
|
38
|
+
config.rabbitmq_url = 'amqp://user:password@myrmq:5672'
|
39
|
+
config.connection_name = 'My Publisher'
|
40
|
+
end
|
41
|
+
|
42
|
+
# Create a publisher for a topic exchange
|
43
|
+
publisher = Ears::Publisher.new('my_exchange', :topic, durable: true)
|
44
|
+
|
45
|
+
# Publish a message
|
46
|
+
data = { user_id: 123, action: 'login', timestamp: Time.now.iso8601 }
|
47
|
+
publisher.publish(data, routing_key: 'user.login')
|
48
|
+
```
|
49
|
+
|
50
|
+
#### Exchange Types and Options
|
51
|
+
|
52
|
+
Publishers support all RabbitMQ exchange types:
|
53
|
+
|
54
|
+
```ruby
|
55
|
+
# Topic exchange (default)
|
56
|
+
topic_publisher = Ears::Publisher.new('events', :topic)
|
57
|
+
|
58
|
+
# Direct exchange
|
59
|
+
direct_publisher = Ears::Publisher.new('commands', :direct)
|
60
|
+
|
61
|
+
# Fanout exchange
|
62
|
+
fanout_publisher = Ears::Publisher.new('broadcasts', :fanout)
|
63
|
+
|
64
|
+
# Headers exchange
|
65
|
+
headers_publisher = Ears::Publisher.new('complex_routing', :headers)
|
66
|
+
|
67
|
+
# Custom exchange options
|
68
|
+
publisher =
|
69
|
+
Ears::Publisher.new(
|
70
|
+
'my_exchange',
|
71
|
+
:topic,
|
72
|
+
durable: true,
|
73
|
+
auto_delete: false,
|
74
|
+
arguments: {
|
75
|
+
'x-message-ttl' => 60_000,
|
76
|
+
},
|
77
|
+
)
|
78
|
+
```
|
79
|
+
|
80
|
+
#### Message Options
|
81
|
+
|
82
|
+
The `publish` method accepts various message options:
|
83
|
+
|
84
|
+
```ruby
|
85
|
+
publisher.publish(
|
86
|
+
{ message: 'Hello World' },
|
87
|
+
routing_key: 'greeting.hello',
|
88
|
+
persistent: true, # Persist message to disk (default: true)
|
89
|
+
headers: {
|
90
|
+
version: '1.0',
|
91
|
+
}, # Custom headers
|
92
|
+
timestamp: Time.now.to_i, # Message timestamp (default: current time)
|
93
|
+
message_id: SecureRandom.uuid, # Unique message identifier
|
94
|
+
correlation_id: 'abc-123', # Correlation ID for request/response patterns
|
95
|
+
reply_to: 'response_queue', # Queue for responses
|
96
|
+
expiration: '60000', # Message TTL in milliseconds
|
97
|
+
priority: 5, # Message priority (0-9)
|
98
|
+
type: 'user_event', # Message type
|
99
|
+
app_id: 'my_application', # Application identifier
|
100
|
+
user_id: 'system', # User identifier (verified by RabbitMQ)
|
101
|
+
)
|
102
|
+
```
|
103
|
+
|
104
|
+
#### Thread-Safe Publishing
|
105
|
+
|
106
|
+
Publishers use a connection pool for thread-safe operation, making them suitable for concurrent use:
|
107
|
+
|
108
|
+
```ruby
|
109
|
+
# Single publisher can be safely used across multiple threads
|
110
|
+
publisher = Ears::Publisher.new('events', :topic)
|
111
|
+
|
112
|
+
# Example with multiple threads
|
113
|
+
threads =
|
114
|
+
10.times.map do |i|
|
115
|
+
Thread.new do
|
116
|
+
100.times do |j|
|
117
|
+
publisher.publish({ thread: i, message: j }, routing_key: "thread.#{i}")
|
118
|
+
end
|
119
|
+
end
|
120
|
+
end
|
121
|
+
|
122
|
+
threads.each(&:join)
|
123
|
+
```
|
124
|
+
|
125
|
+
#### Publisher Configuration
|
126
|
+
|
127
|
+
Publisher behavior can be fine-tuned through configuration options:
|
128
|
+
|
129
|
+
```ruby
|
130
|
+
Ears.configure do |config|
|
131
|
+
# Connection settings
|
132
|
+
config.rabbitmq_url = 'amqp://user:password@myrmq:5672'
|
133
|
+
config.connection_name = 'My Application'
|
134
|
+
|
135
|
+
# Publisher-specific settings
|
136
|
+
config.publisher_pool_size = 32 # Channel pool size (default: 32)
|
137
|
+
config.publisher_pool_timeout = 2 # Pool checkout timeout in seconds (default: 2)
|
138
|
+
|
139
|
+
# Connection retry settings
|
140
|
+
config.publisher_connection_attempts = 30 # Connection retry attempts (default: 30)
|
141
|
+
config.publisher_connection_base_delay = 1 # Base delay between connection attempts (default: 1s)
|
142
|
+
config.publisher_connection_backoff_factor = 1.5 # Connection backoff multiplier (default: 1.5)
|
143
|
+
|
144
|
+
# Publish retry settings
|
145
|
+
config.publisher_max_retries = 3 # Max publish retry attempts (default: 3)
|
146
|
+
config.publisher_retry_base_delay = 0.1 # Base delay between publish retries (default: 0.1s)
|
147
|
+
config.publisher_retry_backoff_factor = 2 # Publish retry backoff multiplier (default: 2)
|
148
|
+
end
|
149
|
+
```
|
150
|
+
|
151
|
+
#### Fault Tolerance and Recovery
|
152
|
+
|
153
|
+
Publishers automatically handle connection failures and provide several recovery mechanisms:
|
154
|
+
|
155
|
+
##### Automatic Retry with Exponential Backoff
|
156
|
+
|
157
|
+
```ruby
|
158
|
+
# Publishers automatically retry failed operations
|
159
|
+
publisher = Ears::Publisher.new('events', :topic)
|
160
|
+
|
161
|
+
# This will automatically retry with exponential backoff if the connection fails
|
162
|
+
publisher.publish({ event: 'user_signup' }, routing_key: 'user.signup')
|
163
|
+
```
|
164
|
+
|
165
|
+
##### Manual Recovery
|
166
|
+
|
167
|
+
If you need to manually reset the connection pool (e.g., after detecting connection issues):
|
168
|
+
|
169
|
+
```ruby
|
170
|
+
publisher = Ears::Publisher.new('events', :topic)
|
171
|
+
|
172
|
+
# Reset the channel pool to force new connections
|
173
|
+
publisher.reset!
|
174
|
+
|
175
|
+
# Subsequent publishes will use fresh channels
|
176
|
+
publisher.publish({ event: 'recovery_test' }, routing_key: 'system.recovery')
|
177
|
+
```
|
178
|
+
|
179
|
+
##### Error Handling
|
180
|
+
|
181
|
+
Publishers raise specific exceptions that you can handle:
|
182
|
+
|
183
|
+
```ruby
|
184
|
+
require 'ears'
|
185
|
+
|
186
|
+
publisher = Ears::Publisher.new('events', :topic)
|
187
|
+
|
188
|
+
begin
|
189
|
+
publisher.publish({ data: 'test' }, routing_key: 'test.message')
|
190
|
+
rescue Ears::PublisherRetryHandler::PublishError => e
|
191
|
+
# Handle publish failures (after all retries exhausted)
|
192
|
+
logger.error "Failed to publish message: #{e.message}"
|
193
|
+
# Consider queuing message for later retry or alerting
|
194
|
+
rescue => e
|
195
|
+
# Handle other unexpected errors
|
196
|
+
logger.error "Unexpected error: #{e.message}"
|
197
|
+
end
|
198
|
+
```
|
199
|
+
|
200
|
+
### Basic consumer usage
|
26
201
|
|
27
202
|
First, you should configure `Ears`.
|
28
203
|
|
@@ -34,6 +209,16 @@ Ears.configure do |config|
|
|
34
209
|
config.connection_name = 'My Consumer'
|
35
210
|
config.recover_from_connection_close = false # optional configuration, defaults to true if not set
|
36
211
|
config.recovery_attempts = 3 # optional configuration, defaults to 10, Bunny::Session would have been nil
|
212
|
+
|
213
|
+
# Publisher configuration (optional)
|
214
|
+
config.publisher_pool_size = 32 # Thread pool size for publishers (default: 32)
|
215
|
+
config.publisher_pool_timeout = 2 # Timeout for pool checkout in seconds (default: 2)
|
216
|
+
config.publisher_connection_attempts = 30 # Connection retry attempts (default: 30)
|
217
|
+
config.publisher_connection_base_delay = 1 # Base delay between connection attempts in seconds (default: 1)
|
218
|
+
config.publisher_connection_backoff_factor = 1.5 # Connection retry backoff multiplier (default: 1.5)
|
219
|
+
config.publisher_max_retries = 3 # Max publish retry attempts (default: 3)
|
220
|
+
config.publisher_retry_base_delay = 0.1 # Base delay between publish retries in seconds (default: 0.1)
|
221
|
+
config.publisher_retry_backoff_factor = 2 # Publish retry backoff multiplier (default: 2)
|
37
222
|
end
|
38
223
|
```
|
39
224
|
|
@@ -285,6 +470,84 @@ Ears.stop!
|
|
285
470
|
|
286
471
|
It will close and reset the current Bunny connection, leading to all consumers being shut down. Also, it will reset the channel.
|
287
472
|
|
473
|
+
### Complete Example: Consumer and Publisher
|
474
|
+
|
475
|
+
Here's a complete example showing both consumer and publisher usage:
|
476
|
+
|
477
|
+
```ruby
|
478
|
+
require 'ears'
|
479
|
+
|
480
|
+
# Shared configuration
|
481
|
+
Ears.configure do |config|
|
482
|
+
config.rabbitmq_url = 'amqp://guest:guest@localhost:5672'
|
483
|
+
config.connection_name = 'Order Processing Service'
|
484
|
+
config.publisher_pool_size = 16
|
485
|
+
end
|
486
|
+
|
487
|
+
# Consumer that processes orders and publishes events
|
488
|
+
class OrderProcessor < Ears::Consumer
|
489
|
+
configure(
|
490
|
+
queue: 'orders',
|
491
|
+
exchange: 'ecommerce',
|
492
|
+
routing_keys: %w[order.created order.updated],
|
493
|
+
retry_queue: true,
|
494
|
+
error_queue: true,
|
495
|
+
)
|
496
|
+
|
497
|
+
def initialize
|
498
|
+
super
|
499
|
+
@event_publisher = Ears::Publisher.new('events', :topic, durable: true)
|
500
|
+
end
|
501
|
+
|
502
|
+
def work(delivery_info, metadata, payload)
|
503
|
+
order = JSON.parse(payload)
|
504
|
+
|
505
|
+
# Process the order
|
506
|
+
process_order(order)
|
507
|
+
|
508
|
+
# Publish success event
|
509
|
+
@event_publisher.publish(
|
510
|
+
{
|
511
|
+
order_id: order['id'],
|
512
|
+
status: 'processed',
|
513
|
+
processed_at: Time.now.iso8601,
|
514
|
+
},
|
515
|
+
routing_key: 'order.processed',
|
516
|
+
)
|
517
|
+
|
518
|
+
ack
|
519
|
+
rescue => error
|
520
|
+
# Publish error event
|
521
|
+
@event_publisher.publish(
|
522
|
+
{
|
523
|
+
order_id: order&.dig('id'),
|
524
|
+
error: error.message,
|
525
|
+
failed_at: Time.now.iso8601,
|
526
|
+
},
|
527
|
+
routing_key: 'order.failed',
|
528
|
+
)
|
529
|
+
|
530
|
+
reject # Send to error queue
|
531
|
+
end
|
532
|
+
|
533
|
+
private
|
534
|
+
|
535
|
+
def process_order(order)
|
536
|
+
# Order processing logic here
|
537
|
+
sleep(0.1) # Simulate processing time
|
538
|
+
end
|
539
|
+
end
|
540
|
+
|
541
|
+
# Setup and run
|
542
|
+
Ears.setup { Ears.setup_consumers(OrderProcessor) }
|
543
|
+
|
544
|
+
begin
|
545
|
+
Ears.run!
|
546
|
+
ensure
|
547
|
+
# Cleanup code here
|
548
|
+
end
|
549
|
+
```
|
550
|
+
|
288
551
|
## Documentation
|
289
552
|
|
290
553
|
If you need more in-depth information, look at [our API documentation](https://www.rubydoc.info/gems/ears).
|
data/ears.gemspec
CHANGED
@@ -10,7 +10,7 @@ Gem::Specification.new do |spec|
|
|
10
10
|
spec.description = 'A gem for building RabbitMQ consumers.'
|
11
11
|
spec.homepage = 'https://github.com/ivx/ears'
|
12
12
|
spec.license = 'MIT'
|
13
|
-
spec.required_ruby_version = Gem::Requirement.new('>= 3.
|
13
|
+
spec.required_ruby_version = Gem::Requirement.new('>= 3.2.9')
|
14
14
|
|
15
15
|
spec.metadata['allowed_push_host'] = 'https://rubygems.org'
|
16
16
|
|
@@ -33,5 +33,6 @@ Gem::Specification.new do |spec|
|
|
33
33
|
spec.require_paths = ['lib']
|
34
34
|
|
35
35
|
spec.add_dependency 'bunny', '>= 2.22.0'
|
36
|
+
spec.add_dependency 'connection_pool', '~> 2.4'
|
36
37
|
spec.add_dependency 'multi_json'
|
37
38
|
end
|
data/lib/ears/configuration.rb
CHANGED
@@ -1,4 +1,5 @@
|
|
1
1
|
require 'ears/errors'
|
2
|
+
require 'logger'
|
2
3
|
|
3
4
|
module Ears
|
4
5
|
# The class representing the global {Ears} configuration.
|
@@ -8,6 +9,14 @@ module Ears
|
|
8
9
|
|
9
10
|
DEFAULT_RABBITMQ_URL = 'amqp://guest:guest@localhost:5672'
|
10
11
|
DEFAULT_RECOVERY_ATTEMPTS = 10
|
12
|
+
DEFAULT_PUBLISHER_POOL_SIZE = 32
|
13
|
+
DEFAULT_PUBLISHER_POOL_TIMEOUT = 2
|
14
|
+
DEFAULT_PUBLISHER_CONNECTION_ATTEMPTS = 30
|
15
|
+
DEFAULT_PUBLISHER_CONNECTION_BASE_DELAY = 1
|
16
|
+
DEFAULT_PUBLISHER_CONNECTION_BACKOFF_FACTOR = 1.5
|
17
|
+
DEFAULT_PUBLISHER_MAX_RETRIES = 3
|
18
|
+
DEFAULT_PUBLISHER_RETRY_BASE_DELAY = 0.1
|
19
|
+
DEFAULT_PUBLISHER_RETRY_BACKOFF_FACTOR = 2
|
11
20
|
|
12
21
|
# @return [String] the connection string for RabbitMQ.
|
13
22
|
attr_accessor :rabbitmq_url
|
@@ -21,9 +30,46 @@ module Ears
|
|
21
30
|
# @return [Integer] max number of recovery attempts, nil means forever
|
22
31
|
attr_accessor :recovery_attempts
|
23
32
|
|
33
|
+
# @return [Integer] the size of the publisher channel pool
|
34
|
+
attr_accessor :publisher_pool_size
|
35
|
+
|
36
|
+
# @return [Integer] the timeout in seconds for acquiring a channel from the publisher pool
|
37
|
+
attr_accessor :publisher_pool_timeout
|
38
|
+
|
39
|
+
# @return [Integer] the number of connection attempts for the publisher
|
40
|
+
attr_accessor :publisher_connection_attempts
|
41
|
+
|
42
|
+
# @return [Float] the base delay in seconds between connection attempts
|
43
|
+
attr_accessor :publisher_connection_base_delay
|
44
|
+
|
45
|
+
# @return [Float] the backoff factor for exponential connection delays
|
46
|
+
attr_accessor :publisher_connection_backoff_factor
|
47
|
+
|
48
|
+
# @return [Integer] the maximum number of retries for failed publish attempts
|
49
|
+
attr_accessor :publisher_max_retries
|
50
|
+
|
51
|
+
# @return [Float] the base delay in seconds between retry attempts
|
52
|
+
attr_accessor :publisher_retry_base_delay
|
53
|
+
|
54
|
+
# @return [Float] the backoff factor for exponential retry delays
|
55
|
+
attr_accessor :publisher_retry_backoff_factor
|
56
|
+
|
57
|
+
# @return [Logger] the logger instance for Ears operations
|
58
|
+
attr_accessor :logger
|
59
|
+
|
24
60
|
def initialize
|
25
61
|
@rabbitmq_url = DEFAULT_RABBITMQ_URL
|
26
62
|
@recovery_attempts = DEFAULT_RECOVERY_ATTEMPTS
|
63
|
+
@publisher_pool_size = DEFAULT_PUBLISHER_POOL_SIZE
|
64
|
+
@publisher_pool_timeout = DEFAULT_PUBLISHER_POOL_TIMEOUT
|
65
|
+
@publisher_connection_attempts = DEFAULT_PUBLISHER_CONNECTION_ATTEMPTS
|
66
|
+
@publisher_connection_base_delay = DEFAULT_PUBLISHER_CONNECTION_BASE_DELAY
|
67
|
+
@publisher_connection_backoff_factor =
|
68
|
+
DEFAULT_PUBLISHER_CONNECTION_BACKOFF_FACTOR
|
69
|
+
@publisher_max_retries = DEFAULT_PUBLISHER_MAX_RETRIES
|
70
|
+
@publisher_retry_base_delay = DEFAULT_PUBLISHER_RETRY_BASE_DELAY
|
71
|
+
@publisher_retry_backoff_factor = DEFAULT_PUBLISHER_RETRY_BACKOFF_FACTOR
|
72
|
+
@logger = Logger.new(IO::NULL)
|
27
73
|
end
|
28
74
|
|
29
75
|
# @return [Proc] that is passed to Bunny’s recovery_attempts_exhausted block. Nil if recovery_attempts is nil.
|
@@ -0,0 +1,108 @@
|
|
1
|
+
require 'bunny'
|
2
|
+
require 'ears/publisher_channel_pool'
|
3
|
+
require 'ears/publisher_retry_handler'
|
4
|
+
|
5
|
+
module Ears
|
6
|
+
# Publisher for sending messages to RabbitMQ exchanges.
|
7
|
+
#
|
8
|
+
# Uses a connection pool for thread-safe publishing with configurable pool size.
|
9
|
+
# This provides better performance and thread safety compared to using per-thread channels.
|
10
|
+
class Publisher
|
11
|
+
# Creates a new publisher for the specified exchange.
|
12
|
+
#
|
13
|
+
# @param [String] exchange_name The name of the exchange to publish to.
|
14
|
+
# @param [Symbol] exchange_type The type of the exchange (:direct, :fanout, :topic or :headers).
|
15
|
+
# @param [Hash] exchange_options The options for the exchange. These are passed on to +Bunny::Exchange.new+.
|
16
|
+
def initialize(exchange_name, exchange_type = :topic, exchange_options = {})
|
17
|
+
@exchange_name = exchange_name
|
18
|
+
@exchange_type = exchange_type
|
19
|
+
@exchange_options = { durable: true }.merge(exchange_options)
|
20
|
+
@config = Ears.configuration
|
21
|
+
@logger = Ears.configuration.logger
|
22
|
+
end
|
23
|
+
|
24
|
+
# Publishes a JSON message to the configured exchange.
|
25
|
+
#
|
26
|
+
# @param [Hash, Array, Object] data The data to serialize as JSON and publish.
|
27
|
+
# @param [String] routing_key The routing key for the message.
|
28
|
+
#
|
29
|
+
# @option opts [String] :routing_key Routing key
|
30
|
+
# @option opts [Boolean] :persistent Should the message be persisted to disk?
|
31
|
+
# @option opts [Boolean] :mandatory Should the message be returned if it cannot be routed to any queue?
|
32
|
+
# @option opts [Integer] :timestamp A timestamp associated with this message
|
33
|
+
# @option opts [Integer] :expiration Expiration time after which the message will be deleted
|
34
|
+
# @option opts [String] :type Message type, e.g. what type of event or command this message represents. Can be any string
|
35
|
+
# @option opts [String] :reply_to Queue name other apps should send the response to
|
36
|
+
# @option opts [String] :content_type Message content type (e.g. application/json)
|
37
|
+
# @option opts [String] :content_encoding Message content encoding (e.g. gzip)
|
38
|
+
# @option opts [String] :correlation_id Message correlated to this one, e.g. what request this message is a reply for
|
39
|
+
# @option opts [Integer] :priority Message priority, 0 to 9. Not used by RabbitMQ, only applications
|
40
|
+
# @option opts [String] :message_id Any message identifier
|
41
|
+
# @option opts [String] :user_id Optional user ID. Verified by RabbitMQ against the actual connection username
|
42
|
+
# @option opts [String] :app_id Optional application ID
|
43
|
+
#
|
44
|
+
# @raise [PublishError] if publishing fails
|
45
|
+
# @return [void]
|
46
|
+
def publish(data, routing_key:, **options)
|
47
|
+
publish_options = default_publish_options.merge(options)
|
48
|
+
|
49
|
+
retry_handler.run do
|
50
|
+
publish_with_channel(data:, routing_key:, publish_options:)
|
51
|
+
end
|
52
|
+
end
|
53
|
+
|
54
|
+
# Resets the channel pool, forcing new channels to be created.
|
55
|
+
# This can be useful for connection recovery scenarios.
|
56
|
+
#
|
57
|
+
# @return [void]
|
58
|
+
def reset!
|
59
|
+
PublisherChannelPool.reset!
|
60
|
+
end
|
61
|
+
|
62
|
+
private
|
63
|
+
|
64
|
+
attr_reader :exchange_name,
|
65
|
+
:exchange_type,
|
66
|
+
:exchange_options,
|
67
|
+
:config,
|
68
|
+
:logger
|
69
|
+
|
70
|
+
def publish_with_channel(data:, routing_key:, publish_options:)
|
71
|
+
unless Ears.connection.open?
|
72
|
+
raise PublisherRetryHandler::PublishToStaleChannelError,
|
73
|
+
'Connection is not open'
|
74
|
+
end
|
75
|
+
|
76
|
+
PublisherChannelPool.with_channel do |channel|
|
77
|
+
exchange = create_exchange(channel)
|
78
|
+
exchange.publish(
|
79
|
+
data,
|
80
|
+
{ routing_key: routing_key }.merge(publish_options),
|
81
|
+
)
|
82
|
+
end
|
83
|
+
end
|
84
|
+
|
85
|
+
def create_exchange(channel)
|
86
|
+
Bunny::Exchange.new(
|
87
|
+
channel,
|
88
|
+
exchange_type,
|
89
|
+
exchange_name,
|
90
|
+
exchange_options,
|
91
|
+
)
|
92
|
+
end
|
93
|
+
|
94
|
+
def default_publish_options
|
95
|
+
{
|
96
|
+
persistent: true,
|
97
|
+
timestamp: Time.now.to_i,
|
98
|
+
headers: {
|
99
|
+
},
|
100
|
+
content_type: 'application/json',
|
101
|
+
}
|
102
|
+
end
|
103
|
+
|
104
|
+
def retry_handler
|
105
|
+
@retry_handler ||= PublisherRetryHandler.new(config, logger)
|
106
|
+
end
|
107
|
+
end
|
108
|
+
end
|
@@ -0,0 +1,55 @@
|
|
1
|
+
require 'connection_pool'
|
2
|
+
|
3
|
+
module Ears
|
4
|
+
# Channel pool management for publishers.
|
5
|
+
# Provides thread-safe channel pooling separate from consumer channels.
|
6
|
+
class PublisherChannelPool
|
7
|
+
class << self
|
8
|
+
# Executes the given block with a channel from the pool.
|
9
|
+
#
|
10
|
+
# @yieldparam [Bunny::Channel] channel The channel to use for publishing
|
11
|
+
# @return [Object] The result of the block
|
12
|
+
def with_channel(&)
|
13
|
+
channel_pool.with(&)
|
14
|
+
end
|
15
|
+
|
16
|
+
# Resets the channel pool, forcing new channels to be created.
|
17
|
+
# This is useful for connection recovery scenarios.
|
18
|
+
#
|
19
|
+
# @return [void]
|
20
|
+
def reset!
|
21
|
+
pool = @channel_pool
|
22
|
+
@channel_pool = nil
|
23
|
+
@creator_pid = nil
|
24
|
+
|
25
|
+
pool&.shutdown(&:close)
|
26
|
+
nil
|
27
|
+
end
|
28
|
+
|
29
|
+
private
|
30
|
+
|
31
|
+
def channel_pool
|
32
|
+
# Recreate lazily after a fork
|
33
|
+
reset! if @creator_pid && @creator_pid != Process.pid
|
34
|
+
|
35
|
+
return @channel_pool if @channel_pool
|
36
|
+
|
37
|
+
init_mutex.synchronize do
|
38
|
+
@channel_pool ||=
|
39
|
+
begin
|
40
|
+
@creator_pid = Process.pid
|
41
|
+
|
42
|
+
ConnectionPool.new(
|
43
|
+
size: Ears.configuration.publisher_pool_size,
|
44
|
+
timeout: Ears.configuration.publisher_pool_timeout,
|
45
|
+
) { Ears.connection.create_channel }
|
46
|
+
end
|
47
|
+
end
|
48
|
+
end
|
49
|
+
|
50
|
+
def init_mutex
|
51
|
+
@init_mutex ||= Mutex.new
|
52
|
+
end
|
53
|
+
end
|
54
|
+
end
|
55
|
+
end
|
@@ -0,0 +1,104 @@
|
|
1
|
+
require 'bunny'
|
2
|
+
|
3
|
+
module Ears
|
4
|
+
# A handler for retries and connection recovery when publishing messages.
|
5
|
+
class PublisherRetryHandler
|
6
|
+
# Exception for publishing to a stale/closed channel
|
7
|
+
class PublishToStaleChannelError < StandardError
|
8
|
+
end
|
9
|
+
|
10
|
+
# Connection errors that should trigger retries
|
11
|
+
CONNECTION_ERRORS = [
|
12
|
+
PublishToStaleChannelError,
|
13
|
+
Bunny::ChannelAlreadyClosed,
|
14
|
+
Bunny::ConnectionClosedError,
|
15
|
+
Bunny::ConnectionForced,
|
16
|
+
Bunny::NetworkFailure,
|
17
|
+
Bunny::TCPConnectionFailed,
|
18
|
+
IOError,
|
19
|
+
Timeout::Error,
|
20
|
+
].freeze
|
21
|
+
|
22
|
+
def initialize(config, logger)
|
23
|
+
@config = config
|
24
|
+
@logger = logger
|
25
|
+
end
|
26
|
+
|
27
|
+
def run(&block)
|
28
|
+
attempt = 1
|
29
|
+
begin
|
30
|
+
block.call
|
31
|
+
rescue *CONNECTION_ERRORS => e
|
32
|
+
attempt = handle_connection_error(e, attempt) # rubocop:disable Lint/UselessAssignment
|
33
|
+
retry
|
34
|
+
rescue StandardError => e
|
35
|
+
attempt = handle_standard_error(e, attempt)
|
36
|
+
retry
|
37
|
+
end
|
38
|
+
end
|
39
|
+
|
40
|
+
private
|
41
|
+
|
42
|
+
attr_reader :config, :logger
|
43
|
+
|
44
|
+
def handle_connection_error(error, attempt)
|
45
|
+
raise(error) unless retry?(error, attempt)
|
46
|
+
|
47
|
+
wait_for_connection(error)
|
48
|
+
logger.info('Resetting channel pool after connection recovery')
|
49
|
+
PublisherChannelPool.reset!
|
50
|
+
|
51
|
+
attempt + 1
|
52
|
+
end
|
53
|
+
|
54
|
+
def handle_standard_error(error, attempt)
|
55
|
+
raise(error) unless retry?(error, attempt)
|
56
|
+
|
57
|
+
sleep(retry_backoff_delay(attempt)) if attempt > 1
|
58
|
+
attempt + 1
|
59
|
+
end
|
60
|
+
|
61
|
+
def retry?(error, attempt)
|
62
|
+
logger.info(
|
63
|
+
"Trying to recover from publish error. Attempt #{attempt}: #{error.class}: #{error.message}",
|
64
|
+
)
|
65
|
+
|
66
|
+
if attempt > config.publisher_max_retries
|
67
|
+
logger.warn(
|
68
|
+
"Connection attempts exhausted, giving up: #{error.class}: #{error.message}",
|
69
|
+
)
|
70
|
+
return false
|
71
|
+
end
|
72
|
+
|
73
|
+
true
|
74
|
+
end
|
75
|
+
|
76
|
+
def wait_for_connection(original_error)
|
77
|
+
connection_attempt = 0
|
78
|
+
logger.info('Trying to reconnect after connection error')
|
79
|
+
while !Ears.connection.open?
|
80
|
+
logger.info(
|
81
|
+
"Connection still closed, attempt #{connection_attempt + 1}",
|
82
|
+
)
|
83
|
+
connection_attempt += 1
|
84
|
+
|
85
|
+
if connection_attempt > config.publisher_connection_attempts
|
86
|
+
logger.error('Connection attempts exhausted, giving up')
|
87
|
+
raise original_error
|
88
|
+
end
|
89
|
+
|
90
|
+
sleep(connection_backoff_delay(connection_attempt))
|
91
|
+
end
|
92
|
+
end
|
93
|
+
|
94
|
+
def retry_backoff_delay(attempt)
|
95
|
+
config.publisher_retry_base_delay *
|
96
|
+
(config.publisher_retry_backoff_factor**(attempt - 1))
|
97
|
+
end
|
98
|
+
|
99
|
+
def connection_backoff_delay(attempt)
|
100
|
+
config.publisher_connection_base_delay *
|
101
|
+
(config.publisher_connection_backoff_factor**(attempt - 1))
|
102
|
+
end
|
103
|
+
end
|
104
|
+
end
|
data/lib/ears/version.rb
CHANGED
data/lib/ears.rb
CHANGED
@@ -2,6 +2,8 @@ require 'bunny'
|
|
2
2
|
require 'ears/configuration'
|
3
3
|
require 'ears/consumer'
|
4
4
|
require 'ears/middleware'
|
5
|
+
require 'ears/publisher'
|
6
|
+
require 'ears/publisher_channel_pool'
|
5
7
|
require 'ears/setup'
|
6
8
|
require 'ears/version'
|
7
9
|
|
@@ -43,8 +45,8 @@ module Ears
|
|
43
45
|
end
|
44
46
|
|
45
47
|
# Used to set up your exchanges, queues and consumers. See {Ears::Setup} for implementation details.
|
46
|
-
def setup(&
|
47
|
-
Ears::Setup.new.instance_eval(&
|
48
|
+
def setup(&)
|
49
|
+
Ears::Setup.new.instance_eval(&)
|
48
50
|
end
|
49
51
|
|
50
52
|
# Quick setup your consumers (including exchanges and queues).
|
data/package-lock.json
CHANGED
@@ -8,7 +8,7 @@
|
|
8
8
|
"devDependencies": {
|
9
9
|
"@invisionag/prettier-config": "^2.1.3",
|
10
10
|
"@prettier/plugin-ruby": "^4.0.4",
|
11
|
-
"prettier": "^3.
|
11
|
+
"prettier": "^3.6.2"
|
12
12
|
}
|
13
13
|
},
|
14
14
|
"node_modules/@invisionag/prettier-config": {
|
@@ -30,9 +30,9 @@
|
|
30
30
|
}
|
31
31
|
},
|
32
32
|
"node_modules/prettier": {
|
33
|
-
"version": "3.
|
34
|
-
"resolved": "https://registry.npmjs.org/prettier/-/prettier-3.
|
35
|
-
"integrity": "sha512-
|
33
|
+
"version": "3.6.2",
|
34
|
+
"resolved": "https://registry.npmjs.org/prettier/-/prettier-3.6.2.tgz",
|
35
|
+
"integrity": "sha512-I7AIg5boAr5R0FFtJ6rCfD+LFsWHp81dolrFD8S79U9tb8Az2nGrJncnMSnys+bpQJfRUzqs9hnA81OAA3hCuQ==",
|
36
36
|
"dev": true,
|
37
37
|
"license": "MIT",
|
38
38
|
"bin": {
|
@@ -62,9 +62,9 @@
|
|
62
62
|
"requires": {}
|
63
63
|
},
|
64
64
|
"prettier": {
|
65
|
-
"version": "3.
|
66
|
-
"resolved": "https://registry.npmjs.org/prettier/-/prettier-3.
|
67
|
-
"integrity": "sha512-
|
65
|
+
"version": "3.6.2",
|
66
|
+
"resolved": "https://registry.npmjs.org/prettier/-/prettier-3.6.2.tgz",
|
67
|
+
"integrity": "sha512-I7AIg5boAr5R0FFtJ6rCfD+LFsWHp81dolrFD8S79U9tb8Az2nGrJncnMSnys+bpQJfRUzqs9hnA81OAA3hCuQ==",
|
68
68
|
"dev": true
|
69
69
|
}
|
70
70
|
}
|
data/package.json
CHANGED
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: ears
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.21.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- InVision AG
|
8
8
|
autorequire:
|
9
9
|
bindir: exe
|
10
10
|
cert_chain: []
|
11
|
-
date: 2025-
|
11
|
+
date: 2025-09-08 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: bunny
|
@@ -24,6 +24,20 @@ dependencies:
|
|
24
24
|
- - ">="
|
25
25
|
- !ruby/object:Gem::Version
|
26
26
|
version: 2.22.0
|
27
|
+
- !ruby/object:Gem::Dependency
|
28
|
+
name: connection_pool
|
29
|
+
requirement: !ruby/object:Gem::Requirement
|
30
|
+
requirements:
|
31
|
+
- - "~>"
|
32
|
+
- !ruby/object:Gem::Version
|
33
|
+
version: '2.4'
|
34
|
+
type: :runtime
|
35
|
+
prerelease: false
|
36
|
+
version_requirements: !ruby/object:Gem::Requirement
|
37
|
+
requirements:
|
38
|
+
- - "~>"
|
39
|
+
- !ruby/object:Gem::Version
|
40
|
+
version: '2.4'
|
27
41
|
- !ruby/object:Gem::Dependency
|
28
42
|
name: multi_json
|
29
43
|
requirement: !ruby/object:Gem::Requirement
|
@@ -73,6 +87,9 @@ files:
|
|
73
87
|
- lib/ears/middlewares/appsignal.rb
|
74
88
|
- lib/ears/middlewares/json.rb
|
75
89
|
- lib/ears/middlewares/max_retries.rb
|
90
|
+
- lib/ears/publisher.rb
|
91
|
+
- lib/ears/publisher_channel_pool.rb
|
92
|
+
- lib/ears/publisher_retry_handler.rb
|
76
93
|
- lib/ears/setup.rb
|
77
94
|
- lib/ears/version.rb
|
78
95
|
- package-lock.json
|
@@ -94,14 +111,14 @@ required_ruby_version: !ruby/object:Gem::Requirement
|
|
94
111
|
requirements:
|
95
112
|
- - ">="
|
96
113
|
- !ruby/object:Gem::Version
|
97
|
-
version: 3.
|
114
|
+
version: 3.2.9
|
98
115
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
99
116
|
requirements:
|
100
117
|
- - ">="
|
101
118
|
- !ruby/object:Gem::Version
|
102
119
|
version: '0'
|
103
120
|
requirements: []
|
104
|
-
rubygems_version: 3.
|
121
|
+
rubygems_version: 3.4.19
|
105
122
|
signing_key:
|
106
123
|
specification_version: 4
|
107
124
|
summary: A gem for building RabbitMQ consumers.
|