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

org.neo4j.kernel.impl.query.Neo4jTransactionalContext Maven / Gradle / Ivy

/*
 * Copyright (c) 2018-2020 "Graph Foundation,"
 * Graph Foundation, Inc. [https://graphfoundation.org]
 *
 * This file is part of ONgDB.
 *
 * ONgDB 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 .
 */
/*
 * Copyright (c) 2002-2020 "Neo4j,"
 * Neo4j Sweden AB [http://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.kernel.impl.query;

import org.neo4j.graphdb.Lock;
import org.neo4j.graphdb.PropertyContainer;
import org.neo4j.graphdb.TransactionTerminatedException;
import org.neo4j.internal.kernel.api.ExecutionStatistics;
import org.neo4j.internal.kernel.api.Kernel;
import org.neo4j.internal.kernel.api.security.SecurityContext;
import org.neo4j.kernel.GraphDatabaseQueryService;
import org.neo4j.kernel.api.KernelTransaction;
import org.neo4j.kernel.api.QueryRegistryOperations;
import org.neo4j.kernel.api.ResourceTracker;
import org.neo4j.kernel.api.Statement;
import org.neo4j.kernel.api.dbms.DbmsOperations;
import org.neo4j.kernel.api.query.ExecutingQuery;
import org.neo4j.kernel.api.txstate.TxStateHolder;
import org.neo4j.kernel.guard.Guard;
import org.neo4j.kernel.impl.api.KernelTransactionImplementation;
import org.neo4j.kernel.impl.core.ThreadToStatementContextBridge;
import org.neo4j.kernel.impl.coreapi.InternalTransaction;
import org.neo4j.kernel.impl.coreapi.PropertyContainerLocker;
import org.neo4j.kernel.impl.query.statistic.StatisticProvider;

public class Neo4jTransactionalContext implements TransactionalContext
{
    private final GraphDatabaseQueryService graph;
    private final Guard guard;
    private final ThreadToStatementContextBridge txBridge;
    private final PropertyContainerLocker locker;

    public final KernelTransaction.Type transactionType;
    public final SecurityContext securityContext;
    private final ExecutingQuery executingQuery;
    private final Kernel kernel;

    /**
     * Current transaction.
     * Field can be read from a different thread in {@link #terminate()}.
     */
    private volatile InternalTransaction transaction;
    private Statement statement;
    private boolean isOpen = true;

    private long pageHits;
    private long pageMisses;

    public Neo4jTransactionalContext(
            GraphDatabaseQueryService graph,
            Guard guard,
            ThreadToStatementContextBridge txBridge,
            PropertyContainerLocker locker,
            InternalTransaction initialTransaction,
            Statement initialStatement,
            ExecutingQuery executingQuery,
            Kernel kernel
    )
    {
        this.graph = graph;
        this.guard = guard;
        this.txBridge = txBridge;
        this.locker = locker;
        this.transactionType = initialTransaction.transactionType();
        this.securityContext = initialTransaction.securityContext();
        this.executingQuery = executingQuery;

        this.transaction = initialTransaction;
        this.statement = initialStatement;
        this.kernel = kernel;
    }

    @Override
    public ExecutingQuery executingQuery()
    {
        return executingQuery;
    }

    @Override
    public DbmsOperations dbmsOperations()
    {
        return graph.getDbmsOperations();
    }

    @Override
    public KernelTransaction kernelTransaction()
    {
        return txBridge.getKernelTransactionBoundToThisThread( true );
    }

    @Override
    public boolean isTopLevelTx()
    {
        return transaction.transactionType() == KernelTransaction.Type.implicit;
    }

    @Override
    public void close( boolean success )
    {
        if ( isOpen )
        {
            try
            {
                statement.queryRegistration().unregisterExecutingQuery( executingQuery );
                statement.close();

                if ( success )
                {
                    transaction.success();
                }
                else
                {
                    transaction.failure();
                }
                transaction.close();
            }
            finally
            {
                statement = null;
                transaction = null;
                isOpen = false;
            }
        }
    }

    @Override
    public void terminate()
    {
        InternalTransaction currentTransaction = transaction;
        if ( currentTransaction != null )
        {
            currentTransaction.terminate();
        }
    }

    @Override
    public void commitAndRestartTx()
    {
       /*
        * This method is use by the Cypher runtime to cater for PERIODIC COMMIT, which allows a single query to
        * periodically, after x number of rows, to commit a transaction and spawn a new one.
        *
        * To still keep track of the running stream after switching transactions, we need to open the new transaction
        * before closing the old one. This way, a query will not disappear and appear when switching transactions.
        *
        * Since our transactions are thread bound, we must first unbind the old transaction from the thread before
        * creating a new one. And then we need to do that thread switching again to close the old transaction.
        */

        checkNotTerminated();

        collectTransactionExecutionStatistic();

        // (1) Unbind current transaction
        QueryRegistryOperations oldQueryRegistryOperations = statement.queryRegistration();
        Statement oldStatement = statement;
        InternalTransaction oldTransaction = transaction;
        KernelTransaction oldKernelTx = txBridge.getKernelTransactionBoundToThisThread( true );
        txBridge.unbindTransactionFromCurrentThread();

        // (2) Create, bind, register, and unbind new transaction
        transaction = graph.beginTransaction( transactionType, securityContext );
        statement = txBridge.get();
        statement.queryRegistration().registerExecutingQuery( executingQuery );
        KernelTransaction kernelTx = txBridge.getKernelTransactionBoundToThisThread( true );
        txBridge.unbindTransactionFromCurrentThread();

        // (3) Rebind old transaction just to commit and close it (and unregister as a side effect of that)
        txBridge.bindTransactionToCurrentThread( oldKernelTx );
        oldQueryRegistryOperations.unregisterExecutingQuery( executingQuery );
        try
        {
            oldStatement.close();
            oldTransaction.success();
            oldTransaction.close();
        }
        catch ( Throwable t )
        {
            // Corner case: The old transaction might have been terminated by the user. Now we also need to
            // terminate the new transaction.
            txBridge.bindTransactionToCurrentThread( kernelTx );
            transaction.failure();
            transaction.close();
            txBridge.unbindTransactionFromCurrentThread();
            throw t;
        }

        // (4) Unbind the now closed old transaction and rebind the new transaction for continued execution
        txBridge.unbindTransactionFromCurrentThread();
        txBridge.bindTransactionToCurrentThread( kernelTx );
    }

    @Override
    public void cleanForReuse()
    {
        // close the old statement reference after the statement has been "upgraded"
        // to either a schema data or a schema statement, so that the locks are "handed over".
        statement.queryRegistration().unregisterExecutingQuery( executingQuery );
        statement.close();
        statement = txBridge.get();
        statement.queryRegistration().registerExecutingQuery( executingQuery );
    }

    @Override
    public boolean twoLayerTransactionState()
    {
        return kernel.modes().twoLayerTransactionState();
    }

    @Override
    public TransactionalContext getOrBeginNewIfClosed()
    {
        checkNotTerminated();

        if ( !isOpen )
        {
            transaction = graph.beginTransaction( transactionType, securityContext );
            statement = txBridge.get();
            statement.queryRegistration().registerExecutingQuery( executingQuery );
            isOpen = true;
        }
        return this;
    }

    public TransactionalContext beginInNewThread()
    {
        InternalTransaction newTx = graph.beginTransaction( transactionType, securityContext );
        return new Neo4jTransactionalContext( graph, guard, txBridge, locker, newTx,
                txBridge.get(), executingQuery, kernel );
    }

    private void checkNotTerminated()
    {
        InternalTransaction currentTransaction = transaction;
        if ( currentTransaction != null )
        {
            currentTransaction.terminationReason().ifPresent( status ->
            {
                throw new TransactionTerminatedException( status );
            } );
        }
    }

    @Override
    public boolean isOpen()
    {
        return isOpen;
    }

    @Override
    public GraphDatabaseQueryService graph()
    {
        return graph;
    }

    @Override
    public Statement statement()
    {
        return statement;
    }

    @Override
    public void check()
    {
        guard.check( kernelTransaction() );
    }

    @Override
    public TxStateHolder stateView()
    {
        return (KernelTransactionImplementation) kernelTransaction();
    }

    @Override
    public Lock acquireWriteLock( PropertyContainer p )
    {
        return locker.exclusiveLock( kernelTransaction(), p );
    }

    @Override
    public KernelTransaction.Revertable restrictCurrentTransaction( SecurityContext context )
    {
        return transaction.overrideWith( context );
    }

    @Override
    public SecurityContext securityContext()
    {
        return securityContext;
    }

    @Override
    public ResourceTracker resourceTracker()
    {
        // We use the current statement as resourceTracker since it is attached to the KernelTransaction
        // and is guaranteed to be cleaned up on transaction failure.
        return statement;
    }

    @Override
    public StatisticProvider kernelStatisticProvider()
    {
        return new TransactionalContextStatisticProvider( kernelTransaction().executionStatistics() );
    }

    private void collectTransactionExecutionStatistic()
    {
        ExecutionStatistics stats = kernelTransaction().executionStatistics();
        pageHits += stats.pageHits();
        pageMisses += stats.pageFaults();
    }

    public Neo4jTransactionalContext copyFrom( GraphDatabaseQueryService graph,
            Guard guard,
            ThreadToStatementContextBridge txBridge, PropertyContainerLocker locker,
            InternalTransaction initialTransaction, Statement initialStatement,
            ExecutingQuery executingQuery )
    {
        return new Neo4jTransactionalContext( graph, guard, txBridge, locker, initialTransaction, initialStatement,
                executingQuery, kernel );
    }

    interface Creator
    {
        Neo4jTransactionalContext create(
                InternalTransaction tx,
                Statement initialStatement,
                ExecutingQuery executingQuery
        );
    }

    private class TransactionalContextStatisticProvider implements StatisticProvider
    {
        private final ExecutionStatistics executionStatistics;

        private TransactionalContextStatisticProvider( ExecutionStatistics executionStatistics )
        {
            this.executionStatistics = executionStatistics;
        }

        @Override
        public long getPageCacheHits()
        {
            return executionStatistics.pageHits() + pageHits;
        }

        @Override
        public long getPageCacheMisses()
        {
            return executionStatistics.pageFaults() + pageMisses;
        }
    }
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy