amqp-client 2.1.0 → 2.2.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/lib/amqp/client/consumer.rb +2 -1
- data/lib/amqp/client/queue.rb +4 -2
- data/lib/amqp/client/version.rb +1 -1
- data/lib/amqp/client.rb +114 -23
- metadata +9 -4
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: b52ef0851be1716d1833d84448e2470b19938b03050a14d8b5a40a1289cb41ae
|
|
4
|
+
data.tar.gz: 6e33352b3e238b7ccf1c203a4779da7b5f01f659ab27c39bf7a78299398025aa
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: e78020ff0cf64c9a4a13fb64625f5f0382950c5981fb2df130f4e61066930ba0c9298bd2d7bd3982b1ddbb1ef492cab89183d7e4a1c815a9fa10204897b0ff27
|
|
7
|
+
data.tar.gz: 2ab30a767b5fc5aa3f5a43949f7cde1b601e74e4bdb0ebc5b0d91306deee773aac9007bcc340610ae52fcba732983b804306cb6fdcbcfdab9e064ec17292ff30
|
data/lib/amqp/client/consumer.rb
CHANGED
|
@@ -39,8 +39,9 @@ module AMQP
|
|
|
39
39
|
|
|
40
40
|
# Update the consumer with new metadata after reconnection
|
|
41
41
|
# @api private
|
|
42
|
-
def update_consume_ok(consume_ok)
|
|
42
|
+
def update_consume_ok(consume_ok, channel_id)
|
|
43
43
|
@consume_ok = consume_ok
|
|
44
|
+
@channel_id = channel_id
|
|
44
45
|
end
|
|
45
46
|
end
|
|
46
47
|
end
|
data/lib/amqp/client/queue.rb
CHANGED
|
@@ -50,11 +50,13 @@ module AMQP
|
|
|
50
50
|
# @param on_cancel [Proc] Optional proc that will be called if the consumer is cancelled by the broker
|
|
51
51
|
# The proc will be called with the consumer tag as the only argument
|
|
52
52
|
# @param arguments [Hash] Custom arguments to the consumer
|
|
53
|
+
# @param consumer_tag [String, nil] Custom consumer tag. Pass nil or "" to let the broker generate one.
|
|
53
54
|
# @yield [Message] Delivered message from the queue
|
|
54
55
|
# @return [Consumer] The consumer object, which can be used to cancel the consumer
|
|
55
56
|
def subscribe(no_ack: false, exclusive: false, prefetch: 1, worker_threads: 1, requeue_on_reject: true,
|
|
56
|
-
on_cancel: nil, arguments: {})
|
|
57
|
-
@client.subscribe(@name, no_ack:, exclusive:, prefetch:, worker_threads:,
|
|
57
|
+
on_cancel: nil, arguments: {}, consumer_tag: nil)
|
|
58
|
+
@client.subscribe(@name, no_ack:, exclusive:, prefetch:, worker_threads:,
|
|
59
|
+
on_cancel:, arguments:, consumer_tag:) do |message|
|
|
58
60
|
yield message
|
|
59
61
|
message.ack unless no_ack
|
|
60
62
|
rescue StandardError => e
|
data/lib/amqp/client/version.rb
CHANGED
data/lib/amqp/client.rb
CHANGED
|
@@ -35,9 +35,23 @@ module AMQP
|
|
|
35
35
|
# @option options [#info, #warn, #error] logger (nil) Logger for {#start} lifecycle events
|
|
36
36
|
# (connected/reconnected/disconnected/reconnect errors). When nil, reconnect errors are
|
|
37
37
|
# written to stderr via Kernel#warn for backwards compatibility.
|
|
38
|
-
|
|
38
|
+
# @param on_connect [Proc, nil] Optional callback invoked with the client after each successful
|
|
39
|
+
# (re)connection, after consumer recovery.
|
|
40
|
+
# @param on_failed [Proc, nil] Optional callback invoked with the triggering error once
|
|
41
|
+
# reconnection has given up after max_retries consecutive failed attempts. The supervisor
|
|
42
|
+
# stops after calling it, same as after {#stop}.
|
|
43
|
+
# @param max_retries [Integer, nil] Number of consecutive reconnect attempts to allow before
|
|
44
|
+
# giving up and calling on_failed. Defaults to nil, retrying forever.
|
|
45
|
+
def initialize(uri = "", on_connect: nil, on_failed: nil, max_retries: nil, **options)
|
|
46
|
+
if max_retries && (!max_retries.is_a?(Integer) || max_retries.negative?)
|
|
47
|
+
raise ArgumentError, "max_retries must be a non-negative Integer or nil"
|
|
48
|
+
end
|
|
49
|
+
|
|
39
50
|
@uri = uri
|
|
40
51
|
@options = options
|
|
52
|
+
@on_connect = on_connect
|
|
53
|
+
@on_failed = on_failed
|
|
54
|
+
@max_retries = max_retries
|
|
41
55
|
@logger = options[:logger]
|
|
42
56
|
@name = parse_name(uri)
|
|
43
57
|
@queues = {}
|
|
@@ -82,36 +96,26 @@ module AMQP
|
|
|
82
96
|
@stopped = false
|
|
83
97
|
initial_conn = connect(read_loop_thread: false)
|
|
84
98
|
log_lifecycle(:info, "connected")
|
|
85
|
-
supervisor = Thread.new(initial_conn) do |conn|
|
|
99
|
+
supervisor = Thread.new(initial_conn) do |conn|
|
|
86
100
|
Thread.current.abort_on_exception = true # Raising an unhandled exception is a bug
|
|
87
|
-
|
|
101
|
+
reconnect_attempts = 0
|
|
102
|
+
loop do
|
|
88
103
|
break if @stopped
|
|
89
104
|
|
|
90
105
|
unless conn
|
|
91
106
|
conn = connect(read_loop_thread: false)
|
|
107
|
+
reconnect_attempts = 0
|
|
92
108
|
log_lifecycle(:info, "reconnected")
|
|
93
109
|
end
|
|
94
110
|
|
|
95
|
-
setup = Thread.new
|
|
96
|
-
# restore connection in another thread, read_loop have to run
|
|
97
|
-
conn.channel(1) # reserve channel 1 for publishes
|
|
98
|
-
@consumers.each_value do |consumer|
|
|
99
|
-
ch = conn.channel
|
|
100
|
-
ch.basic_qos(consumer.prefetch)
|
|
101
|
-
consume_ok = ch.basic_consume(consumer.queue,
|
|
102
|
-
**consumer.basic_consume_args,
|
|
103
|
-
&consumer.block)
|
|
104
|
-
# Update the consumer with new channel and consume_ok metadata
|
|
105
|
-
consumer.update_consume_ok(consume_ok)
|
|
106
|
-
end
|
|
107
|
-
@connq << conn
|
|
108
|
-
# Remove consumers whose internal queues were already closed (e.g. cancelled during reconnect window)
|
|
109
|
-
@consumers.delete_if { |_, c| c.closed? }
|
|
110
|
-
end
|
|
111
|
+
setup = Thread.new { restore_connection(conn) }
|
|
111
112
|
setup.name = thread_name("reconnect_setup")
|
|
112
113
|
conn.read_loop # blocks until connection is closed, then reconnect
|
|
113
114
|
log_lifecycle(:warn, "disconnected")
|
|
114
115
|
rescue Error => e
|
|
116
|
+
reconnect_attempts += 1
|
|
117
|
+
break if give_up_reconnecting?(reconnect_attempts, e)
|
|
118
|
+
|
|
115
119
|
log_reconnect_error(e)
|
|
116
120
|
sleep @options[:reconnect_interval] || 1
|
|
117
121
|
ensure
|
|
@@ -147,7 +151,7 @@ module AMQP
|
|
|
147
151
|
# @!group High level objects
|
|
148
152
|
|
|
149
153
|
# Declare a queue
|
|
150
|
-
# @param name [String] Name of the queue
|
|
154
|
+
# @param name [String, nil] Name of the queue. Pass nil or "" for a server-named queue.
|
|
151
155
|
# @param durable [Boolean] If true the queue will survive broker restarts,
|
|
152
156
|
# messages in the queue will only survive if they are published as persistent
|
|
153
157
|
# @param auto_delete [Boolean] If true the queue will be deleted when the last consumer stops consuming
|
|
@@ -161,7 +165,12 @@ module AMQP
|
|
|
161
165
|
# q = amqp.queue("foobar")
|
|
162
166
|
# q.publish("body")
|
|
163
167
|
def queue(name, durable: true, auto_delete: false, exclusive: false, passive: false, arguments: {})
|
|
164
|
-
|
|
168
|
+
if server_named_queue?(name)
|
|
169
|
+
queue_ok = with_connection do |conn|
|
|
170
|
+
conn.channel(1).queue_declare("", durable:, auto_delete:, exclusive:, passive:, arguments:)
|
|
171
|
+
end
|
|
172
|
+
return Queue.new(self, queue_ok.queue_name)
|
|
173
|
+
end
|
|
165
174
|
|
|
166
175
|
@queues.fetch(name) do
|
|
167
176
|
with_connection do |conn|
|
|
@@ -316,10 +325,11 @@ module AMQP
|
|
|
316
325
|
# @param on_cancel [Proc] Optional proc that will be called if the consumer is cancelled by the broker
|
|
317
326
|
# The proc will be called with the consumer tag as the only argument
|
|
318
327
|
# @param arguments [Hash] Custom arguments to the consumer
|
|
328
|
+
# @param consumer_tag [String, nil] Custom consumer tag. Pass nil or "" to let the broker generate one.
|
|
319
329
|
# @yield [Message] Delivered message from the queue
|
|
320
330
|
# @return [Consumer] The consumer object, which can be used to cancel the consumer
|
|
321
331
|
def subscribe(queue, exclusive: false, no_ack: false, prefetch: 1, worker_threads: 1,
|
|
322
|
-
on_cancel: nil, arguments: {}, &blk)
|
|
332
|
+
on_cancel: nil, arguments: {}, consumer_tag: nil, &blk)
|
|
323
333
|
raise ArgumentError, "worker_threads have to be > 0" if worker_threads <= 0
|
|
324
334
|
|
|
325
335
|
with_connection do |conn|
|
|
@@ -330,7 +340,9 @@ module AMQP
|
|
|
330
340
|
@consumers.delete(consumer_id)
|
|
331
341
|
on_cancel&.call(tag)
|
|
332
342
|
end
|
|
333
|
-
|
|
343
|
+
tag = consumer_tag.nil? ? "" : consumer_tag
|
|
344
|
+
basic_consume_args = { tag:, exclusive:, no_ack:, worker_threads:,
|
|
345
|
+
on_cancel: on_cancel_proc, arguments: }
|
|
334
346
|
consume_ok = ch.basic_consume(queue, **basic_consume_args, &blk)
|
|
335
347
|
consumer = Consumer.new(client: self, channel_id: ch.id, id: consumer_id, block: blk,
|
|
336
348
|
queue:, consume_ok:, prefetch:, basic_consume_args:)
|
|
@@ -553,6 +565,8 @@ module AMQP
|
|
|
553
565
|
# @!endgroup
|
|
554
566
|
#
|
|
555
567
|
def with_connection
|
|
568
|
+
return yield Thread.current[reserved_conn_key] if Thread.current[reserved_conn_key]
|
|
569
|
+
|
|
556
570
|
conn = nil
|
|
557
571
|
loop do
|
|
558
572
|
conn = @connq.pop
|
|
@@ -608,6 +622,15 @@ module AMQP
|
|
|
608
622
|
end
|
|
609
623
|
end
|
|
610
624
|
|
|
625
|
+
def log_give_up(reconnect_attempts, err)
|
|
626
|
+
message = "gave up reconnecting after #{reconnect_attempts} attempts: #{err.inspect}"
|
|
627
|
+
if @logger
|
|
628
|
+
@logger.error("#{lifecycle_prefix}: #{message}")
|
|
629
|
+
else
|
|
630
|
+
warn "AMQP-Client #{message}"
|
|
631
|
+
end
|
|
632
|
+
end
|
|
633
|
+
|
|
611
634
|
def thread_name(role)
|
|
612
635
|
@name ? "amqp.#{role}[#{@name}]" : "amqp.#{role}"
|
|
613
636
|
end
|
|
@@ -616,6 +639,74 @@ module AMQP
|
|
|
616
639
|
@name ? "AMQP::Client[#{@name}]" : "AMQP::Client"
|
|
617
640
|
end
|
|
618
641
|
|
|
642
|
+
def restore_connection(conn)
|
|
643
|
+
conn.channel(1)
|
|
644
|
+
# Snapshot because @consumers can mutate while recovery is running.
|
|
645
|
+
@consumers.values.each do |consumer| # rubocop:disable Style/HashEachMethods
|
|
646
|
+
ch = conn.channel
|
|
647
|
+
ch.basic_qos(consumer.prefetch)
|
|
648
|
+
consume_ok = ch.basic_consume(consumer.queue,
|
|
649
|
+
**consumer.basic_consume_args,
|
|
650
|
+
&consumer.block)
|
|
651
|
+
consumer.update_consume_ok(consume_ok, ch.id)
|
|
652
|
+
@consumers.delete(consumer.id) if consumer.closed?
|
|
653
|
+
rescue Error::ChannelClosed => e
|
|
654
|
+
log_lifecycle(:warn, "failed to resubscribe consumer for #{consumer.queue}: #{e.message}")
|
|
655
|
+
@consumers.delete(consumer.id)
|
|
656
|
+
end
|
|
657
|
+
run_on_connect_hook(conn)
|
|
658
|
+
@connq << conn
|
|
659
|
+
end
|
|
660
|
+
|
|
661
|
+
def server_named_queue?(name)
|
|
662
|
+
name.nil? || name.empty?
|
|
663
|
+
end
|
|
664
|
+
|
|
665
|
+
# Thread-local (not an ivar) so the hook can call back into the client's API without
|
|
666
|
+
# deadlocking, and overlapping reconnects can't clobber each other's reservation.
|
|
667
|
+
def run_on_connect_hook(conn)
|
|
668
|
+
return unless @on_connect
|
|
669
|
+
|
|
670
|
+
Thread.current[reserved_conn_key] = conn
|
|
671
|
+
@on_connect.call(self)
|
|
672
|
+
rescue StandardError => e
|
|
673
|
+
if @logger
|
|
674
|
+
log_lifecycle(:warn, "on_connect raised: #{e.class}: #{e.message}")
|
|
675
|
+
else
|
|
676
|
+
warn "AMQP-Client on_connect error: #{e.inspect}"
|
|
677
|
+
end
|
|
678
|
+
ensure
|
|
679
|
+
Thread.current[reserved_conn_key] = nil
|
|
680
|
+
end
|
|
681
|
+
|
|
682
|
+
# Scoped per instance so a thread touching two Client objects can't cross-wire connections.
|
|
683
|
+
def reserved_conn_key
|
|
684
|
+
:"amqp_client_conn_#{object_id}"
|
|
685
|
+
end
|
|
686
|
+
|
|
687
|
+
# Reports whether the supervisor should give up after this reconnect failure, logging and
|
|
688
|
+
# calling on_failed as a side effect when it does.
|
|
689
|
+
def give_up_reconnecting?(reconnect_attempts, err)
|
|
690
|
+
return false unless @max_retries && reconnect_attempts >= @max_retries
|
|
691
|
+
|
|
692
|
+
@stopped = true
|
|
693
|
+
log_give_up(reconnect_attempts, err)
|
|
694
|
+
run_on_failed_hook(err)
|
|
695
|
+
true
|
|
696
|
+
end
|
|
697
|
+
|
|
698
|
+
def run_on_failed_hook(err)
|
|
699
|
+
return unless @on_failed
|
|
700
|
+
|
|
701
|
+
@on_failed.call(err)
|
|
702
|
+
rescue StandardError => e
|
|
703
|
+
if @logger
|
|
704
|
+
log_lifecycle(:warn, "on_failed raised: #{e.class}: #{e.message}")
|
|
705
|
+
else
|
|
706
|
+
warn "AMQP-Client on_failed error: #{e.inspect}"
|
|
707
|
+
end
|
|
708
|
+
end
|
|
709
|
+
|
|
619
710
|
def default_content_properties
|
|
620
711
|
{
|
|
621
712
|
content_type: @default_content_type,
|
metadata
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: amqp-client
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 2.
|
|
4
|
+
version: 2.2.0
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- CloudAMQP
|
|
@@ -9,7 +9,9 @@ bindir: bin
|
|
|
9
9
|
cert_chain: []
|
|
10
10
|
date: 1980-01-02 00:00:00.000000000 Z
|
|
11
11
|
dependencies: []
|
|
12
|
-
description:
|
|
12
|
+
description: A modern AMQP 0-9-1 Ruby client for RabbitMQ, LavinMQ and any other AMQP
|
|
13
|
+
0-9-1 broker. Very fast, fully thread-safe, with blocking operations, straight-forward
|
|
14
|
+
error handling and no dependencies.
|
|
13
15
|
email:
|
|
14
16
|
- team@cloudamqp.com
|
|
15
17
|
executables: []
|
|
@@ -41,6 +43,8 @@ metadata:
|
|
|
41
43
|
homepage_uri: https://github.com/cloudamqp/amqp-client.rb
|
|
42
44
|
source_code_uri: https://github.com/cloudamqp/amqp-client.rb.git
|
|
43
45
|
changelog_uri: https://github.com/cloudamqp/amqp-client.rb/blob/main/CHANGELOG.md
|
|
46
|
+
documentation_uri: https://cloudamqp.github.io/amqp-client.rb/
|
|
47
|
+
bug_tracker_uri: https://github.com/cloudamqp/amqp-client.rb/issues
|
|
44
48
|
rubygems_mfa_required: 'true'
|
|
45
49
|
rdoc_options: []
|
|
46
50
|
require_paths:
|
|
@@ -56,7 +60,8 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
|
56
60
|
- !ruby/object:Gem::Version
|
|
57
61
|
version: '0'
|
|
58
62
|
requirements: []
|
|
59
|
-
rubygems_version: 4.0.
|
|
63
|
+
rubygems_version: 4.0.16
|
|
60
64
|
specification_version: 4
|
|
61
|
-
summary: AMQP 0-9-1 client
|
|
65
|
+
summary: Modern, fast and dependency-free AMQP 0-9-1 Ruby client for RabbitMQ and
|
|
66
|
+
LavinMQ
|
|
62
67
|
test_files: []
|