bunny 2.23.0 → 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.
Files changed (44) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +38 -36
  3. data/lib/amq/protocol/extensions.rb +2 -0
  4. data/lib/bunny/authentication/credentials_encoder.rb +2 -0
  5. data/lib/bunny/authentication/external_mechanism_encoder.rb +2 -0
  6. data/lib/bunny/authentication/plain_mechanism_encoder.rb +2 -0
  7. data/lib/bunny/channel.rb +778 -150
  8. data/lib/bunny/channel_id_allocator.rb +2 -0
  9. data/lib/bunny/concurrent/atomic_fixnum.rb +2 -0
  10. data/lib/bunny/concurrent/condition.rb +2 -0
  11. data/lib/bunny/concurrent/continuation_queue.rb +2 -0
  12. data/lib/bunny/concurrent/exception_accumulator.rb +115 -0
  13. data/lib/bunny/concurrent/synchronized_sorted_set.rb +2 -0
  14. data/lib/bunny/consumer.rb +4 -11
  15. data/lib/bunny/consumer_tag_generator.rb +2 -0
  16. data/lib/bunny/consumer_work_pool.rb +2 -0
  17. data/lib/bunny/cruby/socket.rb +36 -2
  18. data/lib/bunny/cruby/ssl_socket.rb +44 -1
  19. data/lib/bunny/delivery_info.rb +23 -15
  20. data/lib/bunny/exceptions.rb +33 -2
  21. data/lib/bunny/exchange.rb +27 -13
  22. data/lib/bunny/framing.rb +2 -0
  23. data/lib/bunny/get_response.rb +20 -14
  24. data/lib/bunny/heartbeat_sender.rb +4 -2
  25. data/lib/bunny/message_properties.rb +2 -0
  26. data/lib/bunny/queue.rb +31 -39
  27. data/lib/bunny/reader_loop.rb +9 -7
  28. data/lib/bunny/return_info.rb +18 -11
  29. data/lib/bunny/session.rb +387 -63
  30. data/lib/bunny/socket.rb +7 -12
  31. data/lib/bunny/ssl_socket.rb +7 -12
  32. data/lib/bunny/test_kit.rb +1 -0
  33. data/lib/bunny/timeout.rb +2 -0
  34. data/lib/bunny/timestamp.rb +3 -1
  35. data/lib/bunny/topology_recovery_filter.rb +71 -0
  36. data/lib/bunny/topology_registry.rb +824 -0
  37. data/lib/bunny/transport.rb +54 -49
  38. data/lib/bunny/version.rb +2 -1
  39. data/lib/bunny.rb +2 -1
  40. metadata +25 -14
  41. data/lib/bunny/concurrent/linked_continuation_queue.rb +0 -61
  42. data/lib/bunny/jruby/socket.rb +0 -57
  43. data/lib/bunny/jruby/ssl_socket.rb +0 -58
  44. data/lib/bunny/versioned_delivery_tag.rb +0 -28
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require "socket"
2
4
  require "thread"
3
5
  require "monitor"
@@ -70,7 +72,7 @@ module Bunny
70
72
  @read_timeout = opts[:read_timeout] || DEFAULT_READ_TIMEOUT
71
73
  @read_timeout = nil if @read_timeout == 0
72
74
 
73
- @write_timeout = opts[:socket_timeout] # Backwards compatability
75
+ @write_timeout = opts[:socket_timeout] # Backwards compatibility
74
76
 
75
77
  @write_timeout ||= opts[:write_timeout] || DEFAULT_WRITE_TIMEOUT
76
78
  @write_timeout = nil if @write_timeout == 0
@@ -151,51 +153,26 @@ module Bunny
151
153
  block.call(@tls_context) if @tls_context
152
154
  end
153
155
 
154
- if defined?(JRUBY_VERSION)
155
- # Writes data to the socket.
156
- def write(data)
157
- return write_without_timeout(data) unless @write_timeout
156
+ # Writes data to the socket. If read/write timeout was specified the operation will return after that
157
+ # amount of time has elapsed waiting for the socket.
158
+ def write(data)
159
+ return write_without_timeout(data) unless @write_timeout
158
160
 
159
- begin
160
- if open?
161
- @writes_mutex.synchronize do
162
- @socket.write(data)
163
- end
164
- end
165
- rescue SystemCallError, Timeout::Error, Bunny::ConnectionError, IOError => e
166
- @logger.error "Got an exception when sending data: #{e.message} (#{e.class.name})"
167
- close
168
- @status = :not_connected
169
-
170
- if @session.automatically_recover?
171
- @session.handle_network_failure(e)
172
- else
173
- @session_error_handler.raise(Bunny::NetworkFailure.new("detected a network failure: #{e.message}", e))
161
+ begin
162
+ if open?
163
+ @writes_mutex.synchronize do
164
+ @socket.write_nonblock_fully(data, @write_timeout)
174
165
  end
175
166
  end
176
- end
177
- else
178
- # Writes data to the socket. If read/write timeout was specified the operation will return after that
179
- # amount of time has elapsed waiting for the socket.
180
- def write(data)
181
- return write_without_timeout(data) unless @write_timeout
167
+ rescue SystemCallError, Timeout::Error, Bunny::ConnectionError, IOError => e
168
+ @logger.error "Got an exception when sending data: #{e.message} (#{e.class.name})"
169
+ close
170
+ @status = :not_connected
182
171
 
183
- begin
184
- if open?
185
- @writes_mutex.synchronize do
186
- @socket.write_nonblock_fully(data, @write_timeout)
187
- end
188
- end
189
- rescue SystemCallError, Timeout::Error, Bunny::ConnectionError, IOError => e
190
- @logger.error "Got an exception when sending data: #{e.message} (#{e.class.name})"
191
- close
192
- @status = :not_connected
193
-
194
- if @session.automatically_recover?
195
- @session.handle_network_failure(e)
196
- else
197
- @session_error_handler.raise(Bunny::NetworkFailure.new("detected a network failure: #{e.message}", e))
198
- end
172
+ if @session.automatically_recover?
173
+ @session.handle_network_failure(e)
174
+ else
175
+ @session_error_handler.raise(Bunny::NetworkFailure.new("detected a network failure: #{e.message}", e))
199
176
  end
200
177
  end
201
178
  end
@@ -203,8 +180,10 @@ module Bunny
203
180
  # Writes data to the socket without timeout checks
204
181
  def write_without_timeout(data, raise_exceptions = false)
205
182
  begin
206
- @writes_mutex.synchronize { @socket.write(data) }
207
- @socket.flush
183
+ @writes_mutex.synchronize do
184
+ @socket.write(data)
185
+ @socket.flush
186
+ end
208
187
  rescue SystemCallError, Bunny::ConnectionError, IOError => e
209
188
  close
210
189
  raise e if raise_exceptions
@@ -223,7 +202,7 @@ module Bunny
223
202
  # @private
224
203
  def send_frame(frame)
225
204
  if closed?
226
- @session.handle_network_failure(ConnectionClosedError.new(frame))
205
+ @session.handle_network_failure(ConnectionClosedError.new(frame)) if @session.automatically_recover?
227
206
  else
228
207
  write(frame.encode)
229
208
  end
@@ -235,7 +214,7 @@ module Bunny
235
214
  # @private
236
215
  def send_frame_without_timeout(frame)
237
216
  if closed?
238
- @session.handle_network_failure(ConnectionClosedError.new(frame))
217
+ @session.handle_network_failure(ConnectionClosedError.new(frame)) if @session.automatically_recover?
239
218
  else
240
219
  write_without_timeout(frame.encode)
241
220
  end
@@ -282,7 +261,10 @@ module Bunny
282
261
  # Exposed primarily for Bunny::Channel
283
262
  # @private
284
263
  def read_next_frame(opts = {})
285
- header = read_fully(7)
264
+ @frame_header_buffer ||= String.new(capacity: 7)
265
+ @frame_header_buffer.clear
266
+ header = read_fully_into(@frame_header_buffer, 7)
267
+
286
268
  type, channel, size = AMQ::Protocol::Frame.decode_header(header)
287
269
  payload = if size > 0
288
270
  read_fully(size)
@@ -301,8 +283,26 @@ module Bunny
301
283
  AMQ::Protocol::Frame.new(type, payload, channel)
302
284
  end
303
285
 
286
+ # @private
287
+ def read_fully_into(buffer, count)
288
+ begin
289
+ @socket.read_fully_into(buffer, count, @read_timeout)
290
+ rescue SystemCallError, Timeout::Error, Bunny::ConnectionError, IOError => e
291
+ @logger.error "Got an exception when receiving data: #{e.message} (#{e.class.name})"
292
+ close
293
+ @status = :not_connected
304
294
 
305
- def self.reacheable?(host, port, timeout)
295
+ if @session.automatically_recover?
296
+ raise
297
+ else
298
+ @session_error_handler.raise(Bunny::NetworkFailure.new("detected a network failure: #{e.message}", e))
299
+ end
300
+ end
301
+ buffer
302
+ end
303
+
304
+
305
+ def self.reachable?(host, port, timeout)
306
306
  begin
307
307
  s = Bunny::SocketImpl.open(host, port,
308
308
  :connect_timeout => timeout)
@@ -315,11 +315,16 @@ module Bunny
315
315
  end
316
316
  end
317
317
 
318
+ class << self
319
+ alias_method :reacheable?, :reachable?
320
+ end
321
+
318
322
  def self.ping!(host, port, timeout)
319
- raise ConnectionTimeout.new("#{host}:#{port} is unreachable") if !reacheable?(host, port, timeout)
323
+ raise ConnectionTimeout.new("#{host}:#{port} is unreachable") if !reachable?(host, port, timeout)
320
324
  end
321
325
 
322
326
  def initialize_socket
327
+ @logger.debug("Using connection timeout of #{@connect_timeout} when connecting to #{@host}:#{@port}")
323
328
  begin
324
329
  @socket = Bunny::SocketImpl.open(@host, @port,
325
330
  :keepalive => @opts[:keepalive],
data/lib/bunny/version.rb CHANGED
@@ -1,6 +1,7 @@
1
1
  # encoding: utf-8
2
+ # frozen_string_literal: true
2
3
 
3
4
  module Bunny
4
5
  # @return [String] Version of the library
5
- VERSION = "2.23.0"
6
+ VERSION = "3.0.0"
6
7
  end
data/lib/bunny.rb CHANGED
@@ -1,4 +1,5 @@
1
1
  # -*- encoding: utf-8; mode: ruby -*-
2
+ # frozen_string_literal: true
2
3
 
3
4
  require "timeout"
4
5
 
@@ -88,7 +89,7 @@ module Bunny
88
89
  # @option connection_string_or_opts [Proc] :recovery_attempt_started (nil) Will be called before every connection recovery attempt
89
90
  # @option connection_string_or_opts [Proc] :recovery_completed (nil) Will be called after successful connection recovery
90
91
  # @option connection_string_or_opts [Boolean] :recover_from_connection_close (true) Should this connection recover after receiving a server-sent connection.close (e.g. connection was force closed)?
91
- # @option connection_string_or_opts [Object] :session_error_handler (Thread.current) Object which responds to #raise that will act as a session error handler. Defaults to Thread.current, which will raise asynchronous exceptions in the thread that created the session.
92
+ # @option connection_string_or_opts [Object] :session_error_handler (ExceptionAccumulator.new) Object which responds to #raise that will act as a session error handler. Defaults to a Bunny::ExceptionAccumulator instance which stores exceptions for safe retrieval later. Can be set to Thread.current for legacy behavior (raises asynchronous exceptions in the creating thread).
92
93
  #
93
94
  # @option optz [String] :auth_mechanism ("PLAIN") Authentication mechanism, PLAIN or EXTERNAL
94
95
  # @option optz [String] :locale ("PLAIN") Locale RabbitMQ should use
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: bunny
3
3
  version: !ruby/object:Gem::Version
4
- version: 2.23.0
4
+ version: 3.0.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Chris Duncan
@@ -9,10 +9,9 @@ authors:
9
9
  - Jakub Stastny aka botanicus
10
10
  - Michael S. Klishin
11
11
  - Stefan Kaes
12
- autorequire:
13
12
  bindir: bin
14
13
  cert_chain: []
15
- date: 2024-07-01 00:00:00.000000000 Z
14
+ date: 1980-01-02 00:00:00.000000000 Z
16
15
  dependencies:
17
16
  - !ruby/object:Gem::Dependency
18
17
  name: amq-protocol
@@ -20,20 +19,34 @@ dependencies:
20
19
  requirements:
21
20
  - - "~>"
22
21
  - !ruby/object:Gem::Version
23
- version: '2.3'
22
+ version: '2.7'
23
+ type: :runtime
24
+ prerelease: false
25
+ version_requirements: !ruby/object:Gem::Requirement
26
+ requirements:
27
+ - - "~>"
28
+ - !ruby/object:Gem::Version
29
+ version: '2.7'
30
+ - !ruby/object:Gem::Dependency
31
+ name: logger
32
+ requirement: !ruby/object:Gem::Requirement
33
+ requirements:
34
+ - - "~>"
35
+ - !ruby/object:Gem::Version
36
+ version: '1'
24
37
  - - ">="
25
38
  - !ruby/object:Gem::Version
26
- version: 2.3.1
39
+ version: '1.7'
27
40
  type: :runtime
28
41
  prerelease: false
29
42
  version_requirements: !ruby/object:Gem::Requirement
30
43
  requirements:
31
44
  - - "~>"
32
45
  - !ruby/object:Gem::Version
33
- version: '2.3'
46
+ version: '1'
34
47
  - - ">="
35
48
  - !ruby/object:Gem::Version
36
- version: 2.3.1
49
+ version: '1.7'
37
50
  - !ruby/object:Gem::Dependency
38
51
  name: sorted_set
39
52
  requirement: !ruby/object:Gem::Requirement
@@ -74,7 +87,7 @@ files:
74
87
  - lib/bunny/concurrent/atomic_fixnum.rb
75
88
  - lib/bunny/concurrent/condition.rb
76
89
  - lib/bunny/concurrent/continuation_queue.rb
77
- - lib/bunny/concurrent/linked_continuation_queue.rb
90
+ - lib/bunny/concurrent/exception_accumulator.rb
78
91
  - lib/bunny/concurrent/synchronized_sorted_set.rb
79
92
  - lib/bunny/consumer.rb
80
93
  - lib/bunny/consumer_tag_generator.rb
@@ -87,8 +100,6 @@ files:
87
100
  - lib/bunny/framing.rb
88
101
  - lib/bunny/get_response.rb
89
102
  - lib/bunny/heartbeat_sender.rb
90
- - lib/bunny/jruby/socket.rb
91
- - lib/bunny/jruby/ssl_socket.rb
92
103
  - lib/bunny/message_properties.rb
93
104
  - lib/bunny/queue.rb
94
105
  - lib/bunny/reader_loop.rb
@@ -99,15 +110,16 @@ files:
99
110
  - lib/bunny/test_kit.rb
100
111
  - lib/bunny/timeout.rb
101
112
  - lib/bunny/timestamp.rb
113
+ - lib/bunny/topology_recovery_filter.rb
114
+ - lib/bunny/topology_registry.rb
102
115
  - lib/bunny/transport.rb
103
116
  - lib/bunny/version.rb
104
- - lib/bunny/versioned_delivery_tag.rb
105
117
  homepage: http://rubybunny.info
106
118
  licenses:
107
119
  - MIT
108
120
  metadata:
109
121
  changelog_uri: https://github.com/ruby-amqp/bunny/blob/main/ChangeLog.md
110
- post_install_message:
122
+ source_code_uri: https://github.com/ruby-amqp/bunny/
111
123
  rdoc_options: []
112
124
  require_paths:
113
125
  - lib
@@ -122,8 +134,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
122
134
  - !ruby/object:Gem::Version
123
135
  version: '0'
124
136
  requirements: []
125
- rubygems_version: 3.5.9
126
- signing_key:
137
+ rubygems_version: 4.0.6
127
138
  specification_version: 4
128
139
  summary: Popular easy to use Ruby client for RabbitMQ
129
140
  test_files: []
@@ -1,61 +0,0 @@
1
- if !defined?(JRUBY_VERSION)
2
- raise "Bunny::Concurrent::LinkedContinuationQueue can only be used on JRuby!"
3
- end
4
-
5
- require "java"
6
-
7
- java_import java.util.concurrent.LinkedBlockingQueue
8
- java_import java.util.concurrent.TimeUnit
9
-
10
- module Bunny
11
- module Concurrent
12
- # Continuation queue implementation for JRuby.
13
- #
14
- # On JRuby, we'd rather use reliable and heavily battle tested j.u.c.
15
- # primitives with well described semantics than informally specified, clumsy
16
- # and limited Ruby standard library parts.
17
- #
18
- # This is an implementation of the continuation queue on top of the linked blocking
19
- # queue in j.u.c.
20
- #
21
- # Compared to the Ruby standard library Queue, there is one limitation: you cannot
22
- # push a nil on the queue, it will fail with a null pointer exception.
23
- # @private
24
- class LinkedContinuationQueue
25
- def initialize(*args, &block)
26
- @q = LinkedBlockingQueue.new
27
- end
28
-
29
- def push(el, timeout_in_ms = nil)
30
- if timeout_in_ms
31
- @q.offer(el, timeout_in_ms, TimeUnit::MILLISECONDS)
32
- else
33
- @q.offer(el)
34
- end
35
- end
36
- alias << push
37
-
38
- def pop
39
- @q.take
40
- end
41
-
42
- def poll(timeout_in_ms = nil)
43
- if timeout_in_ms
44
- v = @q.poll(timeout_in_ms, TimeUnit::MILLISECONDS)
45
- raise ::Timeout::Error.new("operation did not finish in #{timeout_in_ms} ms") if v.nil?
46
- v
47
- else
48
- @q.poll
49
- end
50
- end
51
-
52
- def clear
53
- @q.clear
54
- end
55
-
56
- def method_missing(selector, *args, &block)
57
- @q.__send__(selector, *args, &block)
58
- end
59
- end
60
- end
61
- end
@@ -1,57 +0,0 @@
1
- require "bunny/cruby/socket"
2
-
3
- module Bunny
4
- module JRuby
5
- # TCP socket extension that uses Socket#readpartial to avoid excessive CPU
6
- # burn after some time. See issue #165.
7
- # @private
8
- module Socket
9
- include Bunny::Socket
10
-
11
- def self.open(host, port, options = {})
12
- socket = ::Socket.tcp(host, port, nil, nil,
13
- connect_timeout: options[:connect_timeout])
14
- if ::Socket.constants.include?('TCP_NODELAY') || ::Socket.constants.include?(:TCP_NODELAY)
15
- socket.setsockopt(::Socket::IPPROTO_TCP, ::Socket::TCP_NODELAY, true)
16
- end
17
- socket.setsockopt(::Socket::SOL_SOCKET, ::Socket::SO_KEEPALIVE, true) if options.fetch(:keepalive, true)
18
- socket.extend self
19
- socket.options = { :host => host, :port => port }.merge(options)
20
- socket
21
- rescue Errno::ETIMEDOUT
22
- raise ClientTimeout
23
- end
24
-
25
- # Reads given number of bytes with an optional timeout
26
- #
27
- # @param [Integer] count How many bytes to read
28
- # @param [Integer] timeout Timeout
29
- #
30
- # @return [String] Data read from the socket
31
- # @api public
32
- def read_fully(count, timeout = nil)
33
- value = ''
34
-
35
- begin
36
- loop do
37
- value << read_nonblock(count - value.bytesize)
38
- break if value.bytesize >= count
39
- end
40
- rescue EOFError
41
- # JRuby specific fix via https://github.com/jruby/jruby/issues/1694#issuecomment-54873532
42
- IO.select([self], nil, nil, timeout)
43
- retry
44
- rescue *READ_RETRY_EXCEPTION_CLASSES
45
- if IO.select([self], nil, nil, timeout)
46
- retry
47
- else
48
- raise Timeout::Error, "IO timeout when reading #{count} bytes"
49
- end
50
- end
51
-
52
- value
53
- end # read_fully
54
-
55
- end
56
- end
57
- end
@@ -1,58 +0,0 @@
1
- module Bunny
2
- module JRuby
3
- begin
4
- require "bunny/cruby/ssl_socket"
5
- require "openssl"
6
-
7
- # TLS-enabled TCP socket that implements convenience
8
- # methods found in Bunny::Socket.
9
- class SSLSocket < Bunny::SSLSocket
10
-
11
- def initialize(*args)
12
- super
13
- @__bunny_socket_eof_flag__ = false
14
- end
15
-
16
- # Reads given number of bytes with an optional timeout
17
- #
18
- # @param [Integer] count How many bytes to read
19
- # @param [Integer] timeout Timeout
20
- #
21
- # @return [String] Data read from the socket
22
- # @api public
23
- def read_fully(count, timeout = nil)
24
- return nil if @__bunny_socket_eof_flag__
25
-
26
- value = ''
27
- begin
28
- loop do
29
- value << read_nonblock(count - value.bytesize)
30
- break if value.bytesize >= count
31
- end
32
- rescue EOFError => e
33
- @__bunny_socket_eof_flag__ = true
34
- rescue OpenSSL::SSL::SSLError => e
35
- if e.message == "read would block"
36
- if IO.select([self], nil, nil, timeout)
37
- retry
38
- else
39
- raise Timeout::Error, "IO timeout when reading #{count} bytes"
40
- end
41
- else
42
- raise e
43
- end
44
- rescue *READ_RETRY_EXCEPTION_CLASSES => e
45
- if IO.select([self], nil, nil, timeout)
46
- retry
47
- else
48
- raise Timeout::Error, "IO timeout when reading #{count} bytes"
49
- end
50
- end
51
- value
52
- end
53
- end
54
- rescue LoadError => le
55
- puts "Could not load OpenSSL"
56
- end
57
- end
58
- end
@@ -1,28 +0,0 @@
1
- module Bunny
2
- # Wraps a delivery tag (which is an integer) so that {Bunny::Channel} could
3
- # detect stale tags after connection recovery.
4
- #
5
- # @private
6
- class VersionedDeliveryTag
7
- attr_reader :tag
8
- attr_reader :version
9
-
10
- def initialize(tag, version)
11
- raise ArgumentError.new("tag cannot be nil") unless tag
12
- raise ArgumentError.new("version cannot be nil") unless version
13
-
14
- @tag = tag.to_i
15
- @version = version.to_i
16
- end
17
-
18
- def to_i
19
- @tag
20
- end
21
-
22
- def stale?(version)
23
- raise ArgumentError.new("version cannot be nil") unless version
24
-
25
- @version < version.to_i
26
- end
27
- end
28
- end