def test_handle_error_with_handler_and_no_err_msg(self): sock = EventSocket() sock._parent_error_cb = mock() expect(sock._parent_error_cb).args( sock, 'unknown error', 'exception' ) sock._handle_error( 'exception' )
def test_handle_error_no_handler_and_logger_and_no_err_msg(self): sock = EventSocket() sock._logger = mock() expect(sock._logger.error).args( 'unhandled unknown error', exc_info=True ) sock._handle_error( 'exception' )
def test_connect_sets_default_timeout_from_socket(self): now = time.time() sock = EventSocket() expect( time, 'time' ).returns( now ) sock._sock.settimeout( 8.67 ) expect( sock._connect_cb ).args( now+8.67, ('localhost',5309), immediate_raise=True ) sock.connect( ('localhost',5309) )
def test_parent_read_timer_cb_when_read_cb_reset(self): sock = EventSocket() sock._pending_read_cb_event = 'foo' sock._parent_read_timer_cb() assert_equals( 'error processing socket input buffer', sock._error_msg ) assert_equals( None, sock._pending_read_cb_event )
def test_handle_error_no_handler_and_no_logger(self): sock = EventSocket() mock( eventsocket, 'traceback' ) expect(eventsocket.traceback.print_exc) sock._handle_error( 'exception' )
def test_protected_cb_when_no_error(self): sock = EventSocket() cb = mock() expect( cb ).args( 'arg1', 'arg2', arg3='foo' ).returns( 'result' ) assert_equals( 'result', sock._protected_cb( cb, 'arg1', 'arg2', arg3='foo' ) )
def test_connect_sets_timeoutat_from_kwargs(self): now = time.time() sock = EventSocket() expect( time, 'time' ).returns( now ) expect( sock._connect_cb ).args( now+5.3, ('localhost',5309), immediate_raise=True ) sock.connect( ('localhost',5309), timeout=5.3 ) expect( sock._connect_cb ).args( now+5.7, ('localhost',5309), immediate_raise=True ) sock.connect( ('localhost',5309), timeout_at=now+5.7 )
def test_set_inactive_timeout_when_turning_off(self): sock = EventSocket() sock._inactive_event = mock() expect( sock._inactive_event.delete ) sock.set_inactive_timeout(0) assert_equals( None, sock._inactive_event ) assert_equals( 0, sock._inactive_timeout )
def test_parent_read_timer_cb(self): sock = EventSocket() sock._pending_read_cb_event = 'foo' sock._parent_read_cb = mock() expect( sock._parent_read_cb ).args( sock ) sock._parent_read_timer_cb() assert_equals( 'error processing socket input buffer', sock._error_msg ) assert_equals( None, sock._pending_read_cb_event )
def test_write_when_write_event_and_not_pending(self): sock = EventSocket() sock._write_event = mock() expect( sock._write_event.pending ).returns( False ) expect( sock._write_event.add ) expect( sock._flag_activity ) sock.write( 'foo' ) assert_equals( deque(['foo']), sock._write_buf )
def test_bind_without_debugging(self): sock = EventSocket() sock._sock = mock() mock(sock, 'getsockname') expect(sock._sock.bind).args( 'arg1', 'arg2' ) expect(sock.getsockname).returns( ('foo',1234) ) expect(eventsocket.event.read).args( sock, sock._protected_cb, sock._accept_cb ) sock.bind( 'arg1', 'arg2' )
def test_set_inactive_timeout_when_turning_off(self): sock = EventSocket() sock._inactive_event = mock() expect( sock._inactive_event.delete ) expect( eventsocket.event.timeout ).args( 32, sock._inactive_cb ).returns( 'new_timeout' ) sock.set_inactive_timeout(32) assert_equals( 'new_timeout', sock._inactive_event ) assert_equals( 32, sock._inactive_timeout )
def test_connect_cb_when_fail_and_not_immediateraise(self): sock = EventSocket() mock( sock, '_sock' ) mock( sock, '_connect_event' ) expect( sock._sock.connect_ex ).args( ('.com', 1234) ).returns( errno.ECONNREFUSED ) expect( sock._connect_event.delete ) expect( sock._handle_error ).args( socket.error ) sock._connect_cb( time.time(), ('.com',1234) )
def test_connect_cb_when_ealready_and_timeout(self): sock = EventSocket() mock( sock, '_sock' ) mock( sock, '_connect_event' ) # assert delete() not called timeout_at = time.time()-3 expect( sock._sock.connect_ex ).args( ('.com', 1234) ).returns( errno.EALREADY ) expect( sock.close ) sock._connect_cb( timeout_at, ('.com',1234) )
def test_connect_cb_when_einprogress_and_notimeout_and_no_pending_connect(self): sock = EventSocket() mock( sock, '_sock' ) timeout_at = time.time()+3 expect( sock._sock.connect_ex ).args( ('.com', 1234) ).returns( errno.EINPROGRESS ) expect( eventsocket.event.timeout ).args( 0.1, sock._connect_cb, timeout_at, ('.com', 1234) ).returns( 'connectev' ) sock._connect_cb( timeout_at, ('.com',1234) ) assert_equals( 'connectev', sock._connect_event )
def test_flag_activity(self): sock = EventSocket() sock._inactive_event = mock() sock._inactive_timeout = 42 expect( sock._inactive_event.delete ) expect( eventsocket.event.timeout ).args( 42, sock._protected_cb, sock._inactive_cb ).returns( 'doitagain' ) sock._flag_activity() assert_equals( 'doitagain', sock._inactive_event )
def test_read_cb_when_no_data(self): sock = EventSocket() sock._sock = mock() mock( sock, 'getsockopt' ) mock( sock, 'close' ) expect( sock.getsockopt ).args( socket.SOL_SOCKET, socket.SO_RCVBUF ).returns( 42 ) expect( sock._sock.recv ).args( 42 ).returns( '' ) expect( sock.close ) assert_equals( None, sock._read_cb() )
def test_read_cb_simplest_case(self): sock = EventSocket() sock._sock = mock() mock( sock, 'getsockopt' ) expect( sock.getsockopt ).args( socket.SOL_SOCKET, socket.SO_RCVBUF ).returns( 42 ) expect( sock._sock.recv ).args( 42 ).returns( 'sumdata' ) expect( sock._flag_activity ) assert_true( sock._read_cb() ) assert_equals( bytearray('sumdata'), sock._read_buf ) assert_equals( 'error reading from socket', sock._error_msg )
def test_write_cb_when_not_all_data_sent(self): sock = EventSocket() sock._sock = mock() sock._parent_output_empty_cb = mock() # assert not called sock._write_buf = deque(['data1','data2']) expect( sock._sock.send ).args( 'data1' ).returns( 5 ) expect( sock._sock.send ).args( 'data2' ).returns( 2 ) expect( sock._flag_activity ) assert_true( sock._write_cb() ) assert_equals( deque(['ta2']), sock._write_buf )
def test_connect_cb_when_no_err_and_pending_connect_event(self): sock = EventSocket() mock( sock, '_sock' ) mock( sock, '_connect_event' ) expect( sock._sock.connect_ex ).args( ('.com', 1234) ) expect( sock._sock.getpeername ).returns( ('.com', 1234) ) expect( eventsocket.event.read ).any_args() expect( eventsocket.event.write ).any_args() expect( sock._connect_event.delete ) sock._connect_cb( 3.14, ('.com',1234) ) assert_equals( None, sock._connect_event )
def test_write_cb_when_eagain_raised(self): sock = EventSocket() sock._sock = mock() sock._parent_output_empty_cb = mock() # assert not called sock._write_buf = deque(['data1','data2']) expect( sock._sock.send ).args( 'data1' ).raises( EnvironmentError(errno.EAGAIN,'try again') ) expect( sock._flag_activity ) assert_true( sock._write_cb() ) assert_equals( deque(['data1','data2']), sock._write_buf )
def test_protected_cb_when_an_error(self): sock = EventSocket() #mock( sock, '_handle_error' ) cb = mock() sock._error_msg = 'it broked' exc = RuntimeError('fale') expect( cb ).args( 'arg1', 'arg2', arg3='foo' ).raises( exc ) expect( sock._handle_error ).args( exc ) assert_equals( None, sock._protected_cb( cb, 'arg1', 'arg2', arg3='foo' ) ) assert_equals( None, sock._error_msg )
def test_connect_cb_when_no_err(self): sock = EventSocket() mock( sock, '_sock' ) expect( sock._sock.connect_ex ).args( ('.com', 1234) ) expect( sock._sock.getpeername ).returns( ('.com', 1234) ) expect( eventsocket.event.read ).args( sock._sock, sock._protected_cb, sock._read_cb ).returns( 'readev' ) expect( eventsocket.event.write ).args( sock._sock, sock._protected_cb, sock._write_cb ).returns( 'writeev' ) sock._connect_cb( 3.14, ('.com',1234) ) assert_equals( '.com:1234', sock._peername ) assert_equals( 'readev', sock._read_event ) assert_equals( 'writeev', sock._write_event ) assert_equals( None, sock._connect_event )
def test_write_cb_sends_all_data_and_theres_an_output_empty_cb(self): sock = EventSocket() sock._sock = mock() sock._parent_output_empty_cb = mock() sock._write_buf = deque(['data1','data2']) expect( sock._sock.send ).args( 'data1' ).returns( 5 ) expect( sock._sock.send ).args( 'data2' ).returns( 5 ) expect( sock._flag_activity ) expect( sock._parent_output_empty_cb ).args( sock ) assert_equals( None, sock._write_cb() ) assert_equals( 0, len(sock._write_buf) )
def test_accept_cb_when_logger_and_parent_cb(self): sock = EventSocket() sock._sock = mock() sock._parent_accept_cb = 'p_accept_cb' sock._parent_read_cb = 'p_read_cb' sock._parent_error_cb = 'p_error_cb' sock._parent_close_cb = 'p_close_cb' sock._debug = True sock._logger = mock() sock._max_read_buffer = 42 expect(sock._sock.accept).returns( ('connection', 'address') ) expect(sock._logger.debug).args( "accepted connection from address" ) expect(EventSocket.__init__).args( read_cb='p_read_cb', error_cb='p_error_cb', close_cb='p_close_cb', sock='connection', debug=True, logger=sock._logger, max_read_buffer=42 ) expect(sock._protected_cb).args( 'p_accept_cb', is_a(EventSocket) ) assert_true( sock._accept_cb() )
def test_accept_cb_when_no_logger_and_no_parent_cb(self): sock = EventSocket() sock._sock = mock() sock._parent_read_cb = 'p_read_cb' sock._parent_error_cb = 'p_error_cb' sock._parent_close_cb = 'p_close_cb' sock._debug = False sock._logger = None sock._max_read_buffer = 42 expect(sock._sock.accept).returns( ('connection', 'address') ) expect(EventSocket.__init__).args( read_cb='p_read_cb', error_cb='p_error_cb', close_cb='p_close_cb', sock='connection', debug=False, logger=None, max_read_buffer=42 ) assert_true( sock._accept_cb() ) assert_equals( 'error accepting new socket', sock._error_msg )
def test_write_when_write_event_is_pending_and_debugging(self): sock = EventSocket() sock._write_event = mock() sock._peername = 'peername' sock._write_buf = deque(['data']) sock._debug = 2 sock._logger = mock() expect( sock._write_event.pending ).returns( True ) expect( sock._logger.debug ).args(str, 3, 7, 'peername') expect( sock._flag_activity ) sock.write( 'foo' ) assert_equals( deque(['data', 'foo']), sock._write_buf )
def connect(self, host, port): ''' Connect to a host and port. Can be called directly, or is called by the strategy as it tries to find and connect to hosts. ''' # Clear the connect state immediately since we're no longer connected # at this point. self._connected = False # NOTE: purposefully leave output_frame_buffer alone so that pending writes can # still occur. this allows the reconnect to occur silently without # completely breaking any pending data on, say, a channel that was just # opened. self._sock = EventSocket(read_cb=self._sock_read_cb, close_cb=self._sock_close_cb, error_cb=self._sock_error_cb, debug=self._debug, logger=self._logger) self._sock.settimeout(self._connect_timeout) if self._sock_opts: for k, v in self._sock_opts.iteritems(): family, type = k self._sock.setsockopt(family, type, v) self._sock.connect((host, port)) self._sock.setblocking(False) # Only after the socket has connected do we clear this state; closed must # be False so that writes can be buffered in writePacket(). The closed # state might have been set to True due to a socket error or a redirect. self._host = "%s:%d" % (host, port) self._closed = False self._close_info = { 'reply_code': 0, 'reply_text': 'failed to connect to %s' % (self._host), 'class_id': 0, 'method_id': 0 } self._sock.write(PROTOCOL_HEADER)
def test_write_cb_with_no_data(self): sock = EventSocket() sock._sock = mock() sock._parent_output_empty_cb = mock() sock._debug = True sock._logger = mock() mock( sock, '_flag_activity' ) sock._write_buf = deque() assert_equals( None, sock._write_cb() ) assert_equals( sock._error_msg, "error writing socket output buffer" )
def test_write_cb_when_not_all_data_sent_and_logging(self): sock = EventSocket() sock._sock = mock() sock._peername = 'peer' sock._logger = mock() sock._debug = True sock._parent_output_empty_cb = mock() # assert not called sock._write_buf = deque(['data1','data2']) expect( sock._sock.send ).args( 'data1' ).returns( 5 ) expect( sock._sock.send ).args( 'data2' ).returns( 2 ) expect( sock._logger.debug ).args( str, 7, 10, 'peer' ) expect( sock._flag_activity ) assert_true( sock._write_cb() ) assert_equals( deque(['ta2']), sock._write_buf )
def test_set_read_cb_when_data_to_flush_but_pending_read_event(self): sock = EventSocket() sock._read_buf = bytearray('somedata') sock._parent_read_cb = None sock._pending_read_cb_event = 'pending_event' sock._set_read_cb( 'parent_read_cb' ) assert_equals( 'parent_read_cb', sock._parent_read_cb ) assert_equals( 'pending_event', sock._pending_read_cb_event )
class EventTransport(Transport): ''' Transport using libevent-based EventSocket. ''' def __init__(self, *args): super(EventTransport,self).__init__(*args) self._synchronous = False ### ### EventSocket callbacks ### def _sock_close_cb(self, sock): self._connection.transport_closed( msg='socket to %s closed unexpectedly'%(self._host), ) def _sock_error_cb(self, sock, msg, exception=None): self._connection.transport_closed( msg='error on connection to %s: %s'%(self._host, msg) ) def _sock_read_cb(self, sock): self.connection.read_frames() ### ### Transport API ### def connect(self, (host,port)): ''' Connect assuming a host and port tuple. Implemented as non-blocking, and will close the transport if there's an error ''' self._host = "%s:%s"%(host,port) self._sock = EventSocket( read_cb=self._sock_read_cb, close_cb=self._sock_close_cb, error_cb=self._sock_error_cb, debug=self.connection.debug, logger=self.connection.logger ) if self.connection._sock_opts: for k,v in self.connection._sock_opts.iteritems(): family,type = k self._sock.setsockopt(family, type, v) self._sock.setblocking( False ) self._sock.connect( (host,port), timeout=self.connection._connect_timeout ) self._heartbeat_timeout = None
class Connection(object): class TooManyChannels(ConnectionError): '''This connection has too many channels open. Non-fatal.''' class InvalidChannel(ConnectionError): '''The channel id does not correspond to an existing channel. Non-fatal.''' def __init__(self, **kwargs): ''' Initialize the connection. ''' self._debug = kwargs.get('debug', False) self._logger = kwargs.get('logger', root_logger) self._user = kwargs.get('user', 'guest') self._password = kwargs.get('password', 'guest') self._host = kwargs.get('host', 'localhost') self._vhost = kwargs.get('vhost', '/') self._connect_timeout = kwargs.get('connect_timeout', 5) self._sock_opts = kwargs.get('sock_opts') self._sock = None self._heartbeat = kwargs.get('heartbeat') self._reconnect_cb = kwargs.get('reconnect_cb') self._close_cb = kwargs.get('close_cb') self._login_method = kwargs.get('login_method', 'AMQPLAIN') self._locale = kwargs.get('locale', 'en_US') self._client_properties = kwargs.get('client_properties') self._properties = LIBRARY_PROPERTIES.copy() if self._client_properties: self._properties.update(self._client_properties) self._closed = False self._connected = False self._close_info = { 'reply_code': 0, 'reply_text': 'first connect', 'class_id': 0, 'method_id': 0 } self._channels = {0: ConnectionChannel(self, 0)} login_response = Writer() login_response.write_table({ 'LOGIN': self._user, 'PASSWORD': self._password }) #stream = BytesIO() #login_response.flush(stream) #self._login_response = stream.getvalue()[4:] #Skip the length #at the beginning self._login_response = login_response.buffer()[4:] self._channel_counter = 0 self._channel_max = 65535 self._frame_max = 65535 self._frames_read = 0 self._frames_written = 0 self._strategy = kwargs.get('connection_strategy') if not self._strategy: self._strategy = ConnectionStrategy( self, self._host, reconnect_cb=self._reconnect_cb) self._strategy.connect() self._output_frame_buffer = [] @property def logger(self): return self._logger @property def debug(self): return self._debug @property def frame_max(self): return self._frame_max @property def channel_max(self): return self._channel_max @property def frames_read(self): '''Number of frames read in the lifetime of this connection.''' return self._frames_read @property def frames_written(self): '''Number of frames written in the lifetime of this connection.''' return self._frames_written @property def close_info(self): '''Return dict with information on why this connection is closed. Will return None if the connections is open.''' return self._close_info if self._closed else None def reconnect(self): '''Reconnect to the configured host and port.''' self._strategy.connect() def connect(self, host, port): ''' Connect to a host and port. Can be called directly, or is called by the strategy as it tries to find and connect to hosts. ''' # Clear the connect state immediately since we're no longer connected # at this point. self._connected = False # NOTE: purposefully leave output_frame_buffer alone so that pending writes can # still occur. this allows the reconnect to occur silently without # completely breaking any pending data on, say, a channel that was just # opened. self._sock = EventSocket(read_cb=self._sock_read_cb, close_cb=self._sock_close_cb, error_cb=self._sock_error_cb, debug=self._debug, logger=self._logger) self._sock.settimeout(self._connect_timeout) if self._sock_opts: for k, v in self._sock_opts.iteritems(): family, type = k self._sock.setsockopt(family, type, v) self._sock.connect((host, port)) self._sock.setblocking(False) # Only after the socket has connected do we clear this state; closed must # be False so that writes can be buffered in writePacket(). The closed # state might have been set to True due to a socket error or a redirect. self._host = "%s:%d" % (host, port) self._closed = False self._close_info = { 'reply_code': 0, 'reply_text': 'failed to connect to %s' % (self._host), 'class_id': 0, 'method_id': 0 } self._sock.write(PROTOCOL_HEADER) def disconnect(self): ''' Disconnect from the current host, but otherwise leave this object "open" so that it can be reconnected. ''' self._connected = False if self._sock != None: self._sock.close_cb = None try: self._sock.close() except: self.logger.error("Failed to disconnect socket to %s", self._host, exc_info=True) self._sock = None def add_reconnect_callback(self, callback): '''Adds a reconnect callback to the strategy. This can be used to resubscribe to exchanges, etc.''' self._strategy.reconnect_callbacks.append(callback) ### ### EventSocket callbacks ### def _sock_read_cb(self, sock): ''' Callback when there's data to read on the socket. ''' try: self._read_frames() except: self.logger.error("Failed to read frames from %s", self._host, exc_info=True) self.close(reply_code=501, reply_text='Error parsing frames') def _sock_close_cb(self, sock): """ Callback when socket closed. This is intended to be the callback when the closure is unexpected. """ self.logger.warning('socket to %s closed unexpectedly', self._host) self._close_info = { 'reply_code': 0, 'reply_text': 'socket closed unexpectedly to %s' % (self._host), 'class_id': 0, 'method_id': 0 } # We're not connected any more (we're not closed but we're definitely not # connected) self._connected = False self._sock = None # Call back to a user-provided close function self._callback_close() # Fail and do nothing. If you haven't configured permissions and that's # why the socket is closing, this keeps us from looping. self._strategy.fail() def _sock_error_cb(self, sock, msg, exception=None): """ Callback when there's an error on the socket. """ self.logger.error('error on connection to %s: %s', self._host, msg) self._close_info = { 'reply_code': 0, 'reply_text': 'socket error on host %s: %s' % (self._host, msg), 'class_id': 0, 'method_id': 0 } # we're not connected any more (we're not closed but we're definitely not # connected) self._connected = False self._sock = None # Call back to a user-provided close function self._callback_close() # Fail and try to reconnect, because this is expected to be a transient error. self._strategy.fail() self._strategy.next_host() ### ### Connection methods ### def _next_channel_id(self): '''Return the next possible channel id. Is a circular enumeration.''' self._channel_counter += 1 if self._channel_counter >= self._channel_max: self._channel_counter = 1 return self._channel_counter def channel(self, channel_id=None): """ Fetch a Channel object identified by the numeric channel_id, or create that object if it doesn't already exist. If channel_id is not None but no channel exists for that id, will raise InvalidChannel. If there are already too many channels open, will raise TooManyChannels. """ if channel_id is None: # adjust for channel 0 if len(self._channels) - 1 >= self._channel_max: raise Connection.TooManyChannels( "%d channels already open, max %d", len(self._channels) - 1, self._channel_max) channel_id = self._next_channel_id() while channel_id in self._channels: channel_id = self._next_channel_id() elif channel_id in self._channels: return self._channels[channel_id] else: raise Connection.InvalidChannel("%s is not a valid channel id", channel_id) # Call open() here so that ConnectionChannel doesn't have it called. Could # also solve this other ways, but it's a HACK regardless. rval = Channel(self, channel_id) self._channels[channel_id] = rval rval.open() return rval def close(self, reply_code=0, reply_text='', class_id=0, method_id=0): ''' Close this connection. ''' self._close_info = { 'reply_code': reply_code, 'reply_text': reply_text, 'class_id': class_id, 'method_id': method_id } self._channels[0].close() def _close_socket(self): '''Close the socket.''' # The assumption here is that we don't want auto-reconnect to kick in if # the socket is purposefully closed. self._closed = True # By the time we hear about the protocol-level closure, the socket may # have already gone away. if self._sock != None: self._sock.close_cb = None try: self._sock.close() except: self.logger.error('error closing socket') self._sock = None def _callback_close(self): '''Callback to any close handler.''' if self._close_cb: try: self._close_cb() except SystemExit: raise except: self.logger.error('error calling close callback') def _read_frames(self): ''' Read frames from the socket. ''' # Because of the timer callback to dataRead when we re-buffered, there's a # chance that in between we've lost the socket. If that's the case, just # silently return as some code elsewhere would have already notified us. # That bug could be fixed by improving the message reading so that we consume # all possible messages and ensure that only a partial message was rebuffered, # so that we can rely on the next read event to read the subsequent message. if self._sock is None: return data = self._sock.read() reader = Reader(data) p_channels = set() for frame in Frame.read_frames(reader): if self._debug > 1: self.logger.debug("READ: %s", frame) self._frames_read += 1 ch = self.channel(frame.channel_id) ch.buffer_frame(frame) p_channels.add(ch) # Still not clear on what's the best approach here. It seems there's a # slight speedup by calling this directly rather than delaying, but the # delay allows for pending IO with higher priority to execute. self._process_channels(p_channels) #event.timeout(0, self._process_channels, p_channels) # HACK: read the buffer contents and re-buffer. Would prefer to pass # buffer back, but there's no good way of asking the total size of the # buffer, comparing to tell(), and then re-buffering. There's also no # ability to clear the buffer up to the current position. # NOTE: This will be cleared up once eventsocket supports the # uber-awesome buffering scheme that will utilize mmap. if reader.tell() < len(data): self._sock.buffer(data[reader.tell():]) def _process_channels(self, channels): ''' Walk through a set of channels and process their frame buffer. Will collect all socket output and flush in one write. ''' for channel in channels: channel.process_frames() def _flush_buffered_frames(self): # In the rare case (a bug) where this is called but send_frame thinks # they should be buffered, don't clobber. frames = self._output_frame_buffer self._output_frame_buffer = [] for frame in frames: self.send_frame(frame) def send_frame(self, frame): ''' Send a single frame. If there is an output buffer, write to that, else send immediately to the socket. ''' if self._closed: if self._close_info and len(self._close_info['reply_text']) > 0: raise ConnectionClosed("connection is closed: %s : %s"%\ (self._close_info['reply_code'],self._close_info['reply_text']) ) raise ConnectionClosed("connection is closed") if self._sock == None or (not self._connected and frame.channel_id != 0): self._output_frame_buffer.append(frame) return if self._debug > 1: self.logger.debug("WRITE: %s", frame) buf = bytearray() frame.write_frame(buf) self._sock.write(buf) self._frames_written += 1