Base adapter class. Specific implementations (for example, EventMachine-based, Cool.io-based or sockets-based) subclass it and must implement Adapter API methods:
#
#
@abstract
# File lib/amq/client/async/adapter.rb, line 23 23: def self.included(host) 24: host.extend ClassMethods 25: host.extend ProtocolMethodHandlers 26: 27: host.class_eval do 28: 29: # 30: # API 31: # 32: 33: attr_accessor :logger 34: attr_accessor :settings 35: 36: # @return [Array<#call>] 37: attr_reader :callbacks 38: 39: 40: # The locale defines the language in which the server will send reply texts. 41: # 42: # @see http://bit.ly/amqp091reference AMQP 0.9.1 protocol reference (Section 1.4.2.2) 43: attr_accessor :locale 44: 45: # Client capabilities 46: # 47: # @see http://bit.ly/amqp091reference AMQP 0.9.1 protocol reference (Section 1.4.2.2.1) 48: attr_accessor :client_properties 49: 50: # Server properties 51: # 52: # @see http://bit.ly/amqp091reference AMQP 0.9.1 protocol reference (Section 1.4.2.1.3) 53: attr_reader :server_properties 54: 55: # Server capabilities 56: # 57: # @see http://bit.ly/amqp091reference AMQP 0.9.1 protocol reference (Section 1.4.2.1.3) 58: attr_reader :server_capabilities 59: 60: # Locales server supports 61: # 62: # @see http://bit.ly/amqp091reference AMQP 0.9.1 protocol reference (Section 1.4.2.1.3) 63: attr_reader :server_locales 64: 65: # Authentication mechanism used. 66: # 67: # @see http://bit.ly/amqp091reference AMQP 0.9.1 protocol reference (Section 1.4.2.2) 68: attr_reader :mechanism 69: 70: # Authentication mechanisms broker supports. 71: # 72: # @see http://bit.ly/amqp091reference AMQP 0.9.1 protocol reference (Section 1.4.2.2) 73: attr_reader :server_authentication_mechanisms 74: 75: # Channels within this connection. 76: # 77: # @see http://bit.ly/amqp091spec AMQP 0.9.1 specification (Section 2.2.5) 78: attr_reader :channels 79: 80: # Maximum channel number that the server permits this connection to use. 81: # Usable channel numbers are in the range 1..channel_max. 82: # Zero indicates no specified limit. 83: # 84: # @see http://bit.ly/amqp091reference AMQP 0.9.1 protocol reference (Sections 1.4.2.5.1 and 1.4.2.6.1) 85: attr_accessor :channel_max 86: 87: # Maximum frame size that the server permits this connection to use. 88: # 89: # @see http://bit.ly/amqp091reference AMQP 0.9.1 protocol reference (Sections 1.4.2.5.2 and 1.4.2.6.2) 90: attr_accessor :frame_max 91: 92: 93: attr_reader :known_hosts 94: 95: 96: 97: # @api plugin 98: # @see #disconnect 99: # @note Adapters must implement this method but it is NOT supposed to be used directly. 100: # AMQ protocol defines two-step process of closing connection (send Connection.Close 101: # to the peer and wait for Connection.Close-Ok), implemented by {Adapter#disconnect} 102: def close_connection 103: raise NotImplementedError 104: end unless defined?(:close_connection) # since it is a module, this method may already be defined 105: end 106: end
Performs recovery of channels that are in the automatic recovery mode. Does not run recovery callbacks.
@see Channel#auto_recover @see Queue#auto_recover @see Exchange#auto_recover @api plugin
# File lib/amq/client/async/adapter.rb, line 420 420: def auto_recover 421: @channels.select { |channel_id, ch| ch.auto_recovering? }.each { |n, ch| ch.auto_recover } 422: end
@return [Boolean] whether connection is in the automatic recovery mode @api public
# File lib/amq/client/async/adapter.rb, line 407 407: def auto_recovering? 408: !!@auto_recovery 409: end
Defines a callback that will be executed after TCP connection has recovered after a network failure but before AMQP connection is re-opened. Only one callback can be defined (the one defined last replaces previously added ones).
@api public
# File lib/amq/client/async/adapter.rb, line 376 376: def before_recovery(&block) 377: self.redefine_callback(:before_recovery, &block) 378: end
Clears frames that were received but not processed on given channel. Needs to be called when the channel is closed. @private
# File lib/amq/client/async/adapter.rb, line 549 549: def clear_frames_on(channel_id) 550: raise ArgumentError, "channel id cannot be nil!" if channel_id.nil? 551: 552: @frames[channel_id].clear 553: end
@api plugin @see # @note Adapters must implement this method but it is NOT supposed to be used directly.
AMQ protocol defines two-step process of closing connection (send Connection.Close to the peer and wait for Connection.Close-Ok), implemented by {Adapter#disconnect}
# File lib/amq/client/async/adapter.rb, line 102 102: def close_connection 103: raise NotImplementedError 104: end
Properly close connection with AMQ broker, as described in section 2.2.4 of the {bit.ly/amqp091spec AMQP 0.9.1 specification}.
@api plugin @see #
# File lib/amq/client/async/adapter.rb, line 212 212: def disconnect(reply_code = 200, reply_text = "Goodbye", class_id = 0, method_id = 0, &block) 213: @intentionally_closing_connection = true 214: self.on_disconnection do 215: @frames.clear 216: block.call if block 217: end 218: 219: # ruby-amqp/amqp#66, MK. 220: if self.open? 221: closing! 222: self.send_frame(Protocol::Connection::Close.encode(reply_code, reply_text, class_id, method_id)) 223: elsif self.closing? 224: # no-op 225: else 226: self.disconnection_successful 227: end 228: end
@api plugin @see tools.ietf.org/rfc/rfc2595.txt RFC 2595
# File lib/amq/client/async/adapter.rb, line 505 505: def encode_credentials(username, password) 506: "\00##{username}\00##{password}" 507: end
Establish socket connection to the server.
@api plugin
# File lib/amq/client/async/adapter.rb, line 203 203: def establish_connection(settings) 204: raise NotImplementedError 205: end
Handles connection.close. When broker detects a connection level exception, this method is called.
@api plugin @see bit.ly/amqp091reference AMQP 0.9.1 protocol reference (Section 1.5.2.9)
# File lib/amq/client/async/adapter.rb, line 624 624: def handle_close(conn_close) 625: closed! 626: self.exec_callback_yielding_self(:error, conn_close) 627: end
Handles Connection.Close-Ok.
@api plugin @see bit.ly/amqp091reference AMQP 0.9.1 protocol reference (Section 1.4.2.10)
# File lib/amq/client/async/adapter.rb, line 634 634: def handle_close_ok(close_ok) 635: closed! 636: self.disconnection_successful 637: end
@private @api plugin
# File lib/amq/client/async/adapter.rb, line 364 364: def handle_connection_interruption 365: @channels.each { |n, c| c.handle_connection_interruption } 366: self.exec_callback_yielding_self(:after_connection_interruption) 367: end
Handles Connection.Open-Ok.
@api plugin @see bit.ly/amqp091reference AMQP 0.9.1 protocol reference (Section 1.4.2.8.)
# File lib/amq/client/async/adapter.rb, line 612 612: def handle_open_ok(open_ok) 613: @known_hosts = open_ok.known_hosts.dup.freeze 614: 615: opened! 616: self.connection_successful if self.respond_to?(:connection_successful) 617: end
Handles connection.start.
@api plugin @see bit.ly/amqp091reference AMQP 0.9.1 protocol reference (Section 1.4.2.1.)
# File lib/amq/client/async/adapter.rb, line 576 576: def handle_start(connection_start) 577: @server_properties = connection_start.server_properties 578: @server_capabilities = @server_properties["capabilities"] 579: 580: @server_authentication_mechanisms = (connection_start.mechanisms || "").split(" ") 581: @server_locales = Array(connection_start.locales) 582: 583: username = @settings[:user] || @settings[:username] 584: password = @settings[:pass] || @settings[:password] 585: 586: # It's not clear whether we should transition to :opening state here 587: # or in #open but in case authentication fails, it would be strange to have 588: # @status undefined. So lets do this. MK. 589: opening! 590: 591: self.send_frame(Protocol::Connection::StartOk.encode(@client_properties, @mechanism, self.encode_credentials(username, password), @locale)) 592: end
Handles Connection.Tune-Ok.
@api plugin @see bit.ly/amqp091reference AMQP 0.9.1 protocol reference (Section 1.4.2.6)
# File lib/amq/client/async/adapter.rb, line 599 599: def handle_tune(tune_ok) 600: @channel_max = tune_ok.channel_max.freeze 601: @frame_max = tune_ok.frame_max.freeze 602: @heartbeat_interval = self.heartbeat_interval || tune_ok.heartbeat 603: 604: self.send_frame(Protocol::Connection::TuneOk.encode(@channel_max, [settings[:frame_max], @frame_max].min, @heartbeat_interval)) 605: end
Sends connection preamble to the broker. @api plugin
# File lib/amq/client/async/adapter.rb, line 482 482: def handshake 483: @authenticating = true 484: self.send_preamble 485: end
Returns heartbeat interval this client uses, in seconds. This value may or may not be used depending on broker capabilities. Zero means the server does not want a heartbeat.
@return [Fixnum] Heartbeat interval this client uses, in seconds. @see bit.ly/amqp091reference AMQP 0.9.1 protocol reference (Section 1.4.2.6)
# File lib/amq/client/async/adapter.rb, line 276 276: def heartbeat_interval 277: @settings[:heartbeat] || @settings[:heartbeat_interval] || 0 278: end
Defines a callback that will be executed after TCP connection is interrupted (typically because of a network failure). Only one callback can be defined (the one defined last replaces previously added ones).
@api public
# File lib/amq/client/async/adapter.rb, line 356 356: def on_connection_interruption(&block) 357: self.redefine_callback(:after_connection_interruption, &block) 358: end
Defines a callback that will be executed when connection is closed after connection-level exception. Only one callback can be defined (the one defined last replaces previously added ones).
@api public
# File lib/amq/client/async/adapter.rb, line 347 347: def on_error(&block) 348: self.redefine_callback(:error, &block) 349: end
Defines a callback that will be run when TCP connection is closed before authentication finishes. Usually this means authentication failure. You can define only one callback.
@api public
# File lib/amq/client/async/adapter.rb, line 337 337: def on_possible_authentication_failure(&block) 338: @on_possible_authentication_failure = block 339: end
Defines a callback that will be executed after AMQP connection has recovered after a network failure.. Only one callback can be defined (the one defined last replaces previously added ones).
@api public
# File lib/amq/client/async/adapter.rb, line 392 392: def on_recovery(&block) 393: self.redefine_callback(:after_recovery, &block) 394: end
Defines a callback that will be executed after time since last broker heartbeat is greater than or equal to the heartbeat interval (skipped heartbeat is detected). Only one callback can be defined (the one defined last replaces previously added ones).
@api public
# File lib/amq/client/async/adapter.rb, line 453 453: def on_skipped_heartbeats(&block) 454: self.redefine_callback(:skipped_heartbeats, &block) 455: end
Defines a callback that will be run when initial TCP connection fails. You can define only one callback.
@api public
# File lib/amq/client/async/adapter.rb, line 321 321: def on_tcp_connection_failure(&block) 322: @on_tcp_connection_failure = block 323: end
Defines a callback that will be run when TCP connection to AMQP broker is lost (interrupted). You can define only one callback.
@api public
# File lib/amq/client/async/adapter.rb, line 329 329: def on_tcp_connection_loss(&block) 330: @on_tcp_connection_loss = block 331: end
Sends connection.open to the server.
@api plugin @see bit.ly/amqp091reference AMQP 0.9.1 protocol reference (Section 1.4.2.7)
# File lib/amq/client/async/adapter.rb, line 492 492: def open(vhost = "/") 493: self.send_frame(Protocol::Connection::Open.encode(vhost)) 494: end
Processes a single frame.
@param [AMQ::Protocol::Frame] frame @api plugin
# File lib/amq/client/async/adapter.rb, line 514 514: def receive_frame(frame) 515: @frames[frame.channel] ||= Array.new 516: @frames[frame.channel] << frame 517: 518: if frameset_complete?(@frames[frame.channel]) 519: receive_frameset(@frames[frame.channel]) 520: # for channel.close, frame.channel will be nil. MK. 521: clear_frames_on(frame.channel) if @frames[frame.channel] 522: end 523: end
Processes a frameset by finding and invoking a suitable handler. Heartbeat frames are treated in a special way: they simply update @last_server_heartbeat value.
@param [Array
# File lib/amq/client/async/adapter.rb, line 531 531: def receive_frameset(frames) 532: frame = frames.first 533: 534: if Protocol::HeartbeatFrame === frame 535: @last_server_heartbeat = Time.now 536: else 537: if callable = AMQ::Client::HandlersRegistry.find(frame.method_class) 538: f = frames.shift 539: callable.call(self, f, frames) 540: else 541: raise MissingHandlerError.new(frames.first) 542: end 543: end 544: end
@return [Boolean]
# File lib/amq/client/async/adapter.rb, line 312 312: def reconnecting? 313: @reconnecting 314: end
Resets connection state.
@api plugin
# File lib/amq/client/async/adapter.rb, line 499 499: def reset_state! 500: # no-op by default 501: end
@private
# File lib/amq/client/async/adapter.rb, line 398 398: def run_after_recovery_callbacks 399: self.exec_callback_yielding_self(:after_recovery, @settings) 400: 401: @channels.each { |n, ch| ch.run_after_recovery_callbacks } 402: end
@private
# File lib/amq/client/async/adapter.rb, line 381 381: def run_before_recovery_callbacks 382: self.exec_callback_yielding_self(:before_recovery, @settings) 383: 384: @channels.each { |n, ch| ch.run_before_recovery_callbacks } 385: end
@private
# File lib/amq/client/async/adapter.rb, line 458 458: def run_skipped_heartbeats_callbacks 459: self.exec_callback_yielding_self(:skipped_heartbeats, @settings) 460: end
Sends frame to the peer, checking that connection is open.
@raise [ConnectionClosedError]
# File lib/amq/client/async/adapter.rb, line 245 245: def send_frame(frame) 246: if closed? 247: raise ConnectionClosedError.new(frame) 248: else 249: self.send_raw(frame.encode) 250: end 251: end
Sends multiple frames, one by one. For thread safety this method takes a channel object and synchronizes on it.
@api public
# File lib/amq/client/async/adapter.rb, line 257 257: def send_frameset(frames, channel) 258: # some (many) developers end up sharing channels between threads and when multiple 259: # threads publish on the same channel aggressively, at some point frames will be 260: # delivered out of order and broker will raise 505 UNEXPECTED_FRAME exception. 261: # If we synchronize on the channel, however, this is both thread safe and pretty fine-grained 262: # locking. Note that "single frame" methods do not need this kind of synchronization. MK. 263: channel.synchronize do 264: frames.each { |frame| self.send_frame(frame) } 265: end 266: end
Sends a heartbeat frame if connection is open. @api plugin
# File lib/amq/client/async/adapter.rb, line 557 557: def send_heartbeat 558: if tcp_connection_established? && !@handling_skipped_hearbeats 559: if @last_server_heartbeat < (Time.now - (self.heartbeat_interval * 2)) && !reconnecting? 560: logger.error "[amqp] Detected missing server heartbeats" 561: self.handle_skipped_hearbeats 562: end 563: send_frame(Protocol::HeartbeatFrame) 564: end 565: end
Sends AMQ protocol header (also known as preamble).
@note This must be implemented by all AMQP clients. @api plugin @see bit.ly/amqp091spec AMQP 0.9.1 specification (Section 2.2)
# File lib/amq/client/async/adapter.rb, line 238 238: def send_preamble 239: self.send_raw(AMQ::Protocol::PREAMBLE) 240: end
Sends opaque data to AMQ broker over active connection.
@note This must be implemented by all AMQP clients. @api plugin
# File lib/amq/client/async/adapter.rb, line 476 476: def send_raw(data) 477: raise NotImplementedError 478: end
Performs recovery of channels that are in the automatic recovery mode. “before recovery” callbacks are run immediately, “after recovery” callbacks are run after AMQP connection is re-established and auto recovery is performed (using #).
Use this method if you want to run automatic recovery process after handling a connection-level exception, for example, 320 CONNECTION_FORCED (used by RabbitMQ when it is shut down gracefully).
@see Channel#auto_recover @see Queue#auto_recover @see Exchange#auto_recover @api plugin
# File lib/amq/client/async/adapter.rb, line 436 436: def start_automatic_recovery 437: self.run_before_recovery_callbacks 438: self.register_connection_callback do 439: # always run automatic recovery, because it is per-channel 440: # and connection has to start it. Channels that did not opt-in for 441: # autorecovery won't be selected. MK. 442: self.auto_recover 443: self.run_after_recovery_callbacks 444: end 445: end
Called when initial TCP connection fails. @api public
# File lib/amq/client/async/adapter.rb, line 296 296: def tcp_connection_failed 297: @recovered = false 298: 299: @on_tcp_connection_failure.call(@settings) if @on_tcp_connection_failure 300: end
Called when previously established TCP connection fails. @api public
# File lib/amq/client/async/adapter.rb, line 304 304: def tcp_connection_lost 305: @recovered = false 306: 307: @on_tcp_connection_loss.call(self, @settings) if @on_tcp_connection_loss 308: self.handle_connection_interruption 309: end
vhost this connection uses. Default is “/”, a historically estabilished convention of RabbitMQ and amqp gem.
@return [String] vhost this connection uses @api public
# File lib/amq/client/async/adapter.rb, line 286 286: def vhost 287: @settings.fetch(:vhost, "/") 288: end
Determines, whether given frame array contains full content body
# File lib/amq/client/async/adapter.rb, line 671 671: def content_complete?(frames) 672: return false if frames.empty? 673: header = frames[0] 674: raise "Not a content header frame first: #{header.inspect}" unless header.kind_of?(AMQ::Protocol::HeaderFrame) 675: header.body_size == frames[1..1].inject(0) {|sum, frame| sum + frame.payload.size } 676: end
Determines, whether the received frameset is ready to be further processed
# File lib/amq/client/async/adapter.rb, line 664 664: def frameset_complete?(frames) 665: return false if frames.empty? 666: first_frame = frames[0] 667: first_frame.final? || (first_frame.method_class.has_content? && content_complete?(frames[1..1])) 668: end
Returns next frame from buffer whenever possible
@api private
# File lib/amq/client/async/adapter.rb, line 646 646: def get_next_frame 647: return nil unless @chunk_buffer.size > 7 # otherwise, cannot read the length 648: # octet + short 649: offset = 3 # 1 + 2 650: # length 651: payload_length = @chunk_buffer[offset, 4].unpack(AMQ::Protocol::PACK_UINT32).first 652: # 4 bytes for long payload length, 1 byte final octet 653: frame_length = offset + payload_length + 5 654: if frame_length <= @chunk_buffer.size 655: @chunk_buffer.slice!(0, frame_length) 656: else 657: nil 658: end 659: end
Disabled; run with --debug to generate this.
Generated with the Darkfish Rdoc Generator 1.1.6.