garaio_bunny 2.19.1

Sign up to get free protection for your applications and to get access to all the features.
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