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,110 @@
1
+ require "socket"
2
+
3
+ module Bunny
4
+ # TCP socket extension that uses TCP_NODELAY and supports reading
5
+ # fully.
6
+ #
7
+ # Heavily inspired by Dalli by Mike Perham.
8
+ # @private
9
+ module Socket
10
+ attr_accessor :options
11
+
12
+ READ_RETRY_EXCEPTION_CLASSES = if defined?(IO::EAGAINWaitReadable)
13
+ # Ruby 2.1+
14
+ [Errno::EAGAIN, Errno::EWOULDBLOCK, IO::WaitReadable,
15
+ IO::EAGAINWaitReadable, IO::EWOULDBLOCKWaitReadable]
16
+ else
17
+ # 2.0
18
+ [Errno::EAGAIN, Errno::EWOULDBLOCK, IO::WaitReadable]
19
+ end
20
+ WRITE_RETRY_EXCEPTION_CLASSES = if defined?(IO::EAGAINWaitWritable)
21
+ [Errno::EAGAIN, Errno::EWOULDBLOCK, IO::WaitWritable,
22
+ IO::EAGAINWaitWritable, IO::EWOULDBLOCKWaitWritable]
23
+ else
24
+ # 2.0
25
+ [Errno::EAGAIN, Errno::EWOULDBLOCK, IO::WaitWritable]
26
+ end
27
+
28
+ def self.open(host, port, options = {})
29
+ socket = ::Socket.tcp(host, port, nil, nil,
30
+ connect_timeout: options[:connect_timeout])
31
+ if ::Socket.constants.include?('TCP_NODELAY') || ::Socket.constants.include?(:TCP_NODELAY)
32
+ socket.setsockopt(::Socket::IPPROTO_TCP, ::Socket::TCP_NODELAY, true)
33
+ end
34
+ socket.setsockopt(::Socket::SOL_SOCKET, ::Socket::SO_KEEPALIVE, true) if options.fetch(:keepalive, true)
35
+ socket.instance_eval do
36
+ @__bunny_socket_eof_flag__ = false
37
+ end
38
+ socket.extend self
39
+ socket.options = { :host => host, :port => port }.merge(options)
40
+ socket
41
+ rescue Errno::ETIMEDOUT
42
+ raise ClientTimeout
43
+ end
44
+
45
+ # Reads given number of bytes with an optional timeout
46
+ #
47
+ # @param [Integer] count How many bytes to read
48
+ # @param [Integer] timeout Timeout
49
+ #
50
+ # @return [String] Data read from the socket
51
+ # @api public
52
+ def read_fully(count, timeout = nil)
53
+ return nil if @__bunny_socket_eof_flag__
54
+
55
+ value = ''
56
+ begin
57
+ loop do
58
+ value << read_nonblock(count - value.bytesize)
59
+ break if value.bytesize >= count
60
+ end
61
+ rescue EOFError
62
+ # @eof will break Rubinius' TCPSocket implementation. MK.
63
+ @__bunny_socket_eof_flag__ = true
64
+ rescue *READ_RETRY_EXCEPTION_CLASSES
65
+ if IO.select([self], nil, nil, timeout)
66
+ retry
67
+ else
68
+ raise Timeout::Error, "IO timeout when reading #{count} bytes"
69
+ end
70
+ end
71
+ value
72
+ end # read_fully
73
+
74
+ # Writes provided data using IO#write_nonblock, taking care of handling
75
+ # of exceptions it raises when writing would fail (e.g. due to socket buffer
76
+ # being full).
77
+ #
78
+ # IMPORTANT: this method will mutate (slice) the argument. Pass in duplicates
79
+ # if this is not appropriate in your case.
80
+ #
81
+ # @param [String] data Data to write
82
+ # @param [Integer] timeout Timeout
83
+ #
84
+ # @api public
85
+ def write_nonblock_fully(data, timeout = nil)
86
+ return nil if @__bunny_socket_eof_flag__
87
+
88
+ length = data.bytesize
89
+ total_count = 0
90
+ count = 0
91
+ loop do
92
+ begin
93
+ count = self.write_nonblock(data)
94
+ rescue *WRITE_RETRY_EXCEPTION_CLASSES
95
+ if IO.select([], [self], nil, timeout)
96
+ retry
97
+ else
98
+ raise Timeout::Error, "IO timeout when writing to socket"
99
+ end
100
+ end
101
+
102
+ total_count += count
103
+ return total_count if total_count >= length
104
+ data = data.byteslice(count..-1)
105
+ end
106
+
107
+ end
108
+
109
+ end
110
+ end
@@ -0,0 +1,118 @@
1
+ require "socket"
2
+
3
+ module Bunny
4
+ begin
5
+ require "openssl"
6
+
7
+ # TLS-enabled TCP socket that implements convenience
8
+ # methods found in Bunny::Socket.
9
+ class SSLSocket < OpenSSL::SSL::SSLSocket
10
+
11
+ READ_RETRY_EXCEPTION_CLASSES = if defined?(IO::EAGAINWaitReadable)
12
+ # Ruby 2.1+
13
+ [Errno::EAGAIN, Errno::EWOULDBLOCK, IO::WaitReadable,
14
+ IO::EAGAINWaitReadable, IO::EWOULDBLOCKWaitReadable]
15
+ else
16
+ # 2.0
17
+ [Errno::EAGAIN, Errno::EWOULDBLOCK, IO::WaitReadable]
18
+ end
19
+ WRITE_RETRY_EXCEPTION_CLASSES = if defined?(IO::EAGAINWaitWritable)
20
+ [Errno::EAGAIN, Errno::EWOULDBLOCK, IO::WaitWritable,
21
+ IO::EAGAINWaitWritable, IO::EWOULDBLOCKWaitWritable]
22
+ else
23
+ # 2.0
24
+ [Errno::EAGAIN, Errno::EWOULDBLOCK, IO::WaitWritable]
25
+ end
26
+
27
+ def initialize(*args)
28
+ super
29
+ @__bunny_socket_eof_flag__ = false
30
+ end
31
+
32
+ # Reads given number of bytes with an optional timeout
33
+ #
34
+ # @param [Integer] count How many bytes to read
35
+ # @param [Integer] timeout Timeout
36
+ #
37
+ # @return [String] Data read from the socket
38
+ # @api public
39
+ def read_fully(count, timeout = nil)
40
+ return nil if @__bunny_socket_eof_flag__
41
+
42
+ value = ''
43
+ begin
44
+ loop do
45
+ value << read_nonblock(count - value.bytesize)
46
+ break if value.bytesize >= count
47
+ end
48
+ rescue EOFError => e
49
+ @__bunny_socket_eof_flag__ = true
50
+ rescue OpenSSL::SSL::SSLError => e
51
+ if e.message == "read would block"
52
+ if IO.select([self], nil, nil, timeout)
53
+ retry
54
+ else
55
+ raise Timeout::Error, "IO timeout when reading #{count} bytes"
56
+ end
57
+ else
58
+ raise e
59
+ end
60
+ rescue *READ_RETRY_EXCEPTION_CLASSES => e
61
+ if IO.select([self], nil, nil, timeout)
62
+ retry
63
+ else
64
+ raise Timeout::Error, "IO timeout when reading #{count} bytes"
65
+ end
66
+ end
67
+ value
68
+ end
69
+
70
+ # Writes provided data using IO#write_nonblock, taking care of handling
71
+ # of exceptions it raises when writing would fail (e.g. due to socket buffer
72
+ # being full).
73
+ #
74
+ # IMPORTANT: this method will mutate (slice) the argument. Pass in duplicates
75
+ # if this is not appropriate in your case.
76
+ #
77
+ # @param [String] data Data to write
78
+ # @param [Integer] timeout Timeout
79
+ #
80
+ # @api public
81
+ def write_nonblock_fully(data, timeout = nil)
82
+ return nil if @__bunny_socket_eof_flag__
83
+
84
+ length = data.bytesize
85
+ total_count = 0
86
+ count = 0
87
+ loop do
88
+ begin
89
+ count = self.write_nonblock(data)
90
+ rescue OpenSSL::SSL::SSLError => e
91
+ if e.message == "write would block"
92
+ if IO.select([], [self], nil, timeout)
93
+ retry
94
+ else
95
+ raise Timeout::Error, "IO timeout when writing to socket"
96
+ end
97
+ end
98
+ raise e
99
+ rescue *WRITE_RETRY_EXCEPTION_CLASSES
100
+ if IO.select([], [self], nil, timeout)
101
+ retry
102
+ else
103
+ raise Timeout::Error, "IO timeout when writing to socket"
104
+ end
105
+ end
106
+
107
+ total_count += count
108
+ return total_count if total_count >= length
109
+ data = data.byteslice(count..-1)
110
+ end
111
+
112
+ end
113
+
114
+ end
115
+ rescue LoadError
116
+ puts "Could not load OpenSSL"
117
+ end
118
+ end
@@ -0,0 +1,93 @@
1
+ require "bunny/versioned_delivery_tag"
2
+
3
+ module Bunny
4
+ # Wraps {AMQ::Protocol::Basic::Deliver} to
5
+ # provide access to the delivery properties as immutable hash as
6
+ # well as methods.
7
+ class DeliveryInfo
8
+
9
+ #
10
+ # Behaviors
11
+ #
12
+
13
+ include Enumerable
14
+
15
+ #
16
+ # API
17
+ #
18
+
19
+ # @return [Bunny::Consumer] Consumer this delivery is for
20
+ attr_reader :consumer
21
+ # @return [Bunny::Channel] Channel this delivery is on
22
+ attr_reader :channel
23
+
24
+ # @private
25
+ def initialize(basic_deliver, consumer, channel)
26
+ @basic_deliver = basic_deliver
27
+ @hash = {
28
+ :consumer_tag => basic_deliver.consumer_tag,
29
+ :delivery_tag => VersionedDeliveryTag.new(basic_deliver.delivery_tag, channel.recoveries_counter),
30
+ :redelivered => basic_deliver.redelivered,
31
+ :exchange => basic_deliver.exchange,
32
+ :routing_key => basic_deliver.routing_key,
33
+ :consumer => consumer,
34
+ :channel => channel
35
+ }
36
+ @consumer = consumer
37
+ @channel = channel
38
+ end
39
+
40
+ # Iterates over the delivery properties
41
+ # @see Enumerable#each
42
+ def each(*args, &block)
43
+ @hash.each(*args, &block)
44
+ end
45
+
46
+ # Accesses delivery properties by key
47
+ # @see Hash#[]
48
+ def [](k)
49
+ @hash[k]
50
+ end
51
+
52
+ # @return [Hash] Hash representation of this delivery info
53
+ def to_hash
54
+ @hash
55
+ end
56
+
57
+ # @private
58
+ def to_s
59
+ to_hash.to_s
60
+ end
61
+
62
+ # @private
63
+ def inspect
64
+ to_hash.inspect
65
+ end
66
+
67
+ # @return [String] Consumer tag this delivery is for
68
+ def consumer_tag
69
+ @basic_deliver.consumer_tag
70
+ end
71
+
72
+ # @return [String] Delivery identifier that is used to acknowledge, reject and nack deliveries
73
+ def delivery_tag
74
+ @basic_deliver.delivery_tag
75
+ end
76
+
77
+ # @return [Boolean] true if this delivery is a redelivery (the message was requeued at least once)
78
+ def redelivered
79
+ @basic_deliver.redelivered
80
+ end
81
+ alias redelivered? redelivered
82
+
83
+ # @return [String] Name of the exchange this message was published to
84
+ def exchange
85
+ @basic_deliver.exchange
86
+ end
87
+
88
+ # @return [String] Routing key this message was published with
89
+ def routing_key
90
+ @basic_deliver.routing_key
91
+ end
92
+ end
93
+ end
@@ -0,0 +1,269 @@
1
+ module Bunny
2
+ # Base class for all Bunny exceptions
3
+ # @api public
4
+ class Exception < ::StandardError
5
+ end
6
+
7
+ class HostListDepleted < Exception
8
+ def initialize
9
+ super("No more hosts to try in the supplied list of hosts")
10
+ end
11
+ end
12
+
13
+ # Indicates a network failure. If automatic network
14
+ # recovery mode is enabled, these will be typically handled
15
+ # by the client itself.
16
+ #
17
+ # @api public
18
+ class NetworkFailure < Exception
19
+ attr_reader :cause
20
+
21
+ def initialize(message, cause)
22
+ super(message)
23
+ @cause = cause
24
+ end
25
+ end
26
+
27
+ # Base class for all channel level exceptions
28
+ class ChannelLevelException < Exception
29
+ attr_reader :channel, :channel_close
30
+
31
+ def initialize(message, ch, channel_close)
32
+ super(message)
33
+
34
+ @channel = ch
35
+ @channel_close = channel_close
36
+ end
37
+ end
38
+
39
+ # Base class for all connection level exceptions
40
+ class ConnectionLevelException < Exception
41
+ attr_reader :connection, :connection_close
42
+
43
+ def initialize(message, connection, connection_close)
44
+ super(message)
45
+
46
+ @connection = connection
47
+ @connection_close = connection_close
48
+ end
49
+ end
50
+
51
+ # Can indicate either a channel or connection-level issue
52
+ class NotAllowedError < Exception
53
+ attr_reader :connection, :connection_close
54
+
55
+ def initialize(message, connection, connection_close = nil)
56
+ super(message)
57
+
58
+ @connection = connection
59
+ @connection_close = connection_close
60
+ end
61
+ end
62
+
63
+ # Raised when TCP connection to RabbitMQ fails because of an unresolved
64
+ # hostname, connectivity problem, etc
65
+ class TCPConnectionFailed < Exception
66
+ attr_reader :hostname, :port
67
+
68
+ def initialize(e, hostname=nil, port=nil)
69
+ m = case e
70
+ when String then
71
+ e
72
+ when ::Exception then
73
+ e.message
74
+ end
75
+ if hostname && port
76
+ super("Could not establish TCP connection to #{hostname}:#{port}: #{m}")
77
+ else
78
+ super(m)
79
+ end
80
+ end
81
+ end
82
+
83
+ class TCPConnectionFailedForAllHosts < TCPConnectionFailed
84
+ def initialize
85
+ super("Could not establish TCP connection to any of the configured hosts", nil, nil)
86
+ end
87
+ end
88
+
89
+ # Raised when a frame is sent over an already closed connection
90
+ class ConnectionClosedError < Exception
91
+ def initialize(frame)
92
+ if frame.respond_to?(:method_class)
93
+ super("Trying to send frame through a closed connection. Frame is #{frame.inspect}, method class is #{frame.method_class}")
94
+ else
95
+ super("Trying to send frame through a closed connection. Frame is #{frame.inspect}")
96
+ end
97
+ end
98
+ end
99
+
100
+ class ConnectionAlreadyClosed < Exception
101
+ def initialize
102
+ super('Connection has been already closed')
103
+ end
104
+ end
105
+
106
+ class ShutdownSignal < Exception
107
+ end
108
+
109
+ # Raised when RabbitMQ closes TCP connection before finishing connection
110
+ # sequence properly. This typically indicates an authentication issue.
111
+ class PossibleAuthenticationFailureError < Exception
112
+
113
+ #
114
+ # API
115
+ #
116
+
117
+ attr_reader :username, :vhost
118
+
119
+ def initialize(username, vhost, password_length)
120
+ @username = username
121
+ @vhost = vhost
122
+
123
+ super("Authentication with RabbitMQ failed. Please check your connection settings. Username: #{username}, vhost: #{vhost}, password length: #{password_length}")
124
+ end # initialize(settings)
125
+ end # PossibleAuthenticationFailureError
126
+
127
+
128
+ # Raised when RabbitMQ closes TCP connection due to an authentication failure.
129
+ # Relies on RabbitMQ 3.2 Authentication Failure Notifications extension:
130
+ # http://www.rabbitmq.com/auth-notification.html
131
+ class AuthenticationFailureError < PossibleAuthenticationFailureError
132
+
133
+ #
134
+ # API
135
+ #
136
+
137
+ attr_reader :username, :vhost
138
+
139
+ def initialize(username, vhost, password_length)
140
+ @username = username
141
+ @vhost = vhost
142
+
143
+ super(username, vhost, password_length)
144
+ end # initialize(settings)
145
+ end # AuthenticationFailureError
146
+
147
+
148
+ # backwards compatibility
149
+ # @private
150
+ ConnectionError = TCPConnectionFailed
151
+ # @private
152
+ ServerDownError = TCPConnectionFailed
153
+
154
+ # Raised when a channel is closed forcefully using rabbitmqctl
155
+ # or the management UI plugin
156
+ class ForcedChannelCloseError < ChannelLevelException; end
157
+ # Raised when a connection is closed forcefully using rabbitmqctl
158
+ # or the management UI plugin
159
+ class ForcedConnectionCloseError < ConnectionLevelException; end
160
+ # @private
161
+ class MessageError < ConnectionLevelException; end
162
+ # @private
163
+ class ProtocolError < ConnectionLevelException; end
164
+ # Raised when RabbitMQ reports and internal error
165
+ class InternalError < ConnectionLevelException; end
166
+
167
+ # Raised when read or write I/O operations time out (but only if
168
+ # a connection is configured to use them)
169
+ class ClientTimeout < Timeout::Error; end
170
+ # Raised on initial TCP connection timeout
171
+ class ConnectionTimeout < Timeout::Error; end
172
+
173
+
174
+ # Base exception class for data consistency and framing errors.
175
+ class InconsistentDataError < Exception
176
+ end
177
+
178
+ # Raised by adapters when frame does not end with {final octet AMQ::Protocol::Frame::FINAL_OCTET}.
179
+ # This suggest that there is a bug in adapter or AMQ broker implementation.
180
+ #
181
+ # @see https://www.rabbitmq.com/resources/specs/amqp0-9-1.pdf AMQP 0.9.1 specification (Section 2.3)
182
+ class NoFinalOctetError < InconsistentDataError
183
+ def initialize
184
+ super("Frame doesn't end with #{AMQ::Protocol::Frame::FINAL_OCTET} as it must, which means the size is miscalculated.")
185
+ end
186
+ end
187
+
188
+ # Raised by adapters when actual frame payload size in bytes is not equal
189
+ # to the size specified in that frame's header.
190
+ # This suggest that there is a bug in adapter or AMQ broker implementation.
191
+ #
192
+ # @see https://www.rabbitmq.com/resources/specs/amqp0-9-1.pdf AMQP 0.9.1 specification (Section 2.3)
193
+ class BadLengthError < InconsistentDataError
194
+ def initialize(expected_length, actual_length)
195
+ super("Frame payload should be #{expected_length} long, but it's #{actual_length} long.")
196
+ end
197
+ end
198
+
199
+ # Raised when a closed channel is used
200
+ class ChannelAlreadyClosed < Exception
201
+ attr_reader :channel
202
+
203
+ def initialize(message, ch)
204
+ super(message)
205
+
206
+ @channel = ch
207
+ end
208
+ end
209
+
210
+ # Raised when RabbitMQ responds with 406 PRECONDITION_FAILED
211
+ class PreconditionFailed < ChannelLevelException
212
+ end
213
+
214
+ # Raised when RabbitMQ responds with 404 NOT_FOUND
215
+ class NotFound < ChannelLevelException
216
+ end
217
+
218
+ # Raised when RabbitMQ responds with 405 RESOUCE_LOCKED
219
+ class ResourceLocked < ChannelLevelException
220
+ end
221
+
222
+ # Raised when RabbitMQ responds with 403 ACCESS_REFUSED
223
+ class AccessRefused < ChannelLevelException
224
+ end
225
+
226
+ # Raised when RabbitMQ responds with 504 CHANNEL_ERROR
227
+ class ChannelError < ConnectionLevelException
228
+ end
229
+
230
+ # Raised when RabbitMQ responds with 503 COMMAND_INVALID
231
+ class CommandInvalid < ConnectionLevelException
232
+ end
233
+
234
+ # Raised when RabbitMQ responds with 501 FRAME_ERROR
235
+ class FrameError < ConnectionLevelException
236
+ end
237
+
238
+ # Raised when RabbitMQ responds with 505 UNEXPECTED_FRAME
239
+ class UnexpectedFrame < ConnectionLevelException
240
+ end
241
+
242
+ # Raised when RabbitMQ responds with 506 RESOURCE_ERROR
243
+ class ResourceError < ConnectionLevelException
244
+ end
245
+
246
+ # @private
247
+ class NetworkErrorWrapper < Exception
248
+ attr_reader :other
249
+
250
+ def initialize(other)
251
+ super(other.message)
252
+ @other = other
253
+ end
254
+ end
255
+
256
+ # Raised when RabbitMQ responds with 302 CONNECTION_FORCED
257
+ # (which means the connection was closed using rabbitmqctl or
258
+ # RabbitMQ management UI)
259
+ class ConnectionForced < ConnectionLevelException
260
+ end
261
+
262
+ # @private
263
+ class MissingTLSCertificateFile < Exception
264
+ end
265
+
266
+ # @private
267
+ class MissingTLSKeyFile < Exception
268
+ end
269
+ end