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

org.neo4j.driver.internal.InternalSession Maven / Gradle / Ivy

The newest version!
/*
 * Copyright (c) "Neo4j"
 * Neo4j Sweden AB [https://neo4j.com]
 *
 * 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;

import static java.util.Collections.emptyMap;

import java.util.Map;
import java.util.Set;
import org.neo4j.driver.AccessMode;
import org.neo4j.driver.Bookmark;
import org.neo4j.driver.Query;
import org.neo4j.driver.Result;
import org.neo4j.driver.Session;
import org.neo4j.driver.Transaction;
import org.neo4j.driver.TransactionCallback;
import org.neo4j.driver.TransactionConfig;
import org.neo4j.driver.TransactionWork;
import org.neo4j.driver.exceptions.ClientException;
import org.neo4j.driver.internal.async.NetworkSession;
import org.neo4j.driver.internal.bolt.api.BoltConnection;
import org.neo4j.driver.internal.bolt.api.GqlStatusError;
import org.neo4j.driver.internal.bolt.api.TelemetryApi;
import org.neo4j.driver.internal.telemetry.ApiTelemetryWork;
import org.neo4j.driver.internal.util.Futures;

public class InternalSession extends AbstractQueryRunner implements Session {
    private final NetworkSession session;

    public InternalSession(NetworkSession session) {
        this.session = session;
    }

    @Override
    public Result run(Query query) {
        return run(query, TransactionConfig.empty());
    }

    @Override
    public Result run(String query, TransactionConfig config) {
        return run(query, emptyMap(), config);
    }

    @Override
    public Result run(String query, Map parameters, TransactionConfig config) {
        return run(new Query(query, parameters), config);
    }

    @Override
    public Result run(Query query, TransactionConfig config) {
        var cursor = Futures.blockingGet(
                session.runAsync(query, config),
                () -> terminateConnectionOnThreadInterrupt("Thread interrupted while running query in session"));

        // query executed, it is safe to obtain a connection in a blocking way
        var connection = Futures.getNow(session.connectionAsync());
        return new InternalResult(connection, cursor);
    }

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

    @Override
    public void close() {
        Futures.blockingGet(
                session.closeAsync(),
                () -> terminateConnectionOnThreadInterrupt("Thread interrupted while closing the session"));
    }

    @Override
    public Transaction beginTransaction() {
        return beginTransaction(TransactionConfig.empty());
    }

    @Override
    public Transaction beginTransaction(TransactionConfig config) {
        return beginTransaction(config, null);
    }

    public Transaction beginTransaction(TransactionConfig config, String txType) {
        var tx = Futures.blockingGet(
                session.beginTransactionAsync(config, txType, new ApiTelemetryWork(TelemetryApi.UNMANAGED_TRANSACTION)),
                () -> terminateConnectionOnThreadInterrupt("Thread interrupted while starting a transaction"));
        return new InternalTransaction(tx);
    }

    @Override
    @Deprecated
    public  T readTransaction(TransactionWork work) {
        return readTransaction(work, TransactionConfig.empty());
    }

    @Override
    @Deprecated
    public  T readTransaction(TransactionWork work, TransactionConfig config) {
        return transaction(AccessMode.READ, work, config, TelemetryApi.MANAGED_TRANSACTION, true);
    }

    @Override
    public  T executeRead(TransactionCallback callback, TransactionConfig config) {
        return execute(AccessMode.READ, callback, config, TelemetryApi.MANAGED_TRANSACTION, true);
    }

    @Override
    @Deprecated
    public  T writeTransaction(TransactionWork work) {
        return writeTransaction(work, TransactionConfig.empty());
    }

    @Override
    @Deprecated
    public  T writeTransaction(TransactionWork work, TransactionConfig config) {
        return transaction(AccessMode.WRITE, work, config, TelemetryApi.MANAGED_TRANSACTION, true);
    }

    @Override
    public  T executeWrite(TransactionCallback callback, TransactionConfig config) {
        return execute(AccessMode.WRITE, callback, config, TelemetryApi.MANAGED_TRANSACTION, true);
    }

    @Override
    @Deprecated
    public Bookmark lastBookmark() {
        return InternalBookmark.from(session.lastBookmarks());
    }

    @Override
    public Set lastBookmarks() {
        return session.lastBookmarks();
    }

    // Temporary private API
    @Deprecated
    public void reset() {
        Futures.blockingGet(
                session.resetAsync(),
                () -> terminateConnectionOnThreadInterrupt("Thread interrupted while resetting the session"));
    }

     T execute(
            AccessMode accessMode,
            TransactionCallback callback,
            TransactionConfig config,
            TelemetryApi telemetryApi,
            boolean flush) {
        return transaction(
                accessMode, tx -> callback.execute(new DelegatingTransactionContext(tx)), config, telemetryApi, flush);
    }

    private  T transaction(
            AccessMode mode,
            @SuppressWarnings("deprecation") TransactionWork work,
            TransactionConfig config,
            TelemetryApi telemetryApi,
            boolean flush) {
        // use different code path compared to async so that work is executed in the caller thread
        // caller thread will also be the one who sleeps between retries;
        // it is unsafe to execute retries in the event loop threads because this can cause a deadlock
        // event loop thread will bock and wait for itself to read some data
        var apiTelemetryWork = new ApiTelemetryWork(telemetryApi);
        return session.retryLogic().retry(() -> {
            try (var tx = beginTransaction(mode, config, apiTelemetryWork, flush)) {

                var result = work.execute(tx);
                if (result instanceof Result) {
                    var message = String.format(
                            "%s is not a valid return value, it should be consumed before producing a return value",
                            Result.class.getName());
                    throw new ClientException(
                            GqlStatusError.UNKNOWN.getStatus(),
                            GqlStatusError.UNKNOWN.getStatusDescription(message),
                            "N/A",
                            message,
                            GqlStatusError.DIAGNOSTIC_RECORD,
                            null);
                }
                if (tx.isOpen()) {
                    // commit tx if a user has not explicitly committed or rolled back the transaction
                    tx.commit();
                }
                return result;
            }
        });
    }

    private Transaction beginTransaction(
            AccessMode mode, TransactionConfig config, ApiTelemetryWork apiTelemetryWork, boolean flush) {
        var tx = Futures.blockingGet(
                session.beginTransactionAsync(mode, config, null, apiTelemetryWork, flush),
                () -> terminateConnectionOnThreadInterrupt("Thread interrupted while starting a transaction"));
        return new InternalTransaction(tx);
    }

    private void terminateConnectionOnThreadInterrupt(String reason) {
        // try to get current connection if it has been acquired
        BoltConnection connection = null;
        try {
            connection = Futures.getNow(session.connectionAsync());
        } catch (Throwable ignore) {
            // ignore errors because handing interruptions is best effort
        }

        if (connection != null) {
            connection.forceClose(reason);
        }
    }
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy