@Override public CompletableFuture<Connection> connect(Address address) { if (this.address.equals(address)) { return local.connect(address); } return remote.connect(address); }
/** Handles closing the session. */ private void onClose() { if (isOpen()) { LOGGER.debug("Closed session: {}", id); this.id = 0; this.state = State.CLOSED; if (connection != null) connection.close(); client.close(); context.close(); closeListeners.forEach(l -> l.accept(this)); } }
/** * Sends a session request. * * @param request The request to send. * @param future The future to complete once the response is received. * @param checkOpen Whether to check if the session is open. * @param <T> The request type. * @param <U> The response type. * @return The provided future to be completed once the response is received. */ private <T extends Request<T>, U extends Response<U>> CompletableFuture<U> request( T request, CompletableFuture<U> future, boolean checkOpen, boolean recordFailures) { context.checkThread(); // If the session already expired, immediately fail the future. if (checkOpen && !isOpen()) { future.completeExceptionally(new IllegalStateException("session expired")); return future; } // If we're already connected to a server, use the existing connection. The connection will be // reset in the event // of an error on any connection, or callers can reset connections as well. if (connection != null) { return request(request, connection, future, checkOpen, true); } // If we've run out of servers to which to attempt to connect, determine whether we should // expire the // session based on the responses from servers with which we did successfully communicate and // the // time we were last able to successfully communicate with a correct server process. The // failureTime // indicates the first time we received a NO_LEADER_ERROR from a server. if (connectMembers.isEmpty()) { // If open checks are not being performed, don't retry connecting to the servers. Simply fail. if (!checkOpen) { LOGGER.warn("Failed to connect to cluster"); future.completeExceptionally(new IllegalStateException("session not open")); } // If retries have already been scheduled, queue a callback to be called to retry the request. else if (retryFuture != null) { retries.add( () -> { LOGGER.debug("Retrying: {}", request); request(request, future, true, true); }); } // If all servers indicated that no leader could be found and a session timeout has elapsed, // time out the session. else if (failureTime > 0 && failureTime + timeout < System.currentTimeMillis()) { LOGGER.warn("Lost session"); resetConnection().onExpire(); future.completeExceptionally(new IllegalStateException("session expired")); } // If not all servers responded with a NO_LEADER_EXCEPTION or less than a session timeout has // expired, // schedule a retry to attempt to connect to servers again. else { LOGGER.warn("Failed to communicate with cluster. Retrying"); retryFuture = context.schedule(Duration.ofMillis(200), this::retryRequests); retries.add(() -> resetMembers().request(request, future, true, true)); } return future; } // Remove the next random member from the members list. Address member = connectMembers.remove(random.nextInt(connectMembers.size())); // Connect to the server. If the connection fails, recursively attempt to connect to the next // server, // otherwise setup the connection and send the request. if (connectFuture == null) { // If there's no existing connect future, create a new one. LOGGER.info("Connecting: {}", member.socketAddress()); connectFuture = client .connect(member) .thenCompose(this::setupConnection) .whenComplete( (connection, error) -> { connectFuture = null; if (!checkOpen || isOpen()) { if (error == null) { request(request, connection, future, checkOpen, recordFailures); } else { LOGGER.info("Failed to connect: {}", member.socketAddress()); resetConnection().request(request, future, checkOpen, recordFailures); } } else { future.completeExceptionally(new IllegalStateException("session not open")); } }); } else { // We don't want concurrent requests to attempt to connect to the same server at the same // time, so // if the connection is already being attempted, piggyback on the existing connect future. connectFuture.whenComplete( (connection, error) -> { if (!checkOpen || isOpen()) { if (error == null) { request(request, connection, future, checkOpen, recordFailures); } else { request(request, future, checkOpen, recordFailures); } } else { future.completeExceptionally(new IllegalStateException("session not open")); } }); } return future; }
@Override public CompletableFuture<Void> close() { return remote.close().thenRun(local::close); }