All Downloads are FREE. Search and download functionalities are using the official Maven repository.

net.ravendb.client.documents.subscriptions.SubscriptionWorker Maven / Gradle / Ivy

There is a newer version: 6.0.1
Show newest version
package net.ravendb.client.documents.subscriptions;

import com.fasterxml.jackson.core.JsonParser;
import com.fasterxml.jackson.core.TreeNode;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.node.ArrayNode;
import com.fasterxml.jackson.databind.node.ObjectNode;
import net.ravendb.client.documents.DocumentStore;
import net.ravendb.client.documents.commands.GetTcpInfoForRemoteTaskCommand;
import net.ravendb.client.exceptions.AllTopologyNodesDownException;
import net.ravendb.client.exceptions.ClientVersionMismatchException;
import net.ravendb.client.exceptions.cluster.NodeIsPassiveException;
import net.ravendb.client.exceptions.database.DatabaseDoesNotExistException;
import net.ravendb.client.exceptions.documents.subscriptions.*;
import net.ravendb.client.exceptions.security.AuthorizationException;
import net.ravendb.client.extensions.JsonExtensions;
import net.ravendb.client.http.RequestExecutor;
import net.ravendb.client.http.ServerNode;
import net.ravendb.client.primitives.*;
import net.ravendb.client.serverwide.commands.GetTcpInfoCommand;
import net.ravendb.client.serverwide.commands.TcpConnectionInfo;
import net.ravendb.client.serverwide.tcp.TcpConnectionHeaderMessage;
import net.ravendb.client.serverwide.tcp.TcpConnectionHeaderResponse;
import net.ravendb.client.serverwide.tcp.TcpNegotiateParameters;
import net.ravendb.client.serverwide.tcp.TcpNegotiation;
import net.ravendb.client.util.TcpUtils;
import org.apache.commons.io.IOUtils;
import org.apache.commons.lang3.ObjectUtils;
import org.apache.commons.lang3.StringUtils;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;

import java.io.IOException;
import java.net.Socket;
import java.security.GeneralSecurityException;
import java.util.*;
import java.util.concurrent.CompletableFuture;
import java.util.function.Consumer;
import java.util.stream.Collectors;

public class SubscriptionWorker implements CleanCloseable {

    private final Class _clazz;
    private final boolean _revisions;
    private final Log _logger;
    private final DocumentStore _store;
    private final String _dbName;
    private final CancellationTokenSource _processingCts = new CancellationTokenSource();
    private final SubscriptionWorkerOptions _options;
    private Consumer> _subscriber;
    private Socket _tcpClient;
    private JsonParser _parser;
    private boolean _disposed;
    private CompletableFuture _subscriptionTask;
    private int _forcedTopologyUpdateAttempts = 0;

    private List>> afterAcknowledgment;
    private List> onSubscriptionConnectionRetry;

    public void addAfterAcknowledgmentListener(Consumer> handler) {
        afterAcknowledgment.add(handler);
    }

    public void removeAfterAcknowledgmentListener(Consumer> handler) {
        afterAcknowledgment.remove(handler);
    }

    public void addOnSubscriptionConnectionRetry(Consumer handler) {
        onSubscriptionConnectionRetry.add(handler);
    }

    public void removeOnSubscriptionConnectionRetry(Consumer handler) {
        onSubscriptionConnectionRetry.remove(handler);
    }

    @SuppressWarnings("unchecked")
    SubscriptionWorker(Class clazz, SubscriptionWorkerOptions options, boolean withRevisions, DocumentStore documentStore, String dbName) {
        _clazz = (Class) clazz;
        _options = options;
        _revisions = withRevisions;

        if (StringUtils.isEmpty(options.getSubscriptionName())) {
            throw new IllegalArgumentException("SubscriptionConnectionOptions must specify the subscriptionName");
        }
        _store = documentStore;
        _dbName = ObjectUtils.firstNonNull(dbName, documentStore.getDatabase());
        _logger = LogFactory.getLog(SubscriptionWorker.class);

        afterAcknowledgment = new ArrayList<>();
        onSubscriptionConnectionRetry = new ArrayList<>();
    }

    @Override
    public void close() {
        close(true);
    }

    public void close(boolean waitForSubscriptionTask) {
        try {
            if (_disposed) {
                return;
            }

            _disposed = true;
            _processingCts.cancel();


            closeTcpClient(); // we disconnect immediately

            if (_subscriptionTask != null && waitForSubscriptionTask) {
                try {
                    _subscriptionTask.get();
                } catch (Exception e) {
                    // just need to wait for it to end
                }
            }

            if (_subscriptionLocalRequestExecutor != null) {
                _subscriptionLocalRequestExecutor.close();
            }
        } catch (Exception ex) {
            if (_logger.isDebugEnabled()) {
                _logger.debug("Error during close of subscription: " + ex.getMessage(), ex);
            }
        } finally {
            if (onClosed != null) {
                onClosed.accept(this);
            }
        }
    }


    public CompletableFuture run(Consumer> processDocuments) {
        if (processDocuments == null) {
            throw new IllegalArgumentException("ProcessDocuments cannot be null");
        }
        _subscriber = processDocuments;
        return run();
    }

    private CompletableFuture run() {
        if (_subscriptionTask != null) {
            throw new IllegalStateException("The subscription is already running");
        }

        return _subscriptionTask = runSubscriptionAsync();
    }

    private ServerNode _redirectNode;
    private RequestExecutor _subscriptionLocalRequestExecutor;

    public String getCurrentNodeTag() {
        if (_redirectNode != null) {
            return _redirectNode.getClusterTag();
        }
        return null;
    }

    public String getSubscriptionName() {
        if (_options != null) {
            return _options.getSubscriptionName();
        }
        return null;
    }

    private Socket connectToServer() throws IOException, GeneralSecurityException {
        GetTcpInfoForRemoteTaskCommand command = new GetTcpInfoForRemoteTaskCommand(
                "Subscription/" + _dbName,
                _dbName,
                _options != null ? _options.getSubscriptionName() : null,
                true);

        RequestExecutor requestExecutor = _store.getRequestExecutor(_dbName);

        TcpConnectionInfo tcpInfo;

        if (_redirectNode != null) {
            try {
                requestExecutor.execute(_redirectNode, null, command, false, null);
                tcpInfo = command.getResult();
            } catch (ClientVersionMismatchException e) {
                tcpInfo = legacyTryGetTcpInfo(requestExecutor, _redirectNode);
            } catch (Exception e) {
                // if we failed to talk to a node, we'll forget about it and let the topology to
                // redirect us to the current node

                _redirectNode = null;
                throw new RuntimeException(e);
            }
        } else {
            try {
                requestExecutor.execute(command);
                tcpInfo = command.getResult();

                final List tcpInfoUrls = Arrays.stream(tcpInfo.getUrls()).collect(Collectors.toList());

                _redirectNode = requestExecutor.getTopology().getNodes()
                        .stream()
                        .filter(x -> tcpInfoUrls.contains(x.getUrl()))
                        .findFirst()
                        .orElse(null);
            } catch (ClientVersionMismatchException e) {
                tcpInfo = legacyTryGetTcpInfo(requestExecutor);
            }
        }

        Tuple socketStringTuple = TcpUtils.connectWithPriority(tcpInfo, command.getResult().getCertificate(), _store.getCertificate(), _store.getCertificatePrivateKeyPassword());
        _tcpClient = socketStringTuple.first;
        String chosenUrl = socketStringTuple.second;
        _tcpClient.setTcpNoDelay(true);
        _tcpClient.setSendBufferSize(_options.getSendBufferSize());
        _tcpClient.setReceiveBufferSize(_options.getReceiveBufferSize());

        String databaseName = ObjectUtils.firstNonNull(_dbName, _store.getDatabase());

        TcpNegotiateParameters parameters = new TcpNegotiateParameters();
        parameters.setDatabase(databaseName);
        parameters.setOperation(TcpConnectionHeaderMessage.OperationTypes.SUBSCRIPTION);
        parameters.setVersion(TcpConnectionHeaderMessage.SUBSCRIPTION_TCP_VERSION);
        parameters.setReadResponseAndGetVersionCallback(this::readServerResponseAndGetVersion);
        parameters.setDestinationNodeTag(getCurrentNodeTag());
        parameters.setDestinationUrl(chosenUrl);

        _supportedFeatures = TcpNegotiation.negotiateProtocolVersion(_tcpClient.getOutputStream(), parameters);

        if (_supportedFeatures.protocolVersion <= 0) {
            throw new IllegalStateException(_options.getSubscriptionName() + " : TCP negotiation resulted with an invalid protocol version: " + _supportedFeatures.protocolVersion);
        }

        byte[] options = JsonExtensions.getDefaultMapper().writeValueAsBytes(_options);

        _tcpClient.getOutputStream().write(options);
        _tcpClient.getOutputStream().flush();

        if (_subscriptionLocalRequestExecutor != null) {
            _subscriptionLocalRequestExecutor.close();
        }
        _subscriptionLocalRequestExecutor = RequestExecutor.createForSingleNodeWithoutConfigurationUpdates(
                command.getRequestedNode().getUrl(),
                _dbName, requestExecutor.getCertificate(), requestExecutor.getKeyPassword(), requestExecutor.getTrustStore(),
                _store.getExecutorService(),
                _store.getConventions());

        _store.registerEvents(_subscriptionLocalRequestExecutor);

        return _tcpClient;
    }

    private TcpConnectionInfo legacyTryGetTcpInfo(RequestExecutor requestExecutor) {
        GetTcpInfoCommand tcpCommand = new GetTcpInfoCommand("Subscription/" + _dbName, _dbName);
        try {
            requestExecutor.execute(tcpCommand, null);
        } catch (Exception e) {
            _redirectNode = null;
            throw e;
        }

        return tcpCommand.getResult();
    }

    private TcpConnectionInfo legacyTryGetTcpInfo(RequestExecutor requestExecutor, ServerNode node) {
        GetTcpInfoCommand tcpCommand = new GetTcpInfoCommand("Subscription/" + _dbName, _dbName);

        try {
            requestExecutor.execute(node, null, tcpCommand, false, null);
        } catch (Exception e) {
            _redirectNode = null;
            throw e;
        }

        return tcpCommand.getResult();
    }

    private void ensureParser() throws IOException {
        if (_parser == null) {
            _parser = JsonExtensions.getDefaultMapper().getFactory().createParser(_tcpClient.getInputStream());
            _parser.configure(JsonParser.Feature.AUTO_CLOSE_SOURCE, false);
        }
    }

    private int readServerResponseAndGetVersion(String url) {
        try {
            //Reading reply from server
            ensureParser();
            TreeNode response = _parser.readValueAsTree();
            TcpConnectionHeaderResponse reply = JsonExtensions.getDefaultMapper().treeToValue(response, TcpConnectionHeaderResponse.class);

            switch (reply.getStatus()) {
                case OK:
                    return reply.getVersion();
                case AUTHORIZATION_FAILED:
                    throw new AuthorizationException("Cannot access database " + _dbName + " because " + reply.getMessage());
                case TCP_VERSION_MISMATCH:
                    if (reply.getVersion() != TcpNegotiation.OUT_OF_RANGE_STATUS) {
                        return reply.getVersion();
                    }
                    //Kindly request the server to drop the connection
                    sendDropMessage(reply);
                    throw new IllegalStateException("Can't connect to database " + _dbName + " because: " + reply.getMessage());
            }
            return reply.getVersion();
        } catch (IOException e) {
            throw ExceptionsUtils.unwrapException(e);
        }
    }

    private void sendDropMessage(TcpConnectionHeaderResponse reply) throws IOException {
        TcpConnectionHeaderMessage dropMsg = new TcpConnectionHeaderMessage();
        dropMsg.setOperation(TcpConnectionHeaderMessage.OperationTypes.DROP);
        dropMsg.setDatabaseName(_dbName);
        dropMsg.setOperationVersion(TcpConnectionHeaderMessage.SUBSCRIPTION_TCP_VERSION);
        dropMsg.setInfo("Couldn't agree on subscription tcp version ours: " + TcpConnectionHeaderMessage.SUBSCRIPTION_TCP_VERSION + " theirs: " + reply.getVersion());
        byte[] header = JsonExtensions.getDefaultMapper().writeValueAsBytes(dropMsg);
        _tcpClient.getOutputStream().write(header);
        _tcpClient.getOutputStream().flush();
    }

    private void assertConnectionState(SubscriptionConnectionServerMessage connectionStatus) {
        if (connectionStatus.getType() == SubscriptionConnectionServerMessage.MessageType.ERROR) {
            if (connectionStatus.getException().contains("DatabaseDoesNotExistException")) {
                throw new DatabaseDoesNotExistException(_dbName + " does not exists. " + connectionStatus.getMessage());
            }
        }

        if (connectionStatus.getType() != SubscriptionConnectionServerMessage.MessageType.CONNECTION_STATUS) {
            throw new IllegalStateException("Server returned illegal type message when expecting connection status, was:" + connectionStatus.getType());
        }

        switch (connectionStatus.getStatus()) {
            case ACCEPTED:
                break;
            case IN_USE:
                throw new SubscriptionInUseException("Subscription with id " + _options.getSubscriptionName() + " cannot be opened, because it's in use and the connection strategy is " + _options.getStrategy());
            case CLOSED:
                boolean canReconnect = false;
                JsonNode canReconnectNode = connectionStatus.getData().get("CanReconnect");
                if (canReconnectNode != null && canReconnectNode.isBoolean() && canReconnectNode.asBoolean()) {
                    canReconnect = true;
                }
                throw new SubscriptionClosedException("Subscription with id " + _options.getSubscriptionName() + " was closed. " + connectionStatus.getException(), canReconnect);
            case INVALID:
                throw new SubscriptionInvalidStateException("Subscription with id " + _options.getSubscriptionName() + " cannot be opened, because it is in invalid state. " + connectionStatus.getException());
            case NOT_FOUND:
                throw new SubscriptionDoesNotExistException("Subscription with id " + _options.getSubscriptionName() + " cannot be opened, because it does not exist. " + connectionStatus.getException());
            case REDIRECT:
                ObjectNode data = connectionStatus.getData();
                String appropriateNode = data.get("RedirectedTag").asText();
                JsonNode rawReasons = data.get("Reasons");
                Map reasonsDictionary = new HashMap<>();
                if (rawReasons instanceof ArrayNode) {
                    ArrayNode rawReasonsArray = (ArrayNode) rawReasons;
                    for (JsonNode item : rawReasonsArray) {
                        if (item instanceof ObjectNode) {
                            ObjectNode itemAsBlittable = (ObjectNode) item;

                            if (itemAsBlittable.size() == 1) {
                                String tagName = itemAsBlittable.fieldNames().next();
                                reasonsDictionary.put(tagName, itemAsBlittable.get(tagName).asText());
                            }
                        }
                    }
                }

                String reasonsJoined = reasonsDictionary
                        .entrySet()
                        .stream()
                        .map(x -> x.getKey() + ":" + x.getValue())
                        .collect(Collectors.joining());

                SubscriptionDoesNotBelongToNodeException notBelongToNodeException =
                        new SubscriptionDoesNotBelongToNodeException(
                                "Subscription with id " + _options.getSubscriptionName() + " cannot be processed by current node, " +
                                        "it will be redirected to " + appropriateNode + System.lineSeparator() + reasonsJoined);
                notBelongToNodeException.setAppropriateNode(appropriateNode);
                notBelongToNodeException.setReasons(reasonsDictionary);
                throw notBelongToNodeException;
            case CONCURRENCY_RECONNECT:
                throw new SubscriptionChangeVectorUpdateConcurrencyException(connectionStatus.getMessage());
            default:
                throw new IllegalStateException("Subscription " + _options.getSubscriptionName() + " could not be opened, reason: " + connectionStatus.getStatus());
        }

    }

    @SuppressWarnings("ConstantConditions")
    private void processSubscription() throws Exception {
        try {
            _processingCts.getToken().throwIfCancellationRequested();

            try (Socket socket = connectToServer()) {
                _processingCts.getToken().throwIfCancellationRequested();

                Socket tcpClientCopy = _tcpClient;

                SubscriptionConnectionServerMessage connectionStatus = readNextObject(tcpClientCopy);
                if (_processingCts.getToken().isCancellationRequested()) {
                    return;
                }

                if (connectionStatus.getType() != SubscriptionConnectionServerMessage.MessageType.CONNECTION_STATUS
                        || connectionStatus.getStatus() != SubscriptionConnectionServerMessage.ConnectionStatus.ACCEPTED) {
                    assertConnectionState(connectionStatus);
                }

                _lastConnectionFailure = null;
                if (_processingCts.getToken().isCancellationRequested()) {
                    return;
                }

                CompletableFuture notifiedSubscriber = CompletableFuture.completedFuture(null);

                SubscriptionBatch batch = new SubscriptionBatch<>(_clazz, _revisions, _subscriptionLocalRequestExecutor, _store, _dbName, _logger);

                while (!_processingCts.getToken().isCancellationRequested()) {
                    // start the read from the server
                    CompletableFuture readFromServer =
                            CompletableFuture.supplyAsync(() -> {
                                try {
                                    return readSingleSubscriptionBatchFromServer(tcpClientCopy, batch);
                                } catch (IOException e) {
                                    throw new RuntimeException(e);
                                }
                            }, _store.getExecutorService());

                    try {
                        notifiedSubscriber.get();
                    } catch (Exception e) {
                        // if the subscriber errored, we shut down
                        try {
                            closeTcpClient();
                        } catch (Exception ex2) {
                            // nothing to be done here
                        }

                        throw e;
                    }

                    BatchFromServer incomingBatch = readFromServer.get();

                    _processingCts.getToken().throwIfCancellationRequested();

                    String lastReceivedChangeVector = batch.initialize(incomingBatch);

                    notifiedSubscriber = CompletableFuture.runAsync(() -> {
                        try {
                            _subscriber.accept(batch);
                        } catch (Exception ex) {
                            if (_logger.isDebugEnabled()) {
                                _logger.debug("Subscription " + _options.getSubscriptionName() + ". Subscriber threw an exception on document batch", ex);
                            }

                            if (!_options.isIgnoreSubscriberErrors()) {
                                throw new SubscriberErrorException("Subscriber threw an exception in subscription " + _options.getSubscriptionName(), ex);
                            }
                        }

                        try {
                            if (tcpClientCopy != null) {
                                sendAck(lastReceivedChangeVector, tcpClientCopy);
                            }
                        } catch (Exception e) {
                            throw new RuntimeException(e);
                        }
                    }, _store.getExecutorService());
                }
            }

        } catch (OperationCancelledException e) {
            if (!_disposed) {
                throw e;
            }

            // otherwise this is thrown when shutting down, it
            // isn't an error, so we don't need to treat
            // it as such
        }
    }

    private BatchFromServer readSingleSubscriptionBatchFromServer(Socket socket, SubscriptionBatch batch) throws IOException {
        List incomingBatch = new ArrayList<>();
        List includes = new ArrayList<>();
        boolean endOfBatch = false;
        while (!endOfBatch && !_processingCts.getToken().isCancellationRequested()) {
            SubscriptionConnectionServerMessage receivedMessage = readNextObject(socket);
            if (receivedMessage == null || _processingCts.getToken().isCancellationRequested()) {
                break;
            }

            switch (receivedMessage.getType()) {
                case DATA:
                    incomingBatch.add(receivedMessage);
                    break;
                case INCLUDES:
                    includes.add(receivedMessage.getIncludes());
                    break;
                case END_OF_BATCH:
                    endOfBatch = true;
                    break;
                case CONFIRM:
                    EventHelper.invoke(afterAcknowledgment, batch);

                    incomingBatch.clear();
                    batch.getItems().clear();
                    break;
                case CONNECTION_STATUS:
                    assertConnectionState(receivedMessage);
                    break;
                case ERROR:
                    throwSubscriptionError(receivedMessage);
                    break;
                default:
                    throwInvalidServerResponse(receivedMessage);
                    break;
            }
        }

        BatchFromServer batchFromServer = new BatchFromServer();
        batchFromServer.setMessages(incomingBatch);
        batchFromServer.setIncludes(includes);
        return batchFromServer;
    }

    private static void throwInvalidServerResponse(SubscriptionConnectionServerMessage receivedMessage) {
        throw new IllegalArgumentException("Unrecognized message " + receivedMessage.getType() + " type received from server");
    }

    private static void throwSubscriptionError(SubscriptionConnectionServerMessage receivedMessage) {
        throw new IllegalStateException("Connected terminated by server. Exception: " + ObjectUtils.firstNonNull(receivedMessage.getException(), "None"));
    }

    private SubscriptionConnectionServerMessage readNextObject(Socket socket) throws IOException {
        if (_processingCts.getToken().isCancellationRequested() || !_tcpClient.isConnected()) {
            return null;
        }

        if (_disposed) { //if we are disposed, nothing to do...
            return null;
        }

        TreeNode response = _parser.readValueAsTree();
        return JsonExtensions.getDefaultMapper().treeToValue(response, SubscriptionConnectionServerMessage.class);
    }

    private void sendAck(String lastReceivedChangeVector, Socket networkStream) throws IOException {
        SubscriptionConnectionClientMessage msg = new SubscriptionConnectionClientMessage();
        msg.setChangeVector(lastReceivedChangeVector);
        msg.setType(SubscriptionConnectionClientMessage.MessageType.ACKNOWLEDGE);
        byte[] ack = JsonExtensions.getDefaultMapper().writeValueAsBytes(msg);
        networkStream.getOutputStream().write(ack);
        networkStream.getOutputStream().flush();
    }

    private CompletableFuture runSubscriptionAsync() {
        return CompletableFuture.runAsync(() -> {
            while (!_processingCts.getToken().isCancellationRequested()) {
                try {
                    closeTcpClient();
                    if (_logger.isInfoEnabled()) {
                        _logger.info("Subscription " + _options.getSubscriptionName() + ". Connecting to server...");
                    }

                    processSubscription();
                } catch (Exception ex) {
                    try {
                        if (_processingCts.getToken().isCancellationRequested()) {
                            if (!_disposed) {
                                throw ex;
                            }
                            return;
                        }

                        if (_logger.isInfoEnabled()) {
                            _logger.info("Subscription " + _options.getSubscriptionName() + ". Pulling task threw the following exception", ex);
                        }

                        if (shouldTryToReconnect(ex)) {
                            Thread.sleep(_options.getTimeToWaitBeforeConnectionRetry().toMillis());

                            if (_redirectNode == null) {
                                RequestExecutor reqEx = _store.getRequestExecutor(_dbName);
                                List curTopology = reqEx.getTopologyNodes();
                                int nextNodeIndex = (_forcedTopologyUpdateAttempts++) % curTopology.size();
                                _redirectNode = curTopology.get(nextNodeIndex);
                                if (_logger.isInfoEnabled()) {
                                    _logger.info("Subscription '" + _options.getSubscriptionName() + "'. Will modify redirect node from null to " + _redirectNode.getClusterTag(), ex);
                                }
                            }

                            EventHelper.invoke(onSubscriptionConnectionRetry, ex);
                        } else {
                            if (_logger.isErrorEnabled()) {
                                _logger.error("Connection to subscription " + _options.getSubscriptionName() + " have been shut down because of an error", ex);
                            }
                            throw ex;
                        }
                    } catch (Exception e) {
                        throw ExceptionsUtils.unwrapException(e);
                    }
                }
            }
        }, _store.getExecutorService());
    }

    private Date _lastConnectionFailure;
    private TcpConnectionHeaderMessage.SupportedFeatures _supportedFeatures;

    private void assertLastConnectionFailure() {
        if (_lastConnectionFailure == null) {
            _lastConnectionFailure = new Date();
            return;
        }

        if (new Date().getTime() - _lastConnectionFailure.getTime() > _options.getMaxErroneousPeriod().toMillis()) {
            throw new SubscriptionInvalidStateException("Subscription connection was in invalid state for more than "
                    + _options.getMaxErroneousPeriod() + " and therefore will be terminated");
        }
    }

    private boolean shouldTryToReconnect(Exception ex) {
        ex = ExceptionsUtils.unwrapException(ex);
        if (ex instanceof SubscriptionDoesNotBelongToNodeException) {
            SubscriptionDoesNotBelongToNodeException se = (SubscriptionDoesNotBelongToNodeException) ex;
            assertLastConnectionFailure();

            RequestExecutor requestExecutor = _store.getRequestExecutor(_dbName);

            if (se.getAppropriateNode() == null) {
                _redirectNode = null;
                return true;
            }

            ServerNode nodeToRedirectTo = requestExecutor.getTopologyNodes()
                    .stream()
                    .filter(x -> x.getClusterTag().equals(se.getAppropriateNode()))
                    .findFirst()
                    .orElse(null);

            if (nodeToRedirectTo == null) {
                throw new IllegalStateException("Could not redirect to " + se.getAppropriateNode() + ", because it was not found in local topology, even after retrying");
            }

            _redirectNode = nodeToRedirectTo;
            return true;
        } else if (ex instanceof NodeIsPassiveException) {
            // if we failed to talk to a node, we'll forget about it and let the topology to
            // redirect us to the current node
            _redirectNode = null;
            return true;
        } else if (ex instanceof SubscriptionChangeVectorUpdateConcurrencyException) {
            return true;
        } else if (ex instanceof SubscriptionClosedException) {
            SubscriptionClosedException sce = (SubscriptionClosedException) ex;
            if (sce.isCanReconnect()) {
                return true;
            }

            _processingCts.cancel();
            return false;
        }

        if (ex instanceof SubscriptionInUseException
                || ex instanceof SubscriptionDoesNotExistException
                || ex instanceof DatabaseDoesNotExistException
                || ex instanceof AuthorizationException
                || ex instanceof AllTopologyNodesDownException
                || ex instanceof SubscriberErrorException) {
            _processingCts.cancel();
            return false;
        }

        assertLastConnectionFailure();
        return true;
    }

    private void closeTcpClient() {
        if (_parser != null) {
            IOUtils.closeQuietly(_parser);
            _parser = null;
        }

        if (_tcpClient != null) {
            IOUtils.closeQuietly(_tcpClient);
            _tcpClient = null;
        }
    }

    Consumer> onClosed = null;
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy