garaio_bunny 2.19.1

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 (40) hide show
  1. checksums.yaml +7 -0
  2. data/README.md +231 -0
  3. data/lib/amq/protocol/extensions.rb +16 -0
  4. data/lib/bunny/authentication/credentials_encoder.rb +55 -0
  5. data/lib/bunny/authentication/external_mechanism_encoder.rb +27 -0
  6. data/lib/bunny/authentication/plain_mechanism_encoder.rb +19 -0
  7. data/lib/bunny/channel.rb +2055 -0
  8. data/lib/bunny/channel_id_allocator.rb +82 -0
  9. data/lib/bunny/concurrent/atomic_fixnum.rb +75 -0
  10. data/lib/bunny/concurrent/condition.rb +66 -0
  11. data/lib/bunny/concurrent/continuation_queue.rb +62 -0
  12. data/lib/bunny/concurrent/linked_continuation_queue.rb +61 -0
  13. data/lib/bunny/concurrent/synchronized_sorted_set.rb +56 -0
  14. data/lib/bunny/consumer.rb +128 -0
  15. data/lib/bunny/consumer_tag_generator.rb +23 -0
  16. data/lib/bunny/consumer_work_pool.rb +122 -0
  17. data/lib/bunny/cruby/socket.rb +110 -0
  18. data/lib/bunny/cruby/ssl_socket.rb +118 -0
  19. data/lib/bunny/delivery_info.rb +93 -0
  20. data/lib/bunny/exceptions.rb +269 -0
  21. data/lib/bunny/exchange.rb +275 -0
  22. data/lib/bunny/framing.rb +56 -0
  23. data/lib/bunny/get_response.rb +83 -0
  24. data/lib/bunny/heartbeat_sender.rb +71 -0
  25. data/lib/bunny/jruby/socket.rb +57 -0
  26. data/lib/bunny/jruby/ssl_socket.rb +58 -0
  27. data/lib/bunny/message_properties.rb +119 -0
  28. data/lib/bunny/queue.rb +393 -0
  29. data/lib/bunny/reader_loop.rb +158 -0
  30. data/lib/bunny/return_info.rb +74 -0
  31. data/lib/bunny/session.rb +1483 -0
  32. data/lib/bunny/socket.rb +14 -0
  33. data/lib/bunny/ssl_socket.rb +14 -0
  34. data/lib/bunny/test_kit.rb +41 -0
  35. data/lib/bunny/timeout.rb +7 -0
  36. data/lib/bunny/transport.rb +526 -0
  37. data/lib/bunny/version.rb +6 -0
  38. data/lib/bunny/versioned_delivery_tag.rb +28 -0
  39. data/lib/bunny.rb +92 -0
  40. metadata +127 -0
@@ -0,0 +1,14 @@
1
+ # See #165. MK.
2
+ if defined?(JRUBY_VERSION)
3
+ require "bunny/jruby/socket"
4
+
5
+ module Bunny
6
+ SocketImpl = JRuby::Socket
7
+ end
8
+ else
9
+ require "bunny/cruby/socket"
10
+
11
+ module Bunny
12
+ SocketImpl = Socket
13
+ end
14
+ end
@@ -0,0 +1,14 @@
1
+ # See #165. MK.
2
+ if defined?(JRUBY_VERSION)
3
+ require "bunny/jruby/ssl_socket"
4
+
5
+ module Bunny
6
+ SSLSocketImpl = JRuby::SSLSocket
7
+ end
8
+ else
9
+ require "bunny/cruby/ssl_socket"
10
+
11
+ module Bunny
12
+ SSLSocketImpl = SSLSocket
13
+ end
14
+ end
@@ -0,0 +1,41 @@
1
+ # -*- coding: utf-8 -*-
2
+
3
+ require "timeout"
4
+
5
+ module Bunny
6
+ # Unit, integration and stress testing toolkit
7
+ class TestKit
8
+ class << self
9
+
10
+ def poll_while(timeout = 60, &probe)
11
+ Timeout.timeout(timeout) {
12
+ sleep 0.1 while probe.call
13
+ }
14
+ end
15
+ def poll_until(timeout = 60, &probe)
16
+ Timeout.timeout(timeout) {
17
+ sleep 0.1 until probe.call
18
+ }
19
+ end
20
+
21
+ # @return [Integer] Random integer in the range of [a, b]
22
+ # @api private
23
+ def random_in_range(a, b)
24
+ Range.new(a, b).to_a.sample
25
+ end
26
+
27
+ # @param [Integer] a Lower bound of message size, in KB
28
+ # @param [Integer] b Upper bound of message size, in KB
29
+ # @param [Integer] i Random number to use in message generation
30
+ # @return [String] Message payload of length in the given range, with non-ASCII characters
31
+ # @api public
32
+ def message_in_kb(a, b, i)
33
+ s = "Ю#{i}"
34
+ n = random_in_range(a, b) / s.bytesize
35
+
36
+ s * n * 1024
37
+ end
38
+
39
+ end
40
+ end
41
+ end
@@ -0,0 +1,7 @@
1
+ module Bunny
2
+ Timeout = ::Timeout
3
+
4
+ # Backwards compatibility
5
+ # @private
6
+ Timer = Timeout
7
+ end
@@ -0,0 +1,526 @@
1
+ require "socket"
2
+ require "thread"
3
+ require "monitor"
4
+
5
+ begin
6
+ require "openssl"
7
+ rescue LoadError => _le
8
+ $stderr.puts "Could not load OpenSSL"
9
+ end
10
+
11
+ require "bunny/exceptions"
12
+ require "bunny/socket"
13
+
14
+ module Bunny
15
+ # @private
16
+ class Transport
17
+
18
+ #
19
+ # API
20
+ #
21
+
22
+ # Default TCP connection timeout
23
+ DEFAULT_CONNECTION_TIMEOUT = 30.0
24
+
25
+ DEFAULT_READ_TIMEOUT = 30.0
26
+ DEFAULT_WRITE_TIMEOUT = 30.0
27
+
28
+ attr_reader :session, :host, :port, :socket, :connect_timeout, :read_timeout, :write_timeout, :disconnect_timeout
29
+ attr_reader :tls_context, :verify_peer, :tls_ca_certificates, :tls_certificate_path, :tls_key_path
30
+
31
+ def read_timeout=(v)
32
+ @read_timeout = v
33
+ @read_timeout = nil if @read_timeout == 0
34
+ end
35
+
36
+ def initialize(session, host, port, opts)
37
+ @session = session
38
+ @session_error_handler = opts[:session_error_handler]
39
+ @host = host
40
+ @port = port
41
+ @opts = opts
42
+
43
+ @logger = session.logger
44
+ @tls_enabled = tls_enabled?(opts)
45
+
46
+ @read_timeout = opts[:read_timeout] || DEFAULT_READ_TIMEOUT
47
+ @read_timeout = nil if @read_timeout == 0
48
+
49
+ @write_timeout = opts[:socket_timeout] # Backwards compatability
50
+
51
+ @write_timeout ||= opts[:write_timeout] || DEFAULT_WRITE_TIMEOUT
52
+ @write_timeout = nil if @write_timeout == 0
53
+
54
+ @connect_timeout = self.timeout_from(opts)
55
+ @connect_timeout = nil if @connect_timeout == 0
56
+ @disconnect_timeout = @write_timeout || @read_timeout || @connect_timeout
57
+
58
+ @writes_mutex = @session.mutex_impl.new
59
+
60
+ @socket = nil
61
+
62
+ prepare_tls_context(opts) if @tls_enabled
63
+ end
64
+
65
+ def hostname
66
+ @host
67
+ end
68
+
69
+ def local_address
70
+ @socket.local_address
71
+ end
72
+
73
+ def uses_tls?
74
+ @tls_enabled
75
+ end
76
+ alias tls? uses_tls?
77
+
78
+ def uses_ssl?
79
+ @tls_enabled
80
+ end
81
+ alias ssl? uses_ssl?
82
+
83
+
84
+ def connect
85
+ if uses_tls?
86
+ begin
87
+ @socket.connect
88
+ rescue OpenSSL::SSL::SSLError => e
89
+ @logger.error { "TLS connection failed: #{e.message}" }
90
+ raise e
91
+ end
92
+
93
+ log_peer_certificate_info(Logger::DEBUG, @socket.peer_cert)
94
+ log_peer_certificate_chain_info(Logger::DEBUG, @socket.peer_cert_chain)
95
+
96
+ begin
97
+ @socket.post_connection_check(host) if @verify_peer
98
+ rescue OpenSSL::SSL::SSLError => e
99
+ @logger.error do
100
+ msg = "Peer verification of target server failed: #{e.message}. "
101
+ msg += "Target hostname: #{hostname}, see peer certificate chain details below."
102
+ msg
103
+ end
104
+ log_peer_certificate_info(Logger::ERROR, @socket.peer_cert)
105
+ log_peer_certificate_chain_info(Logger::ERROR, @socket.peer_cert_chain)
106
+
107
+ raise e
108
+ end
109
+
110
+ @status = :connected
111
+
112
+ @socket
113
+ else
114
+ # no-op
115
+ end
116
+ end
117
+
118
+ def connected?
119
+ :connected == @status && open?
120
+ end
121
+
122
+ def configure_socket(&block)
123
+ block.call(@socket) if @socket
124
+ end
125
+
126
+ def configure_tls_context(&block)
127
+ block.call(@tls_context) if @tls_context
128
+ end
129
+
130
+ if defined?(JRUBY_VERSION)
131
+ # Writes data to the socket.
132
+ def write(data)
133
+ return write_without_timeout(data) unless @write_timeout
134
+
135
+ begin
136
+ if open?
137
+ @writes_mutex.synchronize do
138
+ @socket.write(data)
139
+ end
140
+ end
141
+ rescue SystemCallError, Timeout::Error, Bunny::ConnectionError, IOError => e
142
+ @logger.error "Got an exception when sending data: #{e.message} (#{e.class.name})"
143
+ close
144
+ @status = :not_connected
145
+
146
+ if @session.automatically_recover?
147
+ @session.handle_network_failure(e)
148
+ else
149
+ @session_error_handler.raise(Bunny::NetworkFailure.new("detected a network failure: #{e.message}", e))
150
+ end
151
+ end
152
+ end
153
+ else
154
+ # Writes data to the socket. If read/write timeout was specified the operation will return after that
155
+ # amount of time has elapsed waiting for the socket.
156
+ def write(data)
157
+ return write_without_timeout(data) unless @write_timeout
158
+
159
+ begin
160
+ if open?
161
+ @writes_mutex.synchronize do
162
+ @socket.write_nonblock_fully(data, @write_timeout)
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))
174
+ end
175
+ end
176
+ end
177
+ end
178
+
179
+ # Writes data to the socket without timeout checks
180
+ def write_without_timeout(data, raise_exceptions = false)
181
+ begin
182
+ @writes_mutex.synchronize { @socket.write(data) }
183
+ @socket.flush
184
+ rescue SystemCallError, Bunny::ConnectionError, IOError => e
185
+ close
186
+ raise e if raise_exceptions
187
+
188
+ if @session.automatically_recover?
189
+ @session.handle_network_failure(e)
190
+ else
191
+ @session_error_handler.raise(Bunny::NetworkFailure.new("detected a network failure: #{e.message}", e))
192
+ end
193
+ end
194
+ end
195
+
196
+ # Sends frame to the peer.
197
+ #
198
+ # @raise [ConnectionClosedError]
199
+ # @private
200
+ def send_frame(frame)
201
+ if closed?
202
+ @session.handle_network_failure(ConnectionClosedError.new(frame))
203
+ else
204
+ write(frame.encode)
205
+ end
206
+ end
207
+
208
+ # Sends frame to the peer without timeout control.
209
+ #
210
+ # @raise [ConnectionClosedError]
211
+ # @private
212
+ def send_frame_without_timeout(frame)
213
+ if closed?
214
+ @session.handle_network_failure(ConnectionClosedError.new(frame))
215
+ else
216
+ write_without_timeout(frame.encode)
217
+ end
218
+ end
219
+
220
+
221
+ def close(reason = nil)
222
+ @socket.close if open?
223
+ end
224
+
225
+ def open?
226
+ @socket && !@socket.closed?
227
+ end
228
+
229
+ def closed?
230
+ !open?
231
+ end
232
+
233
+ def flush
234
+ @socket.flush if @socket
235
+ end
236
+
237
+ def read_fully(count)
238
+ begin
239
+ @socket.read_fully(count, @read_timeout)
240
+ rescue SystemCallError, Timeout::Error, Bunny::ConnectionError, IOError => e
241
+ @logger.error "Got an exception when receiving data: #{e.message} (#{e.class.name})"
242
+ close
243
+ @status = :not_connected
244
+
245
+ if @session.automatically_recover?
246
+ raise
247
+ else
248
+ @session_error_handler.raise(Bunny::NetworkFailure.new("detected a network failure: #{e.message}", e))
249
+ end
250
+ end
251
+ end
252
+
253
+ def read_ready?(timeout = nil)
254
+ io = IO.select([@socket].compact, nil, nil, timeout)
255
+ io && io[0].include?(@socket)
256
+ end
257
+
258
+ # Exposed primarily for Bunny::Channel
259
+ # @private
260
+ def read_next_frame(opts = {})
261
+ header = read_fully(7)
262
+ type, channel, size = AMQ::Protocol::Frame.decode_header(header)
263
+ payload = if size > 0
264
+ read_fully(size)
265
+ else
266
+ ''
267
+ end
268
+ frame_end = read_fully(1)
269
+
270
+ # 1) the size is miscalculated
271
+ if payload.bytesize != size
272
+ raise BadLengthError.new(size, payload.bytesize)
273
+ end
274
+
275
+ # 2) the size is OK, but the string doesn't end with FINAL_OCTET
276
+ raise NoFinalOctetError.new if frame_end != AMQ::Protocol::Frame::FINAL_OCTET
277
+ AMQ::Protocol::Frame.new(type, payload, channel)
278
+ end
279
+
280
+
281
+ def self.reacheable?(host, port, timeout)
282
+ begin
283
+ s = Bunny::SocketImpl.open(host, port,
284
+ :connect_timeout => timeout)
285
+
286
+ true
287
+ rescue SocketError, Timeout::Error => _e
288
+ false
289
+ ensure
290
+ s.close if s
291
+ end
292
+ end
293
+
294
+ def self.ping!(host, port, timeout)
295
+ raise ConnectionTimeout.new("#{host}:#{port} is unreachable") if !reacheable?(host, port, timeout)
296
+ end
297
+
298
+ def initialize_socket
299
+ begin
300
+ @socket = Bunny::SocketImpl.open(@host, @port,
301
+ :keepalive => @opts[:keepalive],
302
+ :connect_timeout => @connect_timeout)
303
+ rescue StandardError, ClientTimeout => e
304
+ @status = :not_connected
305
+ raise Bunny::TCPConnectionFailed.new(e, self.hostname, self.port)
306
+ end
307
+
308
+ @socket
309
+ end
310
+
311
+ def maybe_initialize_socket
312
+ initialize_socket if !@socket || closed?
313
+ end
314
+
315
+ def post_initialize_socket
316
+ @socket = if uses_tls? and !@socket.is_a?(Bunny::SSLSocketImpl)
317
+ wrap_in_tls_socket(@socket)
318
+ else
319
+ @socket
320
+ end
321
+ end
322
+
323
+ protected
324
+
325
+ def tls_enabled?(opts)
326
+ return !!opts[:tls] unless opts[:tls].nil?
327
+ return !!opts[:ssl] unless opts[:ssl].nil?
328
+ (opts[:port] == AMQ::Protocol::TLS_PORT) || false
329
+ end
330
+
331
+ def tls_ca_certificates_paths_from(opts)
332
+ Array(opts[:cacertfile] || opts[:tls_ca_certificates] || opts[:ssl_ca_certificates])
333
+ end
334
+
335
+ def tls_certificate_path_from(opts)
336
+ opts[:certfile] || opts[:tls_cert] || opts[:ssl_cert] || opts[:tls_cert_path] || opts[:ssl_cert_path] || opts[:tls_certificate_path] || opts[:ssl_certificate_path]
337
+ end
338
+
339
+ def tls_key_path_from(opts)
340
+ opts[:keyfile] || opts[:tls_key] || opts[:ssl_key] || opts[:tls_key_path] || opts[:ssl_key_path]
341
+ end
342
+
343
+ def tls_certificate_from(opts)
344
+ begin
345
+ read_client_certificate!
346
+ rescue MissingTLSCertificateFile => _e
347
+ inline_client_certificate_from(opts)
348
+ end
349
+ end
350
+
351
+ def tls_key_from(opts)
352
+ begin
353
+ read_client_key!
354
+ rescue MissingTLSKeyFile => _e
355
+ inline_client_key_from(opts)
356
+ end
357
+ end
358
+
359
+ def peer_certificate_info(peer_cert, prefix = "Peer's leaf certificate")
360
+ exts = peer_cert.extensions.map { |x| x.value }
361
+ # Subject Alternative Names
362
+ sans = exts.select { |s| s =~ /^DNS/ }.map { |s| s.gsub(/^DNS:/, "") }
363
+
364
+ msg = "#{prefix} subject: #{peer_cert.subject}, "
365
+ msg += "subject alternative names: #{sans.join(', ')}, "
366
+ msg += "issuer: #{peer_cert.issuer}, "
367
+ msg += "not valid after: #{peer_cert.not_after}, "
368
+ msg += "X.509 usage extensions: #{exts.join(', ')}"
369
+
370
+ msg
371
+ end
372
+
373
+ def log_peer_certificate_info(severity, peer_cert, prefix = "Peer's leaf certificate")
374
+ @logger.add(severity) { peer_certificate_info(peer_cert, prefix) }
375
+ end
376
+
377
+ def log_peer_certificate_chain_info(severity, chain)
378
+ chain.each do |cert|
379
+ self.log_peer_certificate_info(severity, cert, "Peer's certificate chain entry")
380
+ end
381
+ end
382
+
383
+ def inline_client_certificate_from(opts)
384
+ opts[:tls_certificate] || opts[:ssl_cert_string] || opts[:tls_cert]
385
+ end
386
+
387
+ def inline_client_key_from(opts)
388
+ opts[:tls_key] || opts[:ssl_key_string]
389
+ end
390
+
391
+ def prepare_tls_context(opts)
392
+ if opts.values_at(:verify_ssl, :verify_peer, :verify).all?(&:nil?)
393
+ opts[:verify_peer] = true
394
+ end
395
+
396
+ # client cert/key paths
397
+ @tls_certificate_path = tls_certificate_path_from(opts)
398
+ @tls_key_path = tls_key_path_from(opts)
399
+ # client cert/key
400
+ @tls_certificate = tls_certificate_from(opts)
401
+ @tls_key = tls_key_from(opts)
402
+ @tls_certificate_store = opts[:tls_certificate_store]
403
+
404
+ @verify_peer = as_boolean(opts[:verify_ssl] || opts[:verify_peer] || opts[:verify])
405
+
406
+ @tls_context = initialize_tls_context(OpenSSL::SSL::SSLContext.new, opts)
407
+ end
408
+
409
+ def as_boolean(val)
410
+ case val
411
+ when true then true
412
+ when false then false
413
+ when "true" then true
414
+ when "false" then false
415
+ else
416
+ !!val
417
+ end
418
+ end
419
+
420
+ def wrap_in_tls_socket(socket)
421
+ raise ArgumentError, "cannot wrap nil into TLS socket, @tls_context is nil. This is a Bunny bug." unless socket
422
+ raise "cannot wrap a socket into TLS socket, @tls_context is nil. This is a Bunny bug." unless @tls_context
423
+
424
+ s = Bunny::SSLSocketImpl.new(socket, @tls_context)
425
+
426
+ # always set the SNI server name if possible since RFC 3546 and RFC 6066 both state
427
+ # that TLS clients supporting the extensions can talk to TLS servers that do not
428
+ s.hostname = @host if s.respond_to?(:hostname)
429
+
430
+ s.sync_close = true
431
+ s
432
+ end
433
+
434
+ def check_local_certificate_path!(s)
435
+ raise MissingTLSCertificateFile, "cannot read client TLS certificate from #{s}" unless File.file?(s) && File.readable?(s)
436
+ end
437
+
438
+ def check_local_key_path!(s)
439
+ raise MissingTLSKeyFile, "cannot read client TLS private key from #{s}" unless File.file?(s) && File.readable?(s)
440
+ end
441
+
442
+ def read_client_certificate!
443
+ if @tls_certificate_path
444
+ check_local_certificate_path!(@tls_certificate_path)
445
+ @tls_certificate = File.read(@tls_certificate_path)
446
+ end
447
+ end
448
+
449
+ def read_client_key!
450
+ if @tls_key_path
451
+ check_local_key_path!(@tls_key_path)
452
+ @tls_key = File.read(@tls_key_path)
453
+ end
454
+ end
455
+
456
+ def initialize_tls_context(ctx, opts = {})
457
+ ctx.cert = OpenSSL::X509::Certificate.new(@tls_certificate) if @tls_certificate
458
+ ctx.key = OpenSSL::PKey.read(@tls_key) if @tls_key
459
+ ctx.cert_store = if @tls_certificate_store
460
+ @tls_certificate_store
461
+ else
462
+ # this ivar exists so that this value can be exposed in the API
463
+ @tls_ca_certificates = tls_ca_certificates_paths_from(opts)
464
+ initialize_tls_certificate_store(@tls_ca_certificates)
465
+ end
466
+ should_silence_warnings = opts.fetch(:tls_silence_warnings, false)
467
+
468
+ if !@tls_certificate && !should_silence_warnings
469
+ @logger.warn <<-MSG
470
+ Using TLS but no client certificate is provided. If RabbitMQ is configured to require & verify peer
471
+ certificate, connection will be rejected. Learn more at https://www.rabbitmq.com/ssl.html
472
+ MSG
473
+ end
474
+ if @tls_certificate && !@tls_key
475
+ @logger.warn "Using TLS but no client private key is provided!"
476
+ end
477
+
478
+ verify_mode = if @verify_peer
479
+ OpenSSL::SSL::VERIFY_PEER|OpenSSL::SSL::VERIFY_FAIL_IF_NO_PEER_CERT
480
+ else
481
+ OpenSSL::SSL::VERIFY_NONE
482
+ end
483
+ @logger.debug { "Will use peer verification mode #{verify_mode}" }
484
+ ctx.verify_mode = verify_mode
485
+
486
+ if !@verify_peer && !should_silence_warnings
487
+ @logger.warn <<-MSG
488
+ Using TLS but peer hostname verification is disabled. This is convenient for local development
489
+ but prone to man-in-the-middle attacks. Please set verify_peer: true in production. Learn more at https://www.rabbitmq.com/ssl.html
490
+ MSG
491
+ end
492
+
493
+ ssl_version = opts[:tls_protocol] || opts[:ssl_version] || :TLSv1_2
494
+ ctx.ssl_version = ssl_version if ssl_version
495
+
496
+ ctx
497
+ end
498
+
499
+ def initialize_tls_certificate_store(certs)
500
+ cert_files = []
501
+ cert_inlines = []
502
+ certs.each do |cert|
503
+ # if it starts with / or C:/ then it's a file path that may or may not
504
+ # exist (e.g. a default OpenSSL path). MK.
505
+ if File.readable?(cert) || cert =~ /\A([a-z]:?)?\//i
506
+ cert_files.push(cert)
507
+ else
508
+ cert_inlines.push(cert)
509
+ end
510
+ end
511
+ @logger.debug { "Using CA certificates at #{cert_files.join(', ')}" }
512
+ @logger.debug { "Using #{cert_inlines.count} inline CA certificates" }
513
+ OpenSSL::X509::Store.new.tap do |store|
514
+ store.set_default_paths
515
+ cert_files.select { |path| File.readable?(path) }.
516
+ each { |path| store.add_file(path) }
517
+ cert_inlines.
518
+ each { |cert| store.add_cert(OpenSSL::X509::Certificate.new(cert)) }
519
+ end
520
+ end
521
+
522
+ def timeout_from(options)
523
+ options[:connect_timeout] || options[:connection_timeout] || options[:timeout] || DEFAULT_CONNECTION_TIMEOUT
524
+ end
525
+ end
526
+ end
@@ -0,0 +1,6 @@
1
+ # encoding: utf-8
2
+
3
+ module Bunny
4
+ # @return [String] Version of the library
5
+ VERSION = "2.19.1"
6
+ end
@@ -0,0 +1,28 @@
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