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) "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.Logger;
import org.neo4j.driver.Logging;
import org.neo4j.driver.Value;
import org.neo4j.driver.exceptions.AuthorizationExpiredException;
import org.neo4j.driver.exceptions.ClientException;
import org.neo4j.driver.internal.handlers.ResetResponseHandler;
import org.neo4j.driver.internal.logging.ChannelActivityLogger;
import org.neo4j.driver.internal.logging.ChannelErrorLogger;
import org.neo4j.driver.internal.messaging.ResponseMessageHandler;
import org.neo4j.driver.internal.spi.ResponseHandler;
import org.neo4j.driver.internal.util.ErrorUtil;

import static java.util.Objects.requireNonNull;
import static org.neo4j.driver.internal.async.connection.ChannelAttributes.authorizationStateListener;
import static org.neo4j.driver.internal.messaging.request.ResetMessage.RESET;
import static org.neo4j.driver.internal.util.ErrorUtil.addSuppressed;

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

    private volatile boolean gracefullyClosed;
    private Throwable currentError;
    private boolean fatalErrorOccurred;
    private HandlerHook beforeLastHandlerHook;

    private ResponseHandler autoReadManagingHandler;

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

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

    public void setBeforeLastHandlerHook( HandlerHook beforeLastHandlerHook )
    {
        if ( !channel.eventLoop().inEventLoop() )
        {
            throw new IllegalStateException( "This method may only be called in the EventLoop" );
        }
        this.beforeLastHandlerHook = beforeLastHandlerHook;
    }

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

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

    @Override
    public void handleRecordMessage( Value[] fields )
    {
        if ( log.isDebugEnabled() )
        {
            log.debug( "S: RECORD %s", Arrays.toString( fields ) );
        }
        ResponseHandler handler = handlers.peek();
        if ( handler == null )
        {
            throw new IllegalStateException( "No handler exists to handle RECORD message with fields: " + Arrays.toString( fields ) );
        }
        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 RESET
            channel.pipeline().fireExceptionCaught( currentError );
            return;
        }

        Throwable currentError = this.currentError;
        if ( currentError instanceof AuthorizationExpiredException )
        {
            authorizationStateListener( channel ).onExpired( (AuthorizationExpiredException) currentError, channel );
        }
        else
        {
            // write a RESET to "acknowledge" the failure
            enqueue( new ResetResponseHandler( this ) );
            channel.writeAndFlush( RESET, channel.voidPromise() );
        }

        invokeBeforeLastHandlerHook( HandlerHook.MessageType.FAILURE );
        ResponseHandler handler = removeHandler();
        handler.onFailure( currentError );
    }

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

        ResponseHandler handler = removeHandler();

        Throwable error;
        if ( currentError != null )
        {
            error = currentError;
        }
        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 handleChannelInactive( Throwable cause )
    {
        // report issue if the connection has not been terminated as a result of a graceful shutdown request from its
        // parent pool
        if ( !gracefullyClosed )
        {
            handleChannelError( cause );
        }
        else
        {
            channel.close();
        }
    }

    public void handleChannelError( Throwable error )
    {
        if ( currentError != null )
        {
            // we already have an error, this new error probably is caused by the existing one, thus we chain the new error on this current error
            addSuppressed( currentError, error );
        }
        else
        {
            currentError = error;
        }
        fatalErrorOccurred = true;

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

        errorLog.traceOrDebug( "Closing channel because of a failure", error );
        channel.close();
    }

    public void clearCurrentError()
    {
        currentError = null;
    }

    public Throwable currentError()
    {
        return currentError;
    }

    public boolean fatalErrorOccurred()
    {
        return fatalErrorOccurred;
    }

    public void prepareToCloseChannel( )
    {
        this.gracefullyClosed = true;
    }

    /**
     * Visible for testing
     */
    ResponseHandler autoReadManagingHandler()
    {
        return autoReadManagingHandler;
    }

    private ResponseHandler removeHandler()
    {
        ResponseHandler handler = handlers.remove();
        if ( handler == autoReadManagingHandler )
        {
            // the auto-read managing handler is being removed
            // make sure this dispatcher does not hold on to a removed handler
            updateAutoReadManagingHandler( null );
        }
        return handler;
    }

    private void updateAutoReadManagingHandlerIfNeeded( ResponseHandler handler )
    {
        if ( handler.canManageAutoRead() )
        {
            updateAutoReadManagingHandler( handler );
        }
    }

    private void updateAutoReadManagingHandler( ResponseHandler newHandler )
    {
        if ( autoReadManagingHandler != null )
        {
            // there already exists a handler that manages channel's auto-read
            // make it stop because new managing handler is being added and there should only be a single such handler
            autoReadManagingHandler.disableAutoReadManagement();
            // restore the default value of auto-read
            channel.config().setAutoRead( true );
        }
        autoReadManagingHandler = newHandler;
    }

    private void invokeBeforeLastHandlerHook( HandlerHook.MessageType messageType )
    {
        if ( handlers.size() == 1 && beforeLastHandlerHook != null )
        {
            beforeLastHandlerHook.run( messageType );
        }
    }

    public interface HandlerHook
    {
        enum MessageType
        {
            SUCCESS,
            FAILURE
        }

        void run( MessageType messageType );
    }

    //    For testing only
    Logger getLog()
    {
        return log;
    }

    //    For testing only
    Logger getErrorLog()
    {
        return errorLog;
    }
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy