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

org.neo4j.bolt.fsm.StateMachineImpl Maven / Gradle / Ivy

Go to download

The core of Neo4j Bolt Protocol, this contains the state machine for Bolt sessions.

The newest version!
/*
 * Copyright (c) "Neo4j"
 * Neo4j Sweden AB [https://neo4j.com]
 *
 * This file is part of Neo4j.
 *
 * Neo4j is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program.  If not, see .
 */
package org.neo4j.bolt.fsm;

import static java.lang.String.format;
import static org.neo4j.kernel.api.exceptions.Status.Classification.DatabaseError;

import org.neo4j.bolt.fsm.error.AdmissionControlException;
import org.neo4j.bolt.fsm.error.BoltException;
import org.neo4j.bolt.fsm.error.ConnectionTerminating;
import org.neo4j.bolt.fsm.error.NoSuchStateException;
import org.neo4j.bolt.fsm.error.StateMachineException;
import org.neo4j.bolt.fsm.error.state.IllegalRequestParameterException;
import org.neo4j.bolt.fsm.state.State;
import org.neo4j.bolt.fsm.state.StateReference;
import org.neo4j.bolt.protocol.common.connector.connection.ConnectionHandle;
import org.neo4j.bolt.protocol.common.fsm.response.ResponseHandler;
import org.neo4j.bolt.protocol.common.message.Error;
import org.neo4j.bolt.protocol.common.message.request.RequestMessage;
import org.neo4j.dbms.admissioncontrol.AdmissionControlService;
import org.neo4j.dbms.admissioncontrol.AdmissionControlToken;
import org.neo4j.logging.Log;
import org.neo4j.logging.internal.LogService;

final class StateMachineImpl implements StateMachine, Context {
    private final ConnectionHandle connection;
    private final StateMachineConfiguration configuration;

    private final Log userLog;
    private final Log internalLog;

    private State defaultState;
    private State currentState;
    private final AdmissionControlService admissionControlService;

    private boolean failed;
    private volatile boolean interrupted;

    StateMachineImpl(
            ConnectionHandle connection,
            StateMachineConfiguration configuration,
            LogService logging,
            State initialState,
            AdmissionControlService admissionControlService) {
        this.connection = connection;
        this.configuration = configuration;

        this.userLog = logging.getUserLog(StateMachineImpl.class);
        this.internalLog = logging.getInternalLog(StateMachineImpl.class);

        this.currentState = this.defaultState = initialState;
        this.admissionControlService = admissionControlService;
    }

    @Override
    public ConnectionHandle connection() {
        return this.connection;
    }

    @Override
    public StateMachineConfiguration configuration() {
        return this.configuration;
    }

    @Override
    public StateReference state() {
        return this.currentState.reference();
    }

    @Override
    public State lookup(StateReference reference) throws NoSuchStateException {
        return this.configuration.lookup(reference);
    }

    @Override
    public StateReference defaultState() {
        return this.defaultState.reference();
    }

    @Override
    public void defaultState(StateReference state) throws NoSuchStateException {
        this.defaultState = this.lookup(state);
    }

    @Override
    public boolean hasFailed() {
        return this.failed;
    }

    @Override
    public boolean isInterrupted() {
        return this.interrupted;
    }

    @Override
    public void interrupt() {
        this.interrupted = true;
    }

    @Override
    public void reset() {
        this.failed = false;
        this.interrupted = false;

        this.currentState = this.defaultState;
    }

    @Override
    public boolean validate() {
        var tx = this.connection.transaction().orElse(null);

        if (tx == null) {
            return false;
        }

        // ensure that the transaction remains valid for this operation as it may have been
        // terminated through an administrative command or by timing out in the meantime
        return tx.validate();
    }

    @Override
    @SuppressWarnings("removal") // Removal of isIgnoredWhenFailed - see RequestMessage
    public void process(RequestMessage message, ResponseHandler handler, AdmissionControlToken admissionControlToken)
            throws StateMachineException {
        if (this.failed || this.interrupted) {
            if (!message.isIgnoredWhenFailed()) {
                handler.onFailure(Error.from(BoltException.invalidServerState(
                        message.toString(), this.state().name())));

                throw new IllegalRequestParameterException("Request of type "
                        + message.getClass().getName() + " is not permitted while failed or interrupted");
            }

            handler.onIgnored();
            return;
        }

        try {
            awaitAdmissionControlToken(admissionControlToken);
            var nextStateReference = this.currentState.process(this, message, handler);
            this.currentState = this.lookup(nextStateReference);
            handler.onSuccess();
        } catch (Throwable ex) {
            this.failed = true;
            var error = Error.from(ex);

            // when dealing with database errors, we'll also generate a log message to provide
            // helpful debug information for server administrators
            if (error.status().code().classification() == DatabaseError) {
                String errorMessage;
                if (error.queryId() != null) {
                    errorMessage = format(
                            "Client triggered an unexpected error [%s]: %s, reference %s, queryId: %s.",
                            error.status().code().serialize(), error.message(), error.reference(), error.queryId());
                } else {
                    errorMessage = format(
                            "Client triggered an unexpected error [%s]: %s, reference %s.",
                            error.status().code().serialize(), error.message(), error.reference());
                }
                this.userLog.error(errorMessage);

                if (error.cause() != null) {
                    this.internalLog.error(errorMessage, error.cause());
                }
            }

            // notify the response handler to generate an appropriate response to the client
            handler.onFailure(error);

            // when an exception indicates that it should lead to connection termination,
            // rethrow it to be handled within the parent context (these are generally log
            // worthy conditions)
            if (error.isFatal()
                    || (ex instanceof ConnectionTerminating terminating && terminating.shouldTerminateConnection())) {
                throw ex;
            }
        }
    }

    private void awaitAdmissionControlToken(AdmissionControlToken admissionControlToken)
            throws AdmissionControlException {
        if (admissionControlToken != null) {
            var response = admissionControlService.awaitRelease(admissionControlToken);
            // Convert the admission control response in to a state machine friendly error.
            switch (response) {
                case RELEASED -> {}
                case UNABLE_TO_ALLOCATE_NEW_TOKEN,
                        ADMISSION_CONTROL_PROCESS_STOPPED -> throw new AdmissionControlException();
            }
        }
    }
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy