def test_reading_partial_line_non_blocking(self): mock_socket = mock.Mock() mock_socket.recv = mock.MagicMock(side_effect=["partial-".encode(), "line\n".encode()]) protocol = Protocol(mock_socket) line = protocol.readline(0.1) assert line == 'partial-line'
def test_disconnect_is_detected_blocking(self): mock_socket = mock.Mock() mock_socket.recv = mock.MagicMock(side_effect=["partial-".encode(), ''.encode()]) protocol = Protocol(mock_socket) with pytest.raises(ClientDisconnected): protocol.readline()
def handle(self): self._context.requests_count.incr() protocol = Protocol(self.request) try: line = protocol.readline() except ClientDisconnected: logger.info("Client disconnected before getting line.") protocol.close() return action = Action.from_line(line) if action.is_valid(): logger.debug("Received valid action: '%s'", action) else: return self._invalid_request(protocol, line) if action.name == const.ACTION_SERVER_SHUTDOWN: return handlers.ShutdownActionHandler( protocol, action, server=self.server).handle_action() if action.name == const.ACTION_PING: return handlers.PingActionHandler(protocol, action).handle_action() if action.name == const.ACTION_STATS: return handlers.StatsActionHandler( protocol, action, context=self._context).handle_action() if action.name != const.ACTION_LOCK: return handlers.InvalidActionActionHandler(protocol, action).handle_action() lock_name = action.params.get('name') if not const.VALID_LOCK_NAME_RE.match(lock_name): return handlers.InvalidLockActionHandler( protocol, action, lock_name=lock_name).handle_action() # Get the Lock, only while holding the GLOBAL LOCK # This is done to avoid 2 concurrent clients creating 2 instances of the same lock at the same time with self._context.global_lock: lock = self._context.locks[lock_name] # Acquire lock and proceed, or return failure. # If multiple concurrent clients try to get the lock, only one will proceed if lock.acquire_non_blocking(): self._context.lock_acquired_count.incr() try: return handlers.LockGrantedActionHandler( protocol, action, lock=lock, lock_name=lock_name).handle_action() finally: lock.release() else: self._context.lock_not_acquired_count.incr() return handlers.LockNotGrantedActionHandler( protocol, action, lock_name=lock_name).handle_action()
def test_reading_after_receiving_multiple_lines_non_blocking(self): mock_socket = mock.Mock() mock_socket.recv = mock.MagicMock(side_effect=["line1\nline2\nline3\n".encode()]) protocol = Protocol(mock_socket) line = protocol.readline(0.1) assert line == 'line1' line = protocol.readline(0.1) assert line == 'line2' line = protocol.readline(0.1) assert line == 'line3'
def __init__(self, host='localhost', port=DEFAULT_PORT, client_id=None): """ Creates a client to connect to the server. :param host: hostname of the server :param port: port to connect """ self._host = host self._port = port self._acquired = None self._socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM) self._protocol = Protocol(self._socket) self._client_id = client_id Utils.validate_client_id(client_id)
class LockClient: DEFAULT_PORT = server.TCPServer.DEFAULT_PORT def __init__(self, host='localhost', port=DEFAULT_PORT, client_id=None): """ Creates a client to connect to the server. :param host: hostname of the server :param port: port to connect """ self._host = host self._port = port self._acquired = None self._socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM) self._protocol = Protocol(self._socket) self._client_id = client_id Utils.validate_client_id(client_id) def connect(self): """ Connects to server. :raises ConnectionRefusedError if connection is refused :return: """ logger.info("Connecting to '%s:%s'...", self._host, self._port) self._socket.connect((self._host, self._port)) def lock(self, name: str) -> bool: """ Tries to acquire a lock :param name: lock name :return: boolean indicating if lock as acquired or not """ if not Utils.valid_lock_name(name): raise common.InvalidLockNameError( "Lock name is invalid: '{lock_name}'".format(lock_name=name)) response_code = AcquireLockClientAction(self._protocol, None, [ constants.RESPONSE_OK, constants.RESPONSE_LOCK_NOT_GRANTED, constants.RESPONSE_ERR ]).handle(lock_name=name, client_id=self._client_id) # FIXME: raise specific exception if RESPONSE_ERR is received (ex: InvalidClientId) self._acquired = bool(response_code == constants.RESPONSE_OK) return self._acquired def server_shutdown(self): """Send order to shutdown the server""" return ClientAction(self._protocol, constants.ACTION_SERVER_SHUTDOWN, [constants.RESPONSE_SHUTTING_DOWN]).handle() def ping(self): """Send ping to the server""" return ClientAction(self._protocol, constants.ACTION_PING, [constants.RESPONSE_PONG]).handle() def stats(self): """Get stats from server""" return GetStatsClientAction(self._protocol, constants.ACTION_STATS, constants.RESPONSE_STATS_COMING).handle() def keepalive(self): """Send a keepalive to the server""" return ClientAction(self._protocol, constants.ACTION_KEEPALIVE, [constants.RESPONSE_STILL_ALIVE]).handle() def release(self): """Release the held lock""" return ClientAction(self._protocol, constants.ACTION_RELEASE, [constants.RESPONSE_RELEASED]).handle() def check_connection(self): """ Raises ClientDisconnected if connection is closed (this is used to detect cases like if the server died. """ self._protocol.check_connection() def close(self): """Close the socket. As a result of the disconnection, the lock will be released at the server.""" logger.debug("Closing the socket...") self._protocol.close() @property def acquired(self) -> bool: """ Returns boolean indicating if lock was acquired or not :return: True if lock was acquired, False otherwise """ assert self._acquired in (True, False) # Fail if lock() wasn't called return self._acquired
def _invalid_request(self, protocol: Protocol, line: str): logger.warning("Received invalid request: '%s'", line) protocol.send(const.RESPONSE_INVALID_REQUEST) self.request.close()
def test_reading_full_line_blocking(self): mock_socket = mock.Mock() mock_socket.recv = mock.MagicMock(side_effect=["full-line\n".encode()]) protocol = Protocol(mock_socket) line = protocol.readline() assert line == 'full-line'
def test_non_blocking_protocol(self): mock_socket = mock.Mock() mock_socket.recv = mock.MagicMock(side_effect=[socket.timeout()]) protocol = Protocol(mock_socket) line = protocol.readline(0.1) assert line is None