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

org.neo4j.driver.internal.async.inbound.InboundMessageDispatcher Maven / Gradle / Ivy

There is a newer version: 5.27.0
Show newest version
/*
 * Copyright (c) 2002-2018 "Neo4j,"
 * Neo4j Sweden AB [http://neo4j.com]
 *
 * This file is part of Neo4j.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package org.neo4j.driver.internal.async.inbound;

import io.netty.channel.Channel;

import java.util.Arrays;
import java.util.LinkedList;
import java.util.Map;
import java.util.Queue;

import org.neo4j.driver.internal.handlers.AckFailureResponseHandler;
import org.neo4j.driver.internal.logging.ChannelActivityLogger;
import org.neo4j.driver.internal.messaging.MessageHandler;
import org.neo4j.driver.internal.spi.ResponseHandler;
import org.neo4j.driver.internal.util.ErrorUtil;
import org.neo4j.driver.v1.Logger;
import org.neo4j.driver.v1.Logging;
import org.neo4j.driver.v1.Value;
import org.neo4j.driver.v1.exceptions.ClientException;

import static java.util.Objects.requireNonNull;
import static org.neo4j.driver.internal.messaging.AckFailureMessage.ACK_FAILURE;

public class InboundMessageDispatcher implements MessageHandler
{
    private final Channel channel;
    private final Queue handlers = new LinkedList<>();
    private final Logger log;

    private Throwable currentError;
    private boolean fatalErrorOccurred;
    private boolean ackFailureMuted;

    public InboundMessageDispatcher( Channel channel, Logging logging )
    {
        this.channel = requireNonNull( channel );
        this.log = new ChannelActivityLogger( channel, logging, getClass() );
    }

    public void queue( ResponseHandler handler )
    {
        if ( fatalErrorOccurred )
        {
            handler.onFailure( currentError );
        }
        else
        {
            handlers.add( handler );
        }
    }

    public int queuedHandlersCount()
    {
        return handlers.size();
    }

    @Override
    public void handleInitMessage( String clientNameAndVersion, Map authToken )
    {
        throw new UnsupportedOperationException( "Driver is not supposed to receive INIT message. " +
                                                 "Received INIT with client: '" + clientNameAndVersion + "' " +
                                                 "and auth: " + authToken );
    }

    @Override
    public void handleRunMessage( String statement, Map parameters )
    {
        throw new UnsupportedOperationException( "Driver is not supposed to receive RUN message. " +
                                                 "Received RUN with statement: '" + statement + "' " +
                                                 "and params: " + parameters );
    }

    @Override
    public void handlePullAllMessage()
    {
        throw new UnsupportedOperationException( "Driver is not supposed to receive PULL_ALL message." );
    }

    @Override
    public void handleDiscardAllMessage()
    {
        throw new UnsupportedOperationException( "Driver is not supposed to receive DISCARD_ALL message." );
    }

    @Override
    public void handleResetMessage()
    {
        throw new UnsupportedOperationException( "Driver is not supposed to receive RESET message." );
    }

    @Override
    public void handleAckFailureMessage()
    {
        throw new UnsupportedOperationException( "Driver is not supposed to receive ACK_FAILURE message." );
    }

    @Override
    public void handleSuccessMessage( Map meta )
    {
        log.debug( "S: SUCCESS %s", meta );
        ResponseHandler handler = handlers.remove();
        handler.onSuccess( meta );
    }

    @Override
    public void handleRecordMessage( Value[] fields )
    {
        if ( log.isDebugEnabled() )
        {
            log.debug( "S: RECORD %s", Arrays.toString( fields ) );
        }
        ResponseHandler handler = handlers.peek();
        handler.onRecord( fields );
    }

    @Override
    public void handleFailureMessage( String code, String message )
    {
        log.debug( "S: FAILURE %s \"%s\"", code, message );

        currentError = ErrorUtil.newNeo4jError( code, message );

        if ( ErrorUtil.isFatal( currentError ) )
        {
            // we should not continue using channel after a fatal error
            // fire error event back to the pipeline and avoid sending ACK_FAILURE
            channel.pipeline().fireExceptionCaught( currentError );
            return;
        }

        // try to write ACK_FAILURE before notifying the next response handler
        ackFailureIfNeeded();

        ResponseHandler handler = handlers.remove();
        handler.onFailure( currentError );
    }

    @Override
    public void handleIgnoredMessage()
    {
        log.debug( "S: IGNORED" );

        ResponseHandler handler = handlers.remove();

        Throwable error;
        if ( currentError != null )
        {
            error = currentError;
        }
        else if ( ackFailureMuted )
        {
            error = new ClientException( "Database ignored the request because session has been reset" );
        }
        else
        {
            log.warn( "Received IGNORED message for handler %s but error is missing and RESET is not in progress. " +
                      "Current handlers %s", handler, handlers );

            error = new ClientException( "Database ignored the request" );
        }
        handler.onFailure( error );
    }

    public void handleFatalError( Throwable error )
    {
        currentError = error;
        fatalErrorOccurred = true;

        while ( !handlers.isEmpty() )
        {
            ResponseHandler handler = handlers.remove();
            handler.onFailure( currentError );
        }
    }

    public void clearCurrentError()
    {
        currentError = null;
    }

    public Throwable currentError()
    {
        return currentError;
    }

    public boolean fatalErrorOccurred()
    {
        return fatalErrorOccurred;
    }

    /**
     * Makes this message dispatcher not send ACK_FAILURE in response to FAILURE until it's un-muted using
     * {@link #unMuteAckFailure()}. Muting ACK_FAILURE is needed only when sending RESET message. RESET "jumps"
     * over all queued messages on server and makes them fail. Received failures do not need to be acknowledge because
     * RESET moves server's state machine to READY state.
     * 

* This method is not thread-safe and should only be executed by the event loop thread. */ public void muteAckFailure() { ackFailureMuted = true; } /** * Makes this message dispatcher send ACK_FAILURE in response to FAILURE. Should be used in combination with * {@link #muteAckFailure()} when sending RESET message. *

* This method is not thread-safe and should only be executed by the event loop thread. */ public void unMuteAckFailure() { ackFailureMuted = false; } /** * Check if ACK_FAILURE is muted. *

* This method is not thread-safe and should only be executed by the event loop thread. * * @return {@code true} if ACK_FAILURE has been muted via {@link #muteAckFailure()}, {@code false} otherwise. */ public boolean isAckFailureMuted() { return ackFailureMuted; } private void ackFailureIfNeeded() { if ( !ackFailureMuted ) { queue( new AckFailureResponseHandler( this ) ); channel.writeAndFlush( ACK_FAILURE, channel.voidPromise() ); } } }





© 2015 - 2025 Weber Informatics LLC | Privacy Policy