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

org.neo4j.kernel.impl.api.KernelStatement Maven / Gradle / Ivy

Go to download

Neo4j kernel is a lightweight, embedded Java database designed to store data structured as graphs rather than tables. For more information, see http://neo4j.org.

There is a newer version: 5.25.1
Show newest version
/*
 * Copyright (c) "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.api;

import org.apache.commons.lang3.StringUtils;

import java.io.ByteArrayOutputStream;
import java.io.PrintStream;
import java.nio.charset.StandardCharsets;
import java.util.ArrayDeque;
import java.util.Arrays;
import java.util.Deque;
import java.util.Optional;
import java.util.concurrent.atomic.AtomicReference;

import org.neo4j.configuration.Config;
import org.neo4j.configuration.GraphDatabaseInternalSettings;
import org.neo4j.graphdb.NotInTransactionException;
import org.neo4j.graphdb.TransactionTerminatedException;
import org.neo4j.io.pagecache.context.CursorContext;
import org.neo4j.kernel.api.AssertOpen;
import org.neo4j.kernel.api.QueryRegistry;
import org.neo4j.kernel.api.Statement;
import org.neo4j.kernel.api.exceptions.Status;
import org.neo4j.kernel.api.query.ExecutingQuery;
import org.neo4j.kernel.database.NamedDatabaseId;
import org.neo4j.kernel.impl.locking.Locks;
import org.neo4j.lock.LockTracer;
import org.neo4j.resources.CpuClock;

import static java.lang.Math.subtractExact;
import static java.lang.String.format;
import static org.neo4j.configuration.GraphDatabaseInternalSettings.trace_tx_statements;

/**
 * A resource efficient implementation of {@link Statement}. Designed to be reused within a
 * {@link KernelTransactionImplementation} instance, even across transactions since this instances itself
 * doesn't hold essential state. Usage:
 *
 * 
    *
  1. Construct {@link KernelStatement} when {@link KernelTransactionImplementation} is constructed
  2. *
  3. For every transaction...
  4. *
  5. Call {@link #initialize(Locks.Client, CursorContext, long)} which makes this instance * full available and ready to use. Call when the {@link KernelTransactionImplementation} is initialized.
  6. *
  7. Alternate {@link #acquire()} / {@link #close()} when acquiring / closing a statement for the transaction... * Temporarily asymmetric number of calls to {@link #acquire()} / {@link #close()} is supported, although in * the end an equal number of calls must have been issued.
  8. *
  9. To be safe call {@link #forceClose()} at the end of a transaction to force a close of the statement, * even if there are more than one current call to {@link #acquire()}. This instance is now again ready * to be {@link #initialize(Locks.Client, CursorContext, long)} initialized} and used for the transaction * instance again, when it's initialized.
  10. *
*/ public class KernelStatement extends CloseableResourceManager implements Statement, AssertOpen { private static final int EMPTY_COUNTER = 0; private static final int STATEMENT_TRACK_HISTORY_MAX_SIZE = 100; private static final Deque EMPTY_STATEMENT_HISTORY = new ArrayDeque<>( 0 ); private final QueryRegistry queryRegistry; private final KernelTransactionImplementation transaction; private final NamedDatabaseId namedDatabaseId; private final boolean traceStatements; private final boolean trackStatementClose; private Locks.Client lockClient; private CursorContext cursorContext = CursorContext.NULL; private int referenceCount; private volatile ExecutingQuery executingQuery; private final LockTracer systemLockTracer; private final Deque statementOpenCloseCalls; private final ClockContext clockContext; private long initialStatementHits; private long initialStatementFaults; public KernelStatement( KernelTransactionImplementation transaction, LockTracer systemLockTracer, ClockContext clockContext, AtomicReference cpuClockRef, NamedDatabaseId namedDatabaseId, Config config ) { this.transaction = transaction; this.queryRegistry = new StatementQueryRegistry( this, clockContext.systemClock(), cpuClockRef, config ); this.systemLockTracer = systemLockTracer; this.traceStatements = config.get( trace_tx_statements ); this.trackStatementClose = config.get( GraphDatabaseInternalSettings.track_tx_statement_close ); this.statementOpenCloseCalls = traceStatements ? new ArrayDeque<>() : EMPTY_STATEMENT_HISTORY; this.clockContext = clockContext; this.namedDatabaseId = namedDatabaseId; } public QueryRegistry queryRegistry() { return queryRegistry; } public NamedDatabaseId namedDatabaseId() { return namedDatabaseId; } @Override public void close() { // Check referenceCount > 0 since we allow multiple close calls, // i.e. ignore closing already closed statements if ( referenceCount > 0 && (--referenceCount == 0) ) { cleanupResources(); } recordOpenCloseMethods(); } @Override public void assertOpen() { if ( referenceCount == 0 ) { throw new NotInTransactionException( "The statement has been closed." ); } Optional terminationReason = transaction.getReasonIfTerminated(); terminationReason.ifPresent( status -> { throw new TransactionTerminatedException( status ); } ); } public void initialize( Locks.Client lockClient, CursorContext cursorContext, long startTimeMillis ) { this.lockClient = lockClient; this.cursorContext = cursorContext; this.clockContext.initializeTransaction( startTimeMillis ); } public Locks.Client locks() { return lockClient; } public LockTracer lockTracer() { LockTracer tracer = executingQuery != null ? executingQuery.lockTracer() : null; return tracer == null ? systemLockTracer : systemLockTracer.combine( tracer ); } public long getHits() { /* * The cursor tracer is shared between queries in the same transaction. * This makes it impossible to track page hits per query. * * We try by subtracting initialStatementHits. But this will only work * when executing one query at a time. If multiple queries in the same * transaction are open simultaneously it easily leads to a state where * this method returns more hits than the current executing query are * responsible for. */ return isAcquired() ? subtractExact( cursorContext.getCursorTracer().hits(), initialStatementHits ) : EMPTY_COUNTER; } public long getFaults() { // Comment on getHits also applies here. return isAcquired() ? subtractExact( cursorContext.getCursorTracer().faults(), initialStatementFaults ) : EMPTY_COUNTER; } public final void acquire() { if ( referenceCount++ == 0 ) { clockContext.initializeStatement(); var cursorTracer = cursorContext.getCursorTracer(); this.initialStatementHits = cursorTracer.hits(); this.initialStatementFaults = cursorTracer.faults(); } recordOpenCloseMethods(); } final boolean isAcquired() { return referenceCount > 0; } final void forceClose() { if ( referenceCount > 0 ) { int leakedStatements = referenceCount; referenceCount = 0; cleanupResources(); if ( trackStatementClose && transaction.isSuccess() ) { String message = getStatementNotClosedMessage( leakedStatements ); throw new StatementNotClosedException( message, statementOpenCloseCalls ); } } } public void dispose() { queryRegistry.dispose(); } private String getStatementNotClosedMessage( int leakedStatements ) { String additionalInstruction = traceStatements ? StringUtils.EMPTY : format( " To see statement open/close stack traces please set '%s' setting to true", trace_tx_statements.name() ); return format( "Statements were not correctly closed. Number of leaked statements: %d.%s", leakedStatements, additionalInstruction ); } final String authenticatedUser() { return transaction.securityContext().subject().authenticatedUser(); } final String executingUser() { return transaction.securityContext().subject().executingUser(); } final Optional executingQuery() { return Optional.ofNullable( executingQuery ); } final void startQueryExecution( ExecutingQuery query ) { query.setPreviousQuery( executingQuery ); this.executingQuery = query; } final void stopQueryExecution( ExecutingQuery executingQuery ) { this.executingQuery = executingQuery.getPreviousQuery(); transaction.getStatistics().addWaitingTime( executingQuery.reportedWaitingTimeNanos() ); } private void cleanupResources() { // closing is done by KTI transaction.releaseStatementResources(); executingQuery = null; initialStatementHits = EMPTY_COUNTER; initialStatementFaults = EMPTY_COUNTER; closeAllCloseableResources(); } public KernelTransactionImplementation getTransaction() { return transaction; } private void recordOpenCloseMethods() { if ( traceStatements ) { if ( statementOpenCloseCalls.size() > STATEMENT_TRACK_HISTORY_MAX_SIZE ) { statementOpenCloseCalls.pop(); } StackTraceElement[] stackTrace = Thread.currentThread().getStackTrace(); statementOpenCloseCalls.add( Arrays.copyOfRange(stackTrace, 2, stackTrace.length) ); } } public ClockContext clocks() { return clockContext; } static class StatementNotClosedException extends IllegalStateException { StatementNotClosedException( String s, Deque openCloseTraces ) { super( s ); this.addSuppressed( new StatementTraceException( buildMessage( openCloseTraces ) ) ); } private static String buildMessage( Deque openCloseTraces ) { if ( openCloseTraces.isEmpty() ) { return StringUtils.EMPTY; } int separatorLength = 80; String paddingString = "="; ByteArrayOutputStream out = new ByteArrayOutputStream(); PrintStream printStream = new PrintStream( out, false, StandardCharsets.UTF_8 ); printStream.println(); printStream.println( "Last " + STATEMENT_TRACK_HISTORY_MAX_SIZE + " statements open/close stack traces are:" ); int element = 0; for ( StackTraceElement[] traceElements : openCloseTraces ) { printStream.println( StringUtils.center( "*StackTrace " + element + "*", separatorLength, paddingString ) ); for ( StackTraceElement traceElement : traceElements ) { printStream.println( "\tat " + traceElement ); } printStream.println( StringUtils.center( "", separatorLength, paddingString ) ); printStream.println(); element++; } printStream.println( "All statement open/close stack traces printed." ); return out.toString( StandardCharsets.UTF_8 ); } private static class StatementTraceException extends RuntimeException { StatementTraceException( String message ) { super( message ); } @Override public synchronized Throwable fillInStackTrace() { return this; } } } }




© 2015 - 2024 Weber Informatics LLC | Privacy Policy