async-rabbitmq 0.4.0 → 0.5.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/CHANGELOG.md +45 -0
- data/README.md +55 -9
- data/lib/async_rabbitmq/channel.rb +132 -34
- data/lib/async_rabbitmq/version.rb +1 -1
- data/lib/async_rabbitmq.rb +18 -0
- metadata +1 -1
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 63e32dac7aed43a3627da9fec4b8b2df543592f940f5301eddc4b388869f5728
|
|
4
|
+
data.tar.gz: '08d691a8d45ec1d1cb4162b6b8e874a3bd1bf52813c9ca9004d2439a0eaa2d67'
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 0c3b6732b01c0a6014bf6bd420ffd59e8f4093be2c253194d74accf67b57545d5655e5ac70856c18c4dd4c5697aed8e79e30282af00954fd9e482bc04d65d19f
|
|
7
|
+
data.tar.gz: 64dcd5ac758a50d502fc86d99809a7df1ddb64a19c34251282fc12ccf33ea657011cf86d51c35c1b86e59efd699205ef93e7bad97bf5fc01d1523609559dd50e
|
data/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,51 @@
|
|
|
2
2
|
|
|
3
3
|
All notable changes to this project will be documented in this file.
|
|
4
4
|
|
|
5
|
+
## [0.5.0] - 2026-10-09
|
|
6
|
+
|
|
7
|
+
### Fixed
|
|
8
|
+
|
|
9
|
+
- **A consumer backlog no longer costs a fiber per queued message.** The dispatch loop started an
|
|
10
|
+
`Async::Task` per delivery and took the pool semaphore from inside it, so `pool_size` capped how
|
|
11
|
+
many handlers *ran*, not how many *existed*. A parked task measured ~15.7 KB (the fiber object
|
|
12
|
+
plus its touched stack pages) against ~48 bytes for a queue entry, and 20,000 backlogged messages
|
|
13
|
+
cost 315 MB of RSS. Handlers now run in `pool_size` long-lived worker fibers draining a queue of
|
|
14
|
+
deliveries — the same shape as Bunny's `ConsumerWorkPool`, the .NET client's consumer dispatcher
|
|
15
|
+
and amqp091-go.
|
|
16
|
+
|
|
17
|
+
That queue is deliberately unbounded. The dispatch loop is also what delivers this channel's
|
|
18
|
+
confirms, close-ok and returns, so blocking it on a full queue would deadlock a handler that
|
|
19
|
+
publishes and waits for confirms — which is precisely rabbitmq-amqp-java-client#328, where a
|
|
20
|
+
bounded work pool parked the I/O thread in `LinkedBlockingQueue#put`. Memory is therefore bounded
|
|
21
|
+
by prefetch on a manual-ack consumer and unbounded on an auto-ack one, as it is in every client,
|
|
22
|
+
because automatic acknowledgement has no backpressure to offer.
|
|
23
|
+
- **The missing-prefetch warning no longer sends auto-ack consumers to a no-op.** RabbitMQ applies
|
|
24
|
+
prefetch only to *unacknowledged* messages, so `basic_qos` does nothing at all on a
|
|
25
|
+
`manual_ack: false` consumer. The warning told you to call it anyway, and then went quiet once you
|
|
26
|
+
had — silence in the one case where the caller most believes the consumer is bounded and it is
|
|
27
|
+
not. It now says which of the two cases you are in, and still warns an auto-ack consumer that has
|
|
28
|
+
set a prefetch.
|
|
29
|
+
|
|
30
|
+
### Added
|
|
31
|
+
|
|
32
|
+
- `Channel#backlog` — deliveries handed to the handler workers but not yet picked up, the number
|
|
33
|
+
Bunny reports as `ConsumerWorkPool#backlog`.
|
|
34
|
+
- `AsyncRabbitMQ.warn_unbounded_consumers` — a deliberate way to silence the unbounded-consumer
|
|
35
|
+
warning for someone who has read it and accepted the trade, instead of turning the logger down
|
|
36
|
+
and losing every other warning a channel raises (stale delivery tags, broker-cancelled consumers,
|
|
37
|
+
handler exceptions). One process-wide setting, read when a consumer starts; defaults to `true`.
|
|
38
|
+
|
|
39
|
+
### Changed
|
|
40
|
+
|
|
41
|
+
- A backlog still queued when the channel goes away is now **finished on a graceful `close`** (as
|
|
42
|
+
Bunny's `ConsumerWorkPool#shutdown` drains) and **discarded when the connection is lost or the
|
|
43
|
+
channel is reopened**, where the broker requeues the same messages and running the queued handlers
|
|
44
|
+
too would process each one twice.
|
|
45
|
+
- `Channel#pool_size=` resizes by adding or retiring workers rather than resizing a semaphore.
|
|
46
|
+
Growing takes effect at once; shrinking retires a worker as soon as the queue drains to the
|
|
47
|
+
sentinel — immediately when idle, after the current backlog when busy. A running handler is never
|
|
48
|
+
interrupted.
|
|
49
|
+
|
|
5
50
|
## [0.4.0] - 2026-10-07
|
|
6
51
|
|
|
7
52
|
Reliability review against a payments workload. The two delivery-correctness items
|
data/README.md
CHANGED
|
@@ -1,8 +1,36 @@
|
|
|
1
1
|
# async-rabbitmq
|
|
2
2
|
|
|
3
|
-
A fiber-native RabbitMQ (AMQP 0-9-1) client for Ruby, built on the
|
|
3
|
+
A fiber-native RabbitMQ (AMQP 0-9-1) client for Ruby, built on the amazing work
|
|
4
|
+
of [Bunny](https://github.com/ruby-amqp/bunny).
|
|
5
|
+
|
|
6
|
+
## Bunny is the gold standard
|
|
7
|
+
|
|
8
|
+
Bunny is the client Ruby learned RabbitMQ on. Its API is the one Ruby developers
|
|
9
|
+
already know, its defaults are the ones a decade of production traffic settled,
|
|
10
|
+
and its source is the reference this library was written against. None of that
|
|
11
|
+
is improved on here, and none of it is meant to be.
|
|
12
|
+
|
|
13
|
+
**Parity with Bunny is a design goal**, chosen deliberately over fiber-native
|
|
14
|
+
maximalism: the audience for this gem is people who already know Bunny, and
|
|
15
|
+
surprising them is a bug. Where Bunny has an answer, that is the answer here.
|
|
16
|
+
`pool_size` defaults to 1 and serialises handlers because `ConsumerWorkPool`
|
|
17
|
+
does. A consumer backlog is an unbounded queue drained by a fixed set of workers
|
|
18
|
+
because that is what `ConsumerWorkPool` is. `basic_get` acknowledges manually by
|
|
19
|
+
default because Bunny's does. `spec/integration/25_bunny_api_parity_spec.rb`
|
|
20
|
+
exists to keep it honest.
|
|
21
|
+
|
|
22
|
+
Where this client does diverge, the README says so and says why - see the two
|
|
23
|
+
publishing differences below, and `basic_qos`, which also resizes the handler
|
|
24
|
+
pool where Bunny's leaves it alone.
|
|
25
|
+
|
|
26
|
+
If you are not running under the fiber scheduler, use Bunny. It is excellent,
|
|
27
|
+
and this gem has no advantage over it outside an async application.
|
|
28
|
+
|
|
29
|
+
## What this adds
|
|
30
|
+
|
|
31
|
+
The runtime, not the API. Built on the
|
|
4
32
|
[async](https://github.com/socketry/async) ecosystem and
|
|
5
|
-
[amq-protocol](https://github.com/ruby-amqp/amq-protocol)
|
|
33
|
+
[amq-protocol](https://github.com/ruby-amqp/amq-protocol), with no threads: the
|
|
6
34
|
reader, writer, heartbeat and every consumer handler are fibers, so it fits
|
|
7
35
|
Falcon, async-http and anything else running under the fiber scheduler.
|
|
8
36
|
|
|
@@ -140,11 +168,30 @@ ch.basic_cancel(tag)
|
|
|
140
168
|
ch.each("q") { |delivery, header, body| ... } # blocks until the consumer or channel goes away
|
|
141
169
|
```
|
|
142
170
|
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
171
|
+
Each channel runs `pool_size` long-lived handler fibers (default 1, serialized,
|
|
172
|
+
Bunny parity) draining a queue of deliveries, so a backlog costs the messages it
|
|
173
|
+
holds rather than a fiber per message. `ch.backlog` reports how many deliveries
|
|
174
|
+
are waiting, as Bunny's `ConsumerWorkPool#backlog` does.
|
|
175
|
+
|
|
176
|
+
What bounds that backlog depends on the ack mode. With `manual_ack: true`, set
|
|
177
|
+
`basic_qos` before `basic_consume` and the broker holds everything past the
|
|
178
|
+
prefetch window. With automatic acks there is no backpressure to be had:
|
|
179
|
+
RabbitMQ applies prefetch only to *unacknowledged* messages, so the broker
|
|
180
|
+
treats each one as acked on send and keeps sending whatever you set. The client
|
|
181
|
+
warns once per channel in either case, and says which of the two you are in.
|
|
182
|
+
|
|
183
|
+
If you have read that warning and accepted the trade - an auto-ack consumer on a
|
|
184
|
+
queue you know stays shallow, say - switch it off during boot:
|
|
185
|
+
|
|
186
|
+
```ruby
|
|
187
|
+
AsyncRabbitMQ.warn_unbounded_consumers = false
|
|
188
|
+
```
|
|
189
|
+
|
|
190
|
+
It is deliberately one process-wide setting rather than a per-channel option: an
|
|
191
|
+
application either accepts unbounded consumers or it does not. The point of
|
|
192
|
+
having it at all is that the alternative is turning the logger down, which would
|
|
193
|
+
also lose stale delivery tags, broker-cancelled consumers and handler
|
|
194
|
+
exceptions.
|
|
148
195
|
|
|
149
196
|
### Acknowledging, and delivery tags across a reconnect
|
|
150
197
|
|
|
@@ -282,8 +329,7 @@ end
|
|
|
282
329
|
```
|
|
283
330
|
|
|
284
331
|
`Async::Condition#wait`, a `sleep`, or your own supervisor task work equally
|
|
285
|
-
well.
|
|
286
|
-
tasks were children of whichever task called `connect`.
|
|
332
|
+
well.
|
|
287
333
|
|
|
288
334
|
## Connection recovery
|
|
289
335
|
|
|
@@ -24,6 +24,10 @@ module AsyncRabbitMQ
|
|
|
24
24
|
# after an RPC timeout, used when the session has no rpc_timeout set.
|
|
25
25
|
RESYNC_TIMEOUT = 5
|
|
26
26
|
|
|
27
|
+
# Pushed onto the work queue to retire one handler worker. Compared by
|
|
28
|
+
# identity, so it can never collide with a delivery.
|
|
29
|
+
WORKER_STOP = Object.new.freeze
|
|
30
|
+
|
|
27
31
|
include Instrumented
|
|
28
32
|
|
|
29
33
|
attr_reader :channel_id, :pool_size
|
|
@@ -89,14 +93,27 @@ module AsyncRabbitMQ
|
|
|
89
93
|
@pending_content = nil
|
|
90
94
|
@recovered_condition = nil # fibers parked while the connection recovers
|
|
91
95
|
@pool_size = validate_pool_size!(pool_size)
|
|
92
|
-
@
|
|
96
|
+
@work_queue = nil # Async::Queue of [method, header, body, entry]
|
|
97
|
+
@workers = [] # long-lived handler fibers draining it
|
|
93
98
|
end
|
|
94
99
|
|
|
95
|
-
# Resize the consumer-handler concurrency cap
|
|
96
|
-
#
|
|
100
|
+
# Resize the consumer-handler concurrency cap by adding or retiring
|
|
101
|
+
# workers. Growing takes effect at once. Shrinking is a sentinel on the
|
|
102
|
+
# work queue, so it retires a worker as soon as the queue drains to it:
|
|
103
|
+
# immediately when idle, after the current backlog when busy. A handler
|
|
104
|
+
# already running is never interrupted.
|
|
97
105
|
def pool_size=(n)
|
|
98
|
-
|
|
99
|
-
|
|
106
|
+
n = validate_pool_size!(n)
|
|
107
|
+
return if n == @pool_size
|
|
108
|
+
|
|
109
|
+
@pool_size = n
|
|
110
|
+
resize_workers
|
|
111
|
+
end
|
|
112
|
+
|
|
113
|
+
# Deliveries handed to the handler workers but not yet picked up. Bunny
|
|
114
|
+
# reports the same number as ConsumerWorkPool#backlog.
|
|
115
|
+
def backlog
|
|
116
|
+
@work_queue&.size || 0
|
|
100
117
|
end
|
|
101
118
|
|
|
102
119
|
def open?
|
|
@@ -147,6 +164,7 @@ module AsyncRabbitMQ
|
|
|
147
164
|
instrument("channel.closed") { { channel: @channel_id, reason: :user } }
|
|
148
165
|
@session.channel_closed(@channel_id)
|
|
149
166
|
@queue&.push(nil) # wake dispatch_loop so it can detect :closed and exit
|
|
167
|
+
stop_workers
|
|
150
168
|
wake_each_waiters
|
|
151
169
|
end
|
|
152
170
|
|
|
@@ -409,7 +427,7 @@ module AsyncRabbitMQ
|
|
|
409
427
|
# handlers run concurrently across all consumers on this channel.
|
|
410
428
|
# Returns the consumer tag.
|
|
411
429
|
def basic_consume(queue_name, consumer_tag: "", manual_ack: false, exclusive: false, arguments: {}, &block)
|
|
412
|
-
warn_unbounded_prefetch(queue_name)
|
|
430
|
+
warn_unbounded_prefetch(queue_name, manual_ack)
|
|
413
431
|
resp = rpc(
|
|
414
432
|
AMQ::Protocol::Basic::Consume.encode(@channel_id, queue_name, consumer_tag, false, !manual_ack, exclusive, false, arguments),
|
|
415
433
|
AMQ::Protocol::Basic::ConsumeOk
|
|
@@ -667,6 +685,9 @@ module AsyncRabbitMQ
|
|
|
667
685
|
@confirm_condition = nil
|
|
668
686
|
release_outstanding_slots(error)
|
|
669
687
|
@queue&.push(nil) rescue nil
|
|
688
|
+
# The connection is gone, so the queued deliveries are too: the broker
|
|
689
|
+
# requeues whatever was unacknowledged and sends it again on the new one.
|
|
690
|
+
stop_workers(discard: true)
|
|
670
691
|
rescue => e
|
|
671
692
|
# ignore — best-effort unblock
|
|
672
693
|
end
|
|
@@ -832,20 +853,34 @@ module AsyncRabbitMQ
|
|
|
832
853
|
true
|
|
833
854
|
end
|
|
834
855
|
|
|
835
|
-
# Without a
|
|
836
|
-
#
|
|
837
|
-
#
|
|
838
|
-
#
|
|
839
|
-
|
|
856
|
+
# Without a bound the broker sends as fast as it can and the backlog is
|
|
857
|
+
# held in this process. What can bound it depends on the ack mode, so the
|
|
858
|
+
# advice does too: RabbitMQ applies prefetch only to *unacknowledged*
|
|
859
|
+
# messages, so on an auto-ack consumer basic_qos never engages and telling
|
|
860
|
+
# the user to call it would be wrong. Warned once per channel.
|
|
861
|
+
def warn_unbounded_prefetch(queue_name, manual_ack)
|
|
862
|
+
return unless AsyncRabbitMQ.warn_unbounded_consumers
|
|
840
863
|
return if @qos_warned
|
|
841
|
-
|
|
864
|
+
# A prefetch answers the manual-ack case, so having set one is a reason to
|
|
865
|
+
# stay quiet there. It does nothing for an auto-ack consumer, so it is not
|
|
866
|
+
# a reason here: that is precisely the case where the caller believes the
|
|
867
|
+
# consumer is bounded and it is not.
|
|
868
|
+
return if manual_ack && @prefetch && @prefetch[:count].to_i > 0
|
|
842
869
|
|
|
843
870
|
@qos_warned = true
|
|
844
871
|
@logger.warn(
|
|
845
|
-
|
|
846
|
-
|
|
847
|
-
|
|
848
|
-
|
|
872
|
+
if manual_ack
|
|
873
|
+
"Channel #{@channel_id}: consuming from #{queue_name} without basic_qos. " \
|
|
874
|
+
"The broker will send the whole queue as fast as it can and the backlog is " \
|
|
875
|
+
"held in this process. Call basic_qos(prefetch_count: n) before " \
|
|
876
|
+
"basic_consume to bound it."
|
|
877
|
+
else
|
|
878
|
+
"Channel #{@channel_id}: consuming from #{queue_name} with automatic acks. " \
|
|
879
|
+
"The broker treats every message as acknowledged on send, so basic_qos " \
|
|
880
|
+
"cannot bound this consumer and the whole queue is held in this process. " \
|
|
881
|
+
"Use manual_ack: true with basic_qos(prefetch_count: n) to get " \
|
|
882
|
+
"backpressure, or watch #backlog."
|
|
883
|
+
end
|
|
849
884
|
)
|
|
850
885
|
end
|
|
851
886
|
|
|
@@ -904,9 +939,80 @@ module AsyncRabbitMQ
|
|
|
904
939
|
# -------------------------------------------------------------------------
|
|
905
940
|
|
|
906
941
|
def start_dispatch_task
|
|
942
|
+
start_workers
|
|
907
943
|
@session.spawn_background { dispatch_loop }
|
|
908
944
|
end
|
|
909
945
|
|
|
946
|
+
# One long-lived fiber per pool_size, each draining the work queue for the
|
|
947
|
+
# life of this channel generation. A backlogged delivery costs a 4-element
|
|
948
|
+
# array (~48 bytes) rather than a parked Async::Task and its fiber stack
|
|
949
|
+
# (~15.7 KB measured), so memory tracks the messages held, not the fibers
|
|
950
|
+
# holding them. Bunny's ConsumerWorkPool is the same shape: fixed workers
|
|
951
|
+
# draining an unbounded ::Queue.
|
|
952
|
+
def start_workers
|
|
953
|
+
stop_workers(discard: true)
|
|
954
|
+
queue = @work_queue = Async::Queue.new
|
|
955
|
+
@workers = Array.new(@pool_size) { @session.spawn_background { worker_loop(queue) } }
|
|
956
|
+
end
|
|
957
|
+
|
|
958
|
+
# Retire this generation's workers. The sentinel goes on the back of the
|
|
959
|
+
# queue, so by default they finish the deliveries already queued before
|
|
960
|
+
# standing down - Bunny's ConsumerWorkPool#shutdown drains the same way.
|
|
961
|
+
#
|
|
962
|
+
# +discard+ throws that backlog away instead, for when the connection
|
|
963
|
+
# underneath it has gone: the broker requeues whatever was unacknowledged
|
|
964
|
+
# and sends it again on the new one, so running these handlers too would
|
|
965
|
+
# process every queued message twice. dequeue(timeout: 0) returns at once
|
|
966
|
+
# on an empty queue, so the drain cannot suspend us mid-teardown.
|
|
967
|
+
def stop_workers(discard: false)
|
|
968
|
+
queue = @work_queue
|
|
969
|
+
nil while discard && queue&.dequeue(timeout: 0)
|
|
970
|
+
@workers.size.times { queue&.push(WORKER_STOP) }
|
|
971
|
+
@workers = []
|
|
972
|
+
@work_queue = nil
|
|
973
|
+
end
|
|
974
|
+
|
|
975
|
+
# Workers are bound to the queue they were started on, never to @work_queue,
|
|
976
|
+
# so a worker outliving its generation cannot start draining the next one.
|
|
977
|
+
def resize_workers
|
|
978
|
+
queue = @work_queue
|
|
979
|
+
return unless queue
|
|
980
|
+
|
|
981
|
+
delta = @pool_size - @workers.size
|
|
982
|
+
if delta.positive?
|
|
983
|
+
delta.times { @workers << @session.spawn_background { worker_loop(queue) } }
|
|
984
|
+
elsif delta.negative?
|
|
985
|
+
(-delta).times { queue.push(WORKER_STOP) }
|
|
986
|
+
@workers.pop(-delta)
|
|
987
|
+
end
|
|
988
|
+
end
|
|
989
|
+
|
|
990
|
+
def worker_loop(queue)
|
|
991
|
+
loop do
|
|
992
|
+
job = queue.dequeue
|
|
993
|
+
break if job.nil? || job.equal?(WORKER_STOP)
|
|
994
|
+
|
|
995
|
+
run_handler(*job)
|
|
996
|
+
end
|
|
997
|
+
end
|
|
998
|
+
|
|
999
|
+
def run_handler(method, header, body, entry)
|
|
1000
|
+
started = instrument_clock
|
|
1001
|
+
begin
|
|
1002
|
+
entry[:block].call(method, header, body)
|
|
1003
|
+
rescue => e
|
|
1004
|
+
handle_consumer_error(e, method, entry)
|
|
1005
|
+
ensure
|
|
1006
|
+
if started
|
|
1007
|
+
instrument("message.consumed") do
|
|
1008
|
+
{ channel: @channel_id, queue: entry[:queue_name], consumer_tag: method.consumer_tag,
|
|
1009
|
+
bytes: body.to_s.bytesize, redelivered: method.redelivered,
|
|
1010
|
+
duration: instrument_elapsed(started) }
|
|
1011
|
+
end
|
|
1012
|
+
end
|
|
1013
|
+
end
|
|
1014
|
+
end
|
|
1015
|
+
|
|
910
1016
|
def dispatch_loop
|
|
911
1017
|
loop do
|
|
912
1018
|
msg = @queue.pop
|
|
@@ -979,24 +1085,15 @@ module AsyncRabbitMQ
|
|
|
979
1085
|
if entry
|
|
980
1086
|
stamp_delivery_tag(method)
|
|
981
1087
|
@unsettled[method.delivery_tag.to_i] = true if entry[:manual_ack]
|
|
982
|
-
Async
|
|
983
|
-
|
|
984
|
-
|
|
985
|
-
|
|
986
|
-
|
|
987
|
-
|
|
988
|
-
|
|
989
|
-
|
|
990
|
-
|
|
991
|
-
instrument("message.consumed") do
|
|
992
|
-
{ channel: @channel_id, queue: entry[:queue_name], consumer_tag: method.consumer_tag,
|
|
993
|
-
bytes: body.to_s.bytesize, redelivered: method.redelivered,
|
|
994
|
-
duration: instrument_elapsed(started) }
|
|
995
|
-
end
|
|
996
|
-
end
|
|
997
|
-
end
|
|
998
|
-
end
|
|
999
|
-
end
|
|
1088
|
+
# Hand off without suspending: Async::Queue wraps Thread::Queue,
|
|
1089
|
+
# whose push never blocks. dispatch_loop must stay free, because it
|
|
1090
|
+
# is also what delivers this channel's confirms, close-ok and
|
|
1091
|
+
# returns - a handler that publishes and waits for confirms would
|
|
1092
|
+
# otherwise deadlock against the loop that would have woken it. That
|
|
1093
|
+
# is not hypothetical: it is rabbitmq-amqp-java-client#328, where a
|
|
1094
|
+
# bounded work pool parked the I/O thread in LinkedBlockingQueue#put.
|
|
1095
|
+
# A nil queue means the channel is going away and the delivery is moot.
|
|
1096
|
+
@work_queue&.push([method, header, body, entry])
|
|
1000
1097
|
else
|
|
1001
1098
|
@logger.warn("Delivery on channel #{@channel_id} for unknown consumer #{method.consumer_tag}")
|
|
1002
1099
|
end
|
|
@@ -1119,6 +1216,7 @@ module AsyncRabbitMQ
|
|
|
1119
1216
|
@reply_condition = nil
|
|
1120
1217
|
# Stop dispatch_loop
|
|
1121
1218
|
@queue&.push(nil)
|
|
1219
|
+
stop_workers(discard: true)
|
|
1122
1220
|
wake_each_waiters
|
|
1123
1221
|
end
|
|
1124
1222
|
|
data/lib/async_rabbitmq.rb
CHANGED
|
@@ -1,3 +1,21 @@
|
|
|
1
|
+
module AsyncRabbitMQ
|
|
2
|
+
class << self
|
|
3
|
+
# Whether a channel warns, once, when it starts a consumer whose backlog
|
|
4
|
+
# nothing bounds - an auto-ack consumer, or a manual-ack one with no
|
|
5
|
+
# prefetch. True by default.
|
|
6
|
+
#
|
|
7
|
+
# Set it false when you have read the warning and accepted the trade, so
|
|
8
|
+
# that silencing it does not mean turning the logger down and losing every
|
|
9
|
+
# other warning a channel raises (stale delivery tags, broker-cancelled
|
|
10
|
+
# consumers, handler exceptions). It is read when a consumer starts, so set
|
|
11
|
+
# it during boot, before the first #basic_consume.
|
|
12
|
+
#
|
|
13
|
+
# AsyncRabbitMQ.warn_unbounded_consumers = false
|
|
14
|
+
attr_accessor :warn_unbounded_consumers
|
|
15
|
+
end
|
|
16
|
+
self.warn_unbounded_consumers = true
|
|
17
|
+
end
|
|
18
|
+
|
|
1
19
|
require_relative "async_rabbitmq/version"
|
|
2
20
|
require_relative "async_rabbitmq/log"
|
|
3
21
|
require_relative "async_rabbitmq/errors"
|