def _handshake(s, resolved_address): """ :param s: :return: """ local_port = s.getsockname()[1] # Send details of the protocol versions supported supported_versions = [3, 0, 0, 0] handshake = [Bolt.MAGIC_PREAMBLE] + supported_versions log.debug("[#%04X] C: <MAGIC> 0x%08X", local_port, Bolt.MAGIC_PREAMBLE) log.debug("[#%04X] C: <HANDSHAKE> 0x%08X 0x%08X 0x%08X 0x%08X", local_port, *supported_versions) data = b"".join(struct_pack(">I", num) for num in handshake) s.sendall(data) # Handle the handshake response ready_to_read = False while not ready_to_read: ready_to_read, _, _ = select((s, ), (), (), 1) try: data = s.recv(4) except OSError: raise ServiceUnavailable("Failed to read any data from server {!r} " "after connected".format(resolved_address)) data_size = len(data) if data_size == 0: # If no data is returned after a successful select # response, the server has closed the connection log.debug("[#%04X] S: <CLOSE>", local_port) s.close() raise ServiceUnavailable("Connection to %r closed without handshake " "response" % (resolved_address, )) if data_size != 4: # Some garbled data has been received log.debug("[#%04X] S: @*#!", local_port) s.close() raise ProtocolError("Expected four byte Bolt handshake response " "from %r, received %r instead; check for " "incorrect port number" % (resolved_address, data)) elif data == b"HTTP": log.debug("[#%04X] S: <CLOSE>", local_port) s.close() raise ServiceUnavailable("Cannot to connect to Bolt service on {!r} " "(looks like HTTP)".format(resolved_address)) agreed_version = data[-1], data[-2] log.debug("[#%04X] S: <HANDSHAKE> 0x%06X%02X", local_port, agreed_version[1], agreed_version[0]) if agreed_version == (0, 0): log.debug("[#%04X] C: <CLOSE>", local_port) s.shutdown(SHUT_RDWR) s.close() elif agreed_version in ((3, 0), ): return s, agreed_version else: log.debug("[#%04X] S: <CLOSE>", local_port) s.close() raise ProtocolError("Unknown Bolt protocol version: " "{}".format(agreed_version))
def _connect(resolved_address, **config): """ :param resolved_address: :param config: :return: socket object """ s = None try: if len(resolved_address) == 2: s = socket(AF_INET) elif len(resolved_address) == 4: s = socket(AF_INET6) else: raise ValueError("Unsupported address {!r}".format(resolved_address)) t = s.gettimeout() s.settimeout(config.get("connection_timeout", default_config["connection_timeout"])) log_debug("~~ [CONNECT] %s", resolved_address) s.connect(resolved_address) s.settimeout(t) s.setsockopt(SOL_SOCKET, SO_KEEPALIVE, 1 if config.get("keep_alive", default_config["keep_alive"]) else 0) except SocketTimeout: _force_close(s) raise ServiceUnavailable("Timed out trying to establish connection to {!r}".format(resolved_address)) except SocketError as error: _force_close(s) if error.errno in (61, 99, 111, 10061): raise ServiceUnavailable("Failed to establish connection to {!r} (reason {})".format(resolved_address, error.errno)) else: raise except ConnectionResetError: raise ServiceUnavailable("Failed to establish connection to {!r}".format(resolved_address)) else: return s
def _set_defunct(self, message, error=None): direct_driver = isinstance(self.pool, BoltPool) if error: log.error(str(error)) log.error(message) # We were attempting to receive data but the connection # has unexpectedly terminated. So, we need to close the # connection from the client side, and remove the address # from the connection pool. self._defunct = True self.close() if self.pool: self.pool.deactivate(address=self.unresolved_address) # Iterate through the outstanding responses, and if any correspond # to COMMIT requests then raise an error to signal that we are # unable to confirm that the COMMIT completed successfully. for response in self.responses: if isinstance(response, CommitResponse): if error: raise IncompleteCommit(message) from error else: raise IncompleteCommit(message) if direct_driver: if error: raise ServiceUnavailable(message) from error else: raise ServiceUnavailable(message) else: if error: raise SessionExpired(message) from error else: raise SessionExpired(message)
def fetch_message(self): """ Receive at least one message from the server, if available. :return: 2-tuple of number of detail messages and number of summary messages fetched """ if self._closed: raise ServiceUnavailable( "Failed to read from closed connection {!r} ({!r})".format( self.unresolved_address, self.server_info.address)) if self._defunct: raise ServiceUnavailable( "Failed to read from defunct connection {!r} ({!r})".format( self.unresolved_address, self.server_info.address)) if not self.responses: return 0, 0 # Receive exactly one message details, summary_signature, summary_metadata = next(self.inbox) if details: log.debug("[#%04X] S: RECORD * %d", self.local_port, len(details)) # Do not log any data self.responses[0].on_records(details) if summary_signature is None: return len(details), 0 response = self.responses.popleft() response.complete = True if summary_signature == b"\x70": log.debug("[#%04X] S: SUCCESS %r", self.local_port, summary_metadata) response.on_success(summary_metadata or {}) elif summary_signature == b"\x7E": log.debug("[#%04X] S: IGNORED", self.local_port) response.on_ignored(summary_metadata or {}) elif summary_signature == b"\x7F": log.debug("[#%04X] S: FAILURE %r", self.local_port, summary_metadata) try: response.on_failure(summary_metadata or {}) except (ServiceUnavailable, DatabaseUnavailable): if self.pool: self.pool.deactivate(address=self.unresolved_address), raise except (NotALeader, ForbiddenOnReadOnlyDatabase): if self.pool: self.pool.on_write_failure( address=self.unresolved_address), raise else: raise BoltProtocolError( "Unexpected response message with signature %02X" % summary_signature, address=self.unresolved_address) return len(details), 1
def _handshake(s, resolved_address, der_encoded_server_certificate, error_handler, **config): """ :param s: :return: """ # Send details of the protocol versions supported supported_versions = [2, 1, 0, 0] handshake = [MAGIC_PREAMBLE] + supported_versions log_debug("C: [HANDSHAKE] 0x%X %r", MAGIC_PREAMBLE, supported_versions) data = b"".join(struct_pack(">I", num) for num in handshake) s.sendall(data) # Handle the handshake response ready_to_read, _, _ = select((s,), (), (), 0) while not ready_to_read: ready_to_read, _, _ = select((s,), (), (), 0) try: data = s.recv(4) except ConnectionResetError: raise ServiceUnavailable("Failed to read any data from server {!r} after connected".format(resolved_address)) data_size = len(data) if data_size == 0: # If no data is returned after a successful select # response, the server has closed the connection log_error("S: [CLOSE]") s.close() raise ProtocolError("Connection to %r closed without handshake response" % (resolved_address,)) if data_size != 4: # Some garbled data has been received log_error("S: @*#!") s.close() raise ProtocolError("Expected four byte handshake response, received %r instead" % data) agreed_version, = struct_unpack(">I", data) log_debug("S: [HANDSHAKE] %d", agreed_version) if agreed_version == 0: log_debug("~~ [CLOSE]") s.shutdown(SHUT_RDWR) s.close() elif agreed_version in (1, 2): connection = Connection(resolved_address, s, agreed_version, der_encoded_server_certificate=der_encoded_server_certificate, error_handler=error_handler, **config) connection.init() return connection elif agreed_version == 0x48545450: log_error("S: [CLOSE]") s.close() raise ServiceUnavailable("Cannot to connect to Bolt service on {!r} " "(looks like HTTP)".format(resolved_address)) else: log_error("S: [CLOSE]") s.close() raise ProtocolError("Unknown Bolt protocol version: {}".format(agreed_version))
def _handshake(s, resolved_address): """ :param s: Socket :param resolved_address: :return: (socket, version, client_handshake, server_response_data) """ local_port = s.getsockname()[1] # TODO: Optimize logging code handshake = Bolt.get_handshake() import struct handshake = struct.unpack(">16B", handshake) handshake = [handshake[i:i + 4] for i in range(0, len(handshake), 4)] supported_versions = [("0x%02X%02X%02X%02X" % (vx[0], vx[1], vx[2], vx[3])) for vx in handshake] log.debug("[#%04X] C: <MAGIC> 0x%08X", local_port, int.from_bytes(Bolt.MAGIC_PREAMBLE, byteorder="big")) log.debug("[#%04X] C: <HANDSHAKE> %s %s %s %s", local_port, *supported_versions) data = Bolt.MAGIC_PREAMBLE + Bolt.get_handshake() s.sendall(data) # Handle the handshake response ready_to_read = False while not ready_to_read: ready_to_read, _, _ = select((s,), (), (), 1) try: data = s.recv(4) except OSError: raise ServiceUnavailable("Failed to read any data from server {!r} " "after connected".format(resolved_address)) data_size = len(data) if data_size == 0: # If no data is returned after a successful select # response, the server has closed the connection log.debug("[#%04X] S: <CLOSE>", local_port) s.close() raise BoltHandshakeError("Connection to {address} closed without handshake response".format(address=resolved_address), address=resolved_address, request_data=handshake, response_data=None) if data_size != 4: # Some garbled data has been received log.debug("[#%04X] S: @*#!", local_port) s.close() raise BoltProtocolError("Expected four byte Bolt handshake response from %r, received %r instead; check for incorrect port number" % (resolved_address, data), address=resolved_address) elif data == b"HTTP": log.debug("[#%04X] S: <CLOSE>", local_port) s.close() raise ServiceUnavailable("Cannot to connect to Bolt service on {!r} " "(looks like HTTP)".format(resolved_address)) agreed_version = data[-1], data[-2] log.debug("[#%04X] S: <HANDSHAKE> 0x%06X%02X", local_port, agreed_version[1], agreed_version[0]) return s, agreed_version, handshake, data
def send_all(self): """ Send all queued messages to the server. """ if self.closed(): raise ServiceUnavailable( "Failed to write to closed connection {!r} ({!r})".format( self.unresolved_address, self.server_info.address)) if self.defunct(): raise ServiceUnavailable( "Failed to write to defunct connection {!r} ({!r})".format( self.unresolved_address, self.server_info.address)) self._send_all()
def acquire_direct(self, address): """ Acquire a connection to a given address from the pool. The address supplied should always be an IP address, not a host name. This method is thread safe. """ if self.closed(): raise ServiceUnavailable("Connection pool closed") if not is_ip_address(address[0]): raise ValueError("Invalid IP address {!r}".format(address[0])) with self.lock: try: connections = self.connections[address] except KeyError: connections = self.connections[address] = deque() for connection in list(connections): if connection.closed() or connection.defunct(): connections.remove(connection) continue if not connection.in_use: connection.in_use = True return connection try: connection = self.connector(address) except ServiceUnavailable: self.remove(address) raise else: connection.pool = self connection.in_use = True connections.append(connection) return connection
def connect(address, *, timeout, custom_resolver, ssl_context, keep_alive): """ Connect and perform a handshake and return a valid Connection object, assuming a protocol version can be agreed. """ last_error = None # Establish a connection to the host and port specified # Catches refused connections see: # https://docs.python.org/2/library/errno.html log.debug("[#0000] C: <RESOLVE> %s", address) for resolved_address in Address(address).resolve(resolver=custom_resolver): s = None try: host = address[0] s = _connect(resolved_address, timeout, keep_alive) s = _secure(s, host, ssl_context) return _handshake(s, address) except Exception as error: if s: s.close() last_error = error if last_error is None: raise ServiceUnavailable("Failed to resolve addresses for %s" % address) else: raise last_error
def fetch_routing_info(self, address): """ Fetch raw routing info from a given router address. :param address: router address :return: list of routing records or None if no connection could be established :raise ServiceUnavailable: if the server does not support routing or if routing support is broken """ metadata = {} records = [] def fail(md): if md.get("code") == "Neo.ClientError.Procedure.ProcedureNotFound": raise RoutingProtocolError("Server {!r} does not support routing".format(address)) else: raise RoutingProtocolError("Routing support broken on server {!r}".format(address)) try: with self.acquire_direct(address) as cx: _, _, server_version = (cx.server.agent or "").partition("/") log.debug("[#%04X] C: <ROUTING> query=%r", cx.local_port, self.routing_context or {}) cx.run("CALL dbms.cluster.routing.getRoutingTable({context})", {"context": self.routing_context}, on_success=metadata.update, on_failure=fail) cx.pull_all(on_success=metadata.update, on_records=records.extend) cx.send_all() cx.fetch_all() routing_info = [dict(zip(metadata.get("fields", ()), values)) for values in records] log.debug("[#%04X] S: <ROUTING> info=%r", cx.local_port, routing_info) return routing_info except RoutingProtocolError as error: raise ServiceUnavailable(*error.args) except ServiceUnavailable: self.deactivate(address) return None
def on_failure(self, metadata): code = metadata.get("code") message = metadata.get("message", "Connection initialisation failed") if code == "Neo.ClientError.Security.Unauthorized": raise AuthError(message) else: raise ServiceUnavailable(message)
def connect(address, **config): """ Connect and perform a handshake and return a valid Connection object, assuming a protocol version can be agreed. """ ssl_context = make_ssl_context(**config) last_error = None # Establish a connection to the host and port specified # Catches refused connections see: # https://docs.python.org/2/library/errno.html log.debug("[#0000] C: <RESOLVE> %s", address) address_list = AddressList([address]) address_list.custom_resolve(config.get("resolver")) address_list.dns_resolve() for resolved_address in address_list: s = None try: host = address[0] s = _connect(resolved_address, **config) s, der_encoded_server_certificate = _secure(s, host, ssl_context) connection = _handshake(s, address, der_encoded_server_certificate, **config) except Exception as error: if s: s.close() last_error = error else: return connection if last_error is None: raise ServiceUnavailable("Failed to resolve addresses for %s" % address) else: raise last_error
def update_routing_table(self, *, database): """ Update the routing table from the first router able to provide valid routing information. :param database: The database name :raise neo4j.exceptions.ServiceUnavailable: """ # copied because it can be modified existing_routers = list(self.routing_tables[database].routers) has_tried_initial_routers = False if self.routing_tables[database].missing_fresh_writer(): # TODO: Test this state has_tried_initial_routers = True if self.update_routing_table_from(self.first_initial_routing_address, database=database): # Why is only the first initial routing address used? return if self.update_routing_table_from(*existing_routers, database=database): return if not has_tried_initial_routers and self.first_initial_routing_address not in existing_routers: if self.update_routing_table_from(self.first_initial_routing_address, database=database): # Why is only the first initial routing address used? return # None of the routers have been successful, so just fail log.error("Unable to retrieve routing information") raise ServiceUnavailable("Unable to retrieve routing information")
def connect(address, ssl_context=None, error_handler=None, **config): """ Connect and perform a handshake and return a valid Connection object, assuming a protocol version can be agreed. """ last_error = None # Establish a connection to the host and port specified # Catches refused connections see: # https://docs.python.org/2/library/errno.html log_debug("~~ [RESOLVE] %s", address) resolver = Resolver(custom_resolver=config.get("resolver")) resolver.addresses.append(address) resolver.custom_resolve() resolver.dns_resolve() for resolved_address in resolver.addresses: log_debug("~~ [RESOLVED] %s -> %s", address, resolved_address) try: s = _connect(resolved_address, **config) s, der_encoded_server_certificate = _secure( s, address[0], ssl_context, **config) connection = _handshake(s, resolved_address, der_encoded_server_certificate, error_handler, **config) except Exception as error: last_error = error else: return connection if last_error is None: raise ServiceUnavailable("Failed to resolve addresses for %s" % address) else: raise last_error
def acquire_direct(self, address): """ Acquire a connection to a given address from the pool. The address supplied should always be an IP address, not a host name. This method is thread safe. """ if self.closed(): raise ServiceUnavailable("Connection pool closed") if not is_ip_address(address[0]): raise ValueError("Invalid IP address {!r}".format(address[0])) with self.lock: try: connections = self.connections[address] except KeyError: connections = self.connections[address] = deque() connection_acquisition_start_timestamp = clock() while True: # try to find a free connection in pool for connection in list(connections): if connection.closed() or connection.defunct( ) or connection.timedout(): connections.remove(connection) continue if not connection.in_use: connection.in_use = True return connection # all connections in pool are in-use can_create_new_connection = self._max_connection_pool_size == INFINITE or len( connections) < self._max_connection_pool_size if can_create_new_connection: try: connection = self.connector( address, self.connection_error_handler) except ServiceUnavailable: self.remove(address) raise else: connection.pool = self connection.in_use = True connections.append(connection) return connection # failed to obtain a connection from pool because the pool is full and no free connection in the pool span_timeout = self._connection_acquisition_timeout - ( clock() - connection_acquisition_start_timestamp) if span_timeout > 0: self.cond.wait(span_timeout) # if timed out, then we throw error. This time computation is needed, as with python 2.7, we cannot # tell if the condition is notified or timed out when we come to this line if self._connection_acquisition_timeout <= ( clock() - connection_acquisition_start_timestamp): raise ClientError( "Failed to obtain a connection from pool within {!r}s" .format(self._connection_acquisition_timeout)) else: raise ClientError( "Failed to obtain a connection from pool within {!r}s". format(self._connection_acquisition_timeout))
def _run_transaction(self, access_mode, transaction_function, *args, **kwargs): if not callable(transaction_function): raise TypeError("Unit of work is not callable") metadata = getattr(transaction_function, "metadata", None) timeout = getattr(transaction_function, "timeout", None) retry_delay = retry_delay_generator( self._config.initial_retry_delay, self._config.retry_delay_multiplier, self._config.retry_delay_jitter_factor) errors = [] t0 = -1 # Timer while True: try: self._open_transaction(access_mode=access_mode, database=self._config.database, metadata=metadata, timeout=timeout) tx = self._transaction try: result = transaction_function(tx, *args, **kwargs) except Exception: tx.close() raise else: tx.commit() except IncompleteCommit: raise except (ServiceUnavailable, SessionExpired) as error: errors.append(error) self._disconnect() except TransientError as transient_error: if not transient_error.is_retriable(): raise errors.append(transient_error) else: return result if t0 == -1: t0 = perf_counter( ) # The timer should be started after the first attempt t1 = perf_counter() if t1 - t0 > self._config.max_transaction_retry_time: break delay = next(retry_delay) log.warning( "Transaction failed and will be retried in {}s ({})".format( delay, "; ".join(errors[-1].args))) sleep(delay) if errors: raise errors[-1] else: raise ServiceUnavailable("Transaction failed")
def acquire(self, access_mode=None): for address in resolve(self.address): try: connection = self.acquire_direct(address) # should always be a resolved address except ServiceUnavailable: pass else: return connection raise ServiceUnavailable("Cannot acquire connection to {!r}".format(self.address))
def _connect(resolved_address, timeout, keep_alive): """ :param resolved_address: :param timeout: seconds :param keep_alive: True or False :return: socket object """ s = None # The socket try: if len(resolved_address) == 2: s = socket(AF_INET) elif len(resolved_address) == 4: s = socket(AF_INET6) else: raise ValueError( "Unsupported address {!r}".format(resolved_address)) t = s.gettimeout() if timeout: s.settimeout(timeout) log.debug("[#0000] C: <OPEN> %s", resolved_address) s.connect(resolved_address) s.settimeout(t) keep_alive = 1 if keep_alive else 0 s.setsockopt(SOL_SOCKET, SO_KEEPALIVE, keep_alive) except SocketTimeout: log.debug("[#0000] C: <TIMEOUT> %s", resolved_address) log.debug("[#0000] C: <CLOSE> %s", resolved_address) s.close() raise ServiceUnavailable( "Timed out trying to establish connection to {!r}".format( resolved_address)) except OSError as error: log.debug("[#0000] C: <ERROR> %s %s", type(error).__name__, " ".join(map(repr, error.args))) log.debug("[#0000] C: <CLOSE> %s", resolved_address) s.close() raise ServiceUnavailable( "Failed to establish connection to {!r} (reason {})".format( resolved_address, error)) else: return s
def neo4j_driver(cls, *targets, auth=None, routing_context=None, **config): """ Create a driver for routing-capable Neo4j service access that uses socket I/O and thread-based concurrency. """ from neo4j._exceptions import BoltHandshakeError, BoltSecurityError try: return Neo4jDriver.open(*targets, auth=auth, routing_context=routing_context, **config) except (BoltHandshakeError, BoltSecurityError) as error: from neo4j.exceptions import ServiceUnavailable raise ServiceUnavailable(str(error)) from error
def bolt_driver(cls, target, *, auth=None, **config): """ Create a driver for direct Bolt server access that uses socket I/O and thread-based concurrency. """ from neo4j._exceptions import BoltHandshakeError, BoltSecurityError try: return BoltDriver.open(target, auth=auth, **config) except (BoltHandshakeError, BoltSecurityError) as error: from neo4j.exceptions import ServiceUnavailable raise ServiceUnavailable(str(error)) from error
def _connect(resolved_address, timeout=None, **config): """ :param resolved_address: :param config: :return: socket object """ s = None try: if len(resolved_address) == 2: s = socket(AF_INET) elif len(resolved_address) == 4: s = socket(AF_INET6) else: raise ValueError("Unsupported address " "{!r}".format(resolved_address)) t = s.gettimeout() if timeout is None: s.settimeout(DEFAULT_CONNECTION_TIMEOUT) else: s.settimeout(timeout) log.debug("[#0000] C: <OPEN> %s", resolved_address) s.connect(resolved_address) s.settimeout(t) keep_alive = 1 if config.get("keep_alive", DEFAULT_KEEP_ALIVE) else 0 s.setsockopt(SOL_SOCKET, SO_KEEPALIVE, keep_alive) except SocketTimeout: log.debug("[#0000] C: <TIMEOUT> %s", resolved_address) log.debug("[#0000] C: <CLOSE> %s", resolved_address) s.close() raise ServiceUnavailable("Timed out trying to establish connection " "to {!r}".format(resolved_address)) except OSError as error: log.debug("[#0000] C: <ERROR> %s %s", type(error).__name__, " ".join(map(repr, error.args))) log.debug("[#0000] C: <CLOSE> %s", resolved_address) s.close() raise ServiceUnavailable("Failed to establish connection to {!r} " "(reason {})".format(resolved_address, error)) else: return s
def test_serviceunavailable_raised_from_bolt_protocol_error_with_explicit_style( ): error = BoltProtocolError( "Driver does not support Bolt protocol version: 0x%06X%02X" % (2, 5), address="localhost") with pytest.raises(ServiceUnavailable) as e: assert error.address == "localhost" try: raise error except BoltProtocolError as error_bolt_protocol: error_nested = ServiceUnavailable(str(error_bolt_protocol)) error_nested.__cause__ = error_bolt_protocol raise error_nested # The regexp parameter of the match method is matched with the re.search function. with pytest.raises(AssertionError): e.match("FAIL!") e.match("Driver does not support Bolt protocol version: 0x00000205") assert e.value.__cause__ is error
def send_all(self): """ Send all queued messages to the server. """ if self.closed(): raise ServiceUnavailable("Failed to write to closed connection {!r} ({!r})".format( self.unresolved_address, self.server_info.address)) if self.defunct(): raise ServiceUnavailable("Failed to write to defunct connection {!r} ({!r})".format( self.unresolved_address, self.server_info.address)) try: self._send_all() except (IOError, OSError) as error: log.error("Failed to write data to connection " "{!r} ({!r}); ({!r})". format(self.unresolved_address, self.server_info.address, "; ".join(map(repr, error.args)))) if self.pool: self.pool.deactivate(address=self.unresolved_address) raise
def _run_transaction(self, access_mode, unit_of_work, *args, **kwargs): if not callable(unit_of_work): raise TypeError("Unit of work is not callable") metadata = getattr(unit_of_work, "metadata", None) timeout = getattr(unit_of_work, "timeout", None) retry_delay = retry_delay_generator( self._config.initial_retry_delay, self._config.retry_delay_multiplier, self._config.retry_delay_jitter_factor) errors = [] t0 = perf_counter() while True: try: self._open_transaction(access_mode, metadata, timeout) tx = self._transaction try: result = unit_of_work(tx, *args, **kwargs) except Exception: tx.success = False raise else: if tx.success is None: tx.success = True finally: tx.close() except (ServiceUnavailable, SessionExpired, ConnectionExpired) as error: errors.append(error) except TransientError as error: if is_retriable_transient_error(error): errors.append(error) else: raise else: return result t1 = perf_counter() if t1 - t0 > self._config.max_retry_time: break delay = next(retry_delay) log.warning("Transaction failed and will be retried in {}s " "({})".format(delay, "; ".join(errors[-1].args))) sleep(delay) if errors: raise errors[-1] else: raise ServiceUnavailable("Transaction failed")
def _verify_routing_connectivity(self): from neo4j.exceptions import ServiceUnavailable from neo4j._exceptions import BoltHandshakeError table = self.get_routing_table() routing_info = {} for ix in list(table.routers): try: routing_info[ix] = self._pool.fetch_routing_info(table.routers[0]) except BoltHandshakeError as error: routing_info[ix] = None for key, val in routing_info.items(): if val is not None: return routing_info raise ServiceUnavailable("Could not connect to any routing servers.")
def _run_transaction(self, access_mode, unit_of_work, *args, **kwargs): if not callable(unit_of_work): raise TypeError("Unit of work is not callable") retry_delay = retry_delay_generator(INITIAL_RETRY_DELAY, RETRY_DELAY_MULTIPLIER, RETRY_DELAY_JITTER_FACTOR) errors = [] t0 = perf_counter() while True: try: self._create_transaction() self._connect(access_mode) self.__begin__() tx = self._transaction try: result = unit_of_work(tx, *args, **kwargs) except: if tx.success is None: tx.success = False raise else: if tx.success is None: tx.success = True finally: tx.close() except (ServiceUnavailable, SessionExpired) as error: errors.append(error) except TransientError as error: if is_retriable_transient_error(error): errors.append(error) else: raise else: return result t1 = perf_counter() if t1 - t0 > self._max_retry_time: break sleep(next(retry_delay)) if errors: raise errors[-1] else: raise ServiceUnavailable("Transaction failed")
def _verify_routing_connectivity(self): from neo4j.exceptions import ServiceUnavailable from neo4j._exceptions import BoltHandshakeError table = self._pool.get_routing_table_for_default_database() routing_info = {} for ix in list(table.routers): try: routing_info[ix] = self._pool.fetch_routing_info( address=table.routers[0], timeout=self._default_workspace_config. connection_acquisition_timeout, database=self._default_workspace_config.database) except BoltHandshakeError as error: routing_info[ix] = None for key, val in routing_info.items(): if val is not None: return routing_info raise ServiceUnavailable("Could not connect to any routing servers.")
def fetch_routing_info(self, address, database, bookmarks, timeout): """ Fetch raw routing info from a given router address. :param address: router address :param database: the database name to get routing table for :param bookmarks: iterable of bookmark values after which the routing info should be fetched :param timeout: connection acquisition timeout in seconds :return: list of routing records, or None if no connection could be established or if no readers or writers are present :raise ServiceUnavailable: if the server does not support routing, or if routing support is broken or outdated """ try: cx = self._acquire(address, timeout) try: routing_table = cx.route( database or self.workspace_config.database, bookmarks) finally: self.release(cx) except BoltRoutingError as error: # Connection was successful, but routing support is # broken. This may indicate that the routing procedure # does not exist (for protocol versions prior to 4.3). # This error is converted into ServiceUnavailable, # therefore surfacing to the application as a signal that # routing is broken. log.debug("Routing is broken (%s)", error) raise ServiceUnavailable(*error.args) except ServiceUnavailable as error: # The routing table request suffered a connection # failure. This should return a null routing table, # signalling to the caller to retry the request # elsewhere. log.debug("Routing is unavailable (%s)", error) routing_table = None # If the routing table is empty, deactivate the address. if not routing_table: self.deactivate(address) return routing_table
def fetch_routing_info(self, *, address, timeout, database): """ Fetch raw routing info from a given router address. :param address: router address :param timeout: seconds :param database: the data base name to get routing table for :param address: the address by which the client initially contacted the server as a hint for inclusion in the returned routing table. :return: list of routing records or None if no connection could be established :raise ServiceUnavailable: if the server does not support routing or if routing support is broken """ try: with self._acquire(address, timeout) as cx: return cx.route(database or self.workspace_config.database) except BoltRoutingError as error: raise ServiceUnavailable(*error.args) except ServiceUnavailable: self.deactivate(address=address) return None
def commit(self): """Mark this transaction as successful and close in order to trigger a COMMIT. :raise TransactionError: if the transaction is already closed """ if self._closed: raise TransactionError("Transaction closed") metadata = {} self._consume_results() # DISCARD pending records then do a commit. try: self._connection.commit(on_success=metadata.update) self._connection.send_all() self._connection.fetch_all() except BoltIncompleteCommitError: self._closed = True self._on_closed() raise ServiceUnavailable("Connection closed during commit") self._bookmark = metadata.get("bookmark") self._closed = True self._on_closed() return self._bookmark