diff --git a/driver/src/main/java/org/neo4j/driver/Session.java b/driver/src/main/java/org/neo4j/driver/Session.java index 99cb719eb7..d65fbf3c23 100644 --- a/driver/src/main/java/org/neo4j/driver/Session.java +++ b/driver/src/main/java/org/neo4j/driver/Session.java @@ -19,7 +19,6 @@ package org.neo4j.driver; import java.util.Map; -import java.util.function.Consumer; import org.neo4j.driver.async.AsyncSession; import org.neo4j.driver.util.Resource; @@ -85,20 +84,6 @@ public interface Session extends Resource, StatementRunner */ Transaction beginTransaction( TransactionConfig config ); - /** - * Begin a new explicit {@linkplain Transaction transaction}, - * requiring that the server hosting is at least as up-to-date as the - * transaction referenced by the supplied bookmark. - * - * @param bookmark a reference to a previous transaction - * @return a new {@link Transaction} - * @deprecated This method is deprecated in favour of {@link Driver#session(Consumer)} that accepts an initial - * bookmark. Session will ensure that all nested transactions are chained with bookmarks to guarantee - * causal consistency. This method will be removed in the next major release. - */ - @Deprecated - Transaction beginTransaction( String bookmark ); - /** * Execute given unit of work in a {@link AccessMode#READ read} transaction. *

diff --git a/driver/src/main/java/org/neo4j/driver/async/AsyncTransactionWork.java b/driver/src/main/java/org/neo4j/driver/async/AsyncTransactionWork.java index 838fcd6a81..456c9b0af2 100644 --- a/driver/src/main/java/org/neo4j/driver/async/AsyncTransactionWork.java +++ b/driver/src/main/java/org/neo4j/driver/async/AsyncTransactionWork.java @@ -18,10 +18,8 @@ */ package org.neo4j.driver.async; -import org.neo4j.driver.Transaction; - /** - * Callback that executes operations against a given {@link Transaction}. + * Callback that executes operations against a given {@link AsyncTransaction}. * To be used with {@link AsyncSession#readTransactionAsync(AsyncTransactionWork)} and * {@link AsyncSession#writeTransactionAsync(AsyncTransactionWork)} (AsyncTransactionWork)} methods. * diff --git a/driver/src/main/java/org/neo4j/driver/internal/AbstractStatementRunner.java b/driver/src/main/java/org/neo4j/driver/internal/AbstractStatementRunner.java index 6ee3d75dff..59abbf0101 100644 --- a/driver/src/main/java/org/neo4j/driver/internal/AbstractStatementRunner.java +++ b/driver/src/main/java/org/neo4j/driver/internal/AbstractStatementRunner.java @@ -19,22 +19,19 @@ package org.neo4j.driver.internal; import java.util.Map; -import java.util.concurrent.CompletionStage; -import org.neo4j.driver.async.AsyncStatementRunner; -import org.neo4j.driver.internal.types.InternalTypeSystem; -import org.neo4j.driver.internal.util.Extract; -import org.neo4j.driver.internal.value.MapValue; import org.neo4j.driver.Record; import org.neo4j.driver.Statement; import org.neo4j.driver.StatementResult; -import org.neo4j.driver.async.StatementResultCursor; import org.neo4j.driver.StatementRunner; import org.neo4j.driver.Value; import org.neo4j.driver.Values; +import org.neo4j.driver.internal.types.InternalTypeSystem; +import org.neo4j.driver.internal.util.Extract; +import org.neo4j.driver.internal.value.MapValue; import org.neo4j.driver.types.TypeSystem; -public abstract class AbstractStatementRunner implements StatementRunner, AsyncStatementRunner +public abstract class AbstractStatementRunner implements StatementRunner { @Override public final StatementResult run( String statementTemplate, Value parameters ) @@ -42,48 +39,24 @@ public final StatementResult run( String statementTemplate, Value parameters ) return run( new Statement( statementTemplate, parameters ) ); } - @Override - public final CompletionStage runAsync( String statementTemplate, Value parameters ) - { - return runAsync( new Statement( statementTemplate, parameters ) ); - } - @Override public final StatementResult run( String statementTemplate, Map statementParameters ) { return run( statementTemplate, parameters( statementParameters ) ); } - @Override - public final CompletionStage runAsync( String statementTemplate, Map statementParameters ) - { - return runAsync( statementTemplate, parameters( statementParameters ) ); - } - @Override public final StatementResult run( String statementTemplate, Record statementParameters ) { return run( statementTemplate, parameters( statementParameters ) ); } - @Override - public final CompletionStage runAsync( String statementTemplate, Record statementParameters ) - { - return runAsync( statementTemplate, parameters( statementParameters ) ); - } - @Override public final StatementResult run( String statementText ) { return run( statementText, Values.EmptyMap ); } - @Override - public final CompletionStage runAsync( String statementText ) - { - return runAsync( statementText, Values.EmptyMap ); - } - @Override public final TypeSystem typeSystem() { diff --git a/driver/src/main/java/org/neo4j/driver/internal/DirectConnectionProvider.java b/driver/src/main/java/org/neo4j/driver/internal/DirectConnectionProvider.java index dfdcccbbcc..329414c0b5 100644 --- a/driver/src/main/java/org/neo4j/driver/internal/DirectConnectionProvider.java +++ b/driver/src/main/java/org/neo4j/driver/internal/DirectConnectionProvider.java @@ -21,7 +21,7 @@ import java.util.concurrent.CompletionStage; import org.neo4j.driver.AccessMode; -import org.neo4j.driver.internal.async.DecoratedConnection; +import org.neo4j.driver.internal.async.connection.DecoratedConnection; import org.neo4j.driver.internal.spi.Connection; import org.neo4j.driver.internal.spi.ConnectionPool; import org.neo4j.driver.internal.spi.ConnectionProvider; diff --git a/driver/src/main/java/org/neo4j/driver/internal/DriverFactory.java b/driver/src/main/java/org/neo4j/driver/internal/DriverFactory.java index e6872a6127..e83a167103 100644 --- a/driver/src/main/java/org/neo4j/driver/internal/DriverFactory.java +++ b/driver/src/main/java/org/neo4j/driver/internal/DriverFactory.java @@ -26,9 +26,9 @@ import java.net.URI; import java.security.GeneralSecurityException; -import org.neo4j.driver.internal.async.BootstrapFactory; -import org.neo4j.driver.internal.async.ChannelConnector; -import org.neo4j.driver.internal.async.ChannelConnectorImpl; +import org.neo4j.driver.internal.async.connection.BootstrapFactory; +import org.neo4j.driver.internal.async.connection.ChannelConnector; +import org.neo4j.driver.internal.async.connection.ChannelConnectorImpl; import org.neo4j.driver.internal.async.pool.ConnectionPoolImpl; import org.neo4j.driver.internal.async.pool.PoolSettings; import org.neo4j.driver.internal.cluster.DnsResolver; diff --git a/driver/src/main/java/org/neo4j/driver/internal/InternalDriver.java b/driver/src/main/java/org/neo4j/driver/internal/InternalDriver.java index b625a6bdbf..9880cf2da4 100644 --- a/driver/src/main/java/org/neo4j/driver/internal/InternalDriver.java +++ b/driver/src/main/java/org/neo4j/driver/internal/InternalDriver.java @@ -29,6 +29,8 @@ import org.neo4j.driver.Session; import org.neo4j.driver.SessionParametersTemplate; import org.neo4j.driver.async.AsyncSession; +import org.neo4j.driver.internal.async.InternalAsyncSession; +import org.neo4j.driver.internal.async.NetworkSession; import org.neo4j.driver.internal.metrics.MetricsProvider; import org.neo4j.driver.internal.reactive.InternalRxSession; import org.neo4j.driver.internal.security.SecurityPlan; @@ -57,7 +59,7 @@ public class InternalDriver implements Driver @Override public Session session() { - return newSession( SessionParameters.empty() ); + return new InternalSession( newSession( SessionParameters.empty() ) ); } @Override @@ -65,7 +67,7 @@ public Session session( Consumer templateConsumer ) { SessionParameters.Template template = SessionParameters.template(); templateConsumer.accept( template ); - return newSession( template.build() ); + return new InternalSession( newSession( template.build() ) ); } @Override @@ -85,7 +87,7 @@ public RxSession rxSession( Consumer templateConsumer @Override public AsyncSession asyncSession() { - return newSession( SessionParameters.empty() ); + return new InternalAsyncSession( newSession( SessionParameters.empty() ) ); } @Override @@ -93,7 +95,7 @@ public AsyncSession asyncSession( Consumer templateCo { SessionParameters.Template template = SessionParameters.template(); templateConsumer.accept( template ); - return newSession( template.build() ); + return new InternalAsyncSession( newSession( template.build() ) ); } @Override @@ -148,7 +150,7 @@ private static RuntimeException driverCloseException() return new IllegalStateException( "This driver instance has already been closed" ); } - private NetworkSession newSession( SessionParameters parameters ) + public NetworkSession newSession( SessionParameters parameters ) { assertOpen(); NetworkSession session = sessionFactory.newInstance( parameters ); diff --git a/driver/src/main/java/org/neo4j/driver/internal/InternalSession.java b/driver/src/main/java/org/neo4j/driver/internal/InternalSession.java new file mode 100644 index 0000000000..5c937c8fa4 --- /dev/null +++ b/driver/src/main/java/org/neo4j/driver/internal/InternalSession.java @@ -0,0 +1,190 @@ +/* + * Copyright (c) 2002-2019 "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; + +import java.util.Map; + +import org.neo4j.driver.AccessMode; +import org.neo4j.driver.Session; +import org.neo4j.driver.Statement; +import org.neo4j.driver.StatementResult; +import org.neo4j.driver.Transaction; +import org.neo4j.driver.TransactionConfig; +import org.neo4j.driver.TransactionWork; +import org.neo4j.driver.async.StatementResultCursor; +import org.neo4j.driver.internal.async.ExplicitTransaction; +import org.neo4j.driver.internal.async.NetworkSession; +import org.neo4j.driver.internal.spi.Connection; +import org.neo4j.driver.internal.util.Futures; + +import static java.util.Collections.emptyMap; + +public class InternalSession extends AbstractStatementRunner implements Session +{ + private final NetworkSession session; + + public InternalSession( NetworkSession session ) + { + this.session = session; + } + + @Override + public StatementResult run( Statement statement ) + { + return run( statement, TransactionConfig.empty() ); + } + + @Override + public StatementResult run( String statement, TransactionConfig config ) + { + return run( statement, emptyMap(), config ); + } + + @Override + public StatementResult run( String statement, Map parameters, TransactionConfig config ) + { + return run( new Statement( statement, parameters ), config ); + } + + @Override + public StatementResult run( Statement statement, TransactionConfig config ) + { + StatementResultCursor cursor = Futures.blockingGet( session.runAsync( statement, config, false ), + () -> terminateConnectionOnThreadInterrupt( "Thread interrupted while running query in session" ) ); + + // query executed, it is safe to obtain a connection in a blocking way + Connection connection = Futures.getNow( session.connectionAsync() ); + return new InternalStatementResult( 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 ) + { + ExplicitTransaction tx = Futures.blockingGet( session.beginTransactionAsync( config ), + () -> terminateConnectionOnThreadInterrupt( "Thread interrupted while starting a transaction" ) ); + return new InternalTransaction( tx ); + } + + @Override + public T readTransaction( TransactionWork work ) + { + return readTransaction( work, TransactionConfig.empty() ); + } + + @Override + public T readTransaction( TransactionWork work, TransactionConfig config ) + { + return transaction( AccessMode.READ, work, config ); + } + + @Override + public T writeTransaction( TransactionWork work ) + { + return writeTransaction( work, TransactionConfig.empty() ); + } + + @Override + public T writeTransaction( TransactionWork work, TransactionConfig config ) + { + return transaction( AccessMode.WRITE, work, config ); + } + + @Override + public String lastBookmark() + { + return session.lastBookmark(); + } + + @Override + @SuppressWarnings( "deprecation" ) + public void reset() + { + Futures.blockingGet( session.resetAsync(), () -> terminateConnectionOnThreadInterrupt( "Thread interrupted while resetting the session" ) ); + } + + private T transaction( AccessMode mode, TransactionWork work, TransactionConfig config ) + { + // 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 + return session.retryLogic().retry( () -> { + try ( Transaction tx = beginTransaction( mode, config ) ) + { + try + { + T result = work.execute( tx ); + tx.success(); + return result; + } + catch ( Throwable t ) + { + // mark transaction for failure if the given unit of work threw exception + // this will override any success marks that were made by the unit of work + tx.failure(); + throw t; + } + } + } ); + } + + private Transaction beginTransaction( AccessMode mode, TransactionConfig config ) + { + ExplicitTransaction tx = Futures.blockingGet( session.beginTransactionAsync( mode, config ), + () -> 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 + Connection connection = null; + try + { + connection = Futures.getNow( session.connectionAsync() ); + } + catch ( Throwable ignore ) + { + // ignore errors because handing interruptions is best effort + } + + if ( connection != null ) + { + connection.terminateAndRelease( reason ); + } + } +} diff --git a/driver/src/main/java/org/neo4j/driver/internal/InternalTransaction.java b/driver/src/main/java/org/neo4j/driver/internal/InternalTransaction.java new file mode 100644 index 0000000000..60d26b6947 --- /dev/null +++ b/driver/src/main/java/org/neo4j/driver/internal/InternalTransaction.java @@ -0,0 +1,73 @@ +/* + * Copyright (c) 2002-2019 "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; + +import org.neo4j.driver.Statement; +import org.neo4j.driver.StatementResult; +import org.neo4j.driver.Transaction; +import org.neo4j.driver.async.StatementResultCursor; +import org.neo4j.driver.internal.async.ExplicitTransaction; +import org.neo4j.driver.internal.util.Futures; + +public class InternalTransaction extends AbstractStatementRunner implements Transaction +{ + private final ExplicitTransaction tx; + public InternalTransaction( ExplicitTransaction tx ) + { + this.tx = tx; + } + + @Override + public void success() + { + tx.success(); + } + + @Override + public void failure() + { + tx.failure(); + } + + @Override + public void close() + { + Futures.blockingGet( tx.closeAsync(), + () -> terminateConnectionOnThreadInterrupt( "Thread interrupted while closing the transaction" ) ); + } + + @Override + public StatementResult run( Statement statement ) + { + StatementResultCursor cursor = Futures.blockingGet( tx.runAsync( statement, false ), + () -> terminateConnectionOnThreadInterrupt( "Thread interrupted while running query in transaction" ) ); + return new InternalStatementResult( tx.connection(), cursor ); + } + + @Override + public boolean isOpen() + { + return tx.isOpen(); + } + + private void terminateConnectionOnThreadInterrupt( String reason ) + { + tx.connection().terminateAndRelease( reason ); + } +} diff --git a/driver/src/main/java/org/neo4j/driver/internal/SessionFactory.java b/driver/src/main/java/org/neo4j/driver/internal/SessionFactory.java index d2f2657bd8..2863980644 100644 --- a/driver/src/main/java/org/neo4j/driver/internal/SessionFactory.java +++ b/driver/src/main/java/org/neo4j/driver/internal/SessionFactory.java @@ -20,6 +20,8 @@ import java.util.concurrent.CompletionStage; +import org.neo4j.driver.internal.async.NetworkSession; + public interface SessionFactory { NetworkSession newInstance( SessionParameters parameters ); diff --git a/driver/src/main/java/org/neo4j/driver/internal/SessionFactoryImpl.java b/driver/src/main/java/org/neo4j/driver/internal/SessionFactoryImpl.java index 5cb16520d7..37ee254375 100644 --- a/driver/src/main/java/org/neo4j/driver/internal/SessionFactoryImpl.java +++ b/driver/src/main/java/org/neo4j/driver/internal/SessionFactoryImpl.java @@ -23,6 +23,8 @@ import org.neo4j.driver.AccessMode; import org.neo4j.driver.Config; import org.neo4j.driver.Logging; +import org.neo4j.driver.internal.async.NetworkSession; +import org.neo4j.driver.internal.async.LeakLoggingNetworkSession; import org.neo4j.driver.internal.retry.RetryLogic; import org.neo4j.driver.internal.spi.ConnectionProvider; diff --git a/driver/src/main/java/org/neo4j/driver/internal/async/AsyncAbstractStatementRunner.java b/driver/src/main/java/org/neo4j/driver/internal/async/AsyncAbstractStatementRunner.java new file mode 100644 index 0000000000..d2a7df9ea4 --- /dev/null +++ b/driver/src/main/java/org/neo4j/driver/internal/async/AsyncAbstractStatementRunner.java @@ -0,0 +1,58 @@ +/* + * Copyright (c) 2002-2019 "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; + +import java.util.Map; +import java.util.concurrent.CompletionStage; + +import org.neo4j.driver.Record; +import org.neo4j.driver.Statement; +import org.neo4j.driver.Value; +import org.neo4j.driver.Values; +import org.neo4j.driver.async.AsyncStatementRunner; +import org.neo4j.driver.async.StatementResultCursor; + +import static org.neo4j.driver.internal.AbstractStatementRunner.parameters; + +public abstract class AsyncAbstractStatementRunner implements AsyncStatementRunner +{ + @Override + public final CompletionStage runAsync( String statementTemplate, Value parameters ) + { + return runAsync( new Statement( statementTemplate, parameters ) ); + } + + @Override + public final CompletionStage runAsync( String statementTemplate, Map statementParameters ) + { + return runAsync( statementTemplate, parameters( statementParameters ) ); + } + + @Override + public final CompletionStage runAsync( String statementTemplate, Record statementParameters ) + { + return runAsync( statementTemplate, parameters( statementParameters ) ); + } + + @Override + public final CompletionStage runAsync( String statementText ) + { + return runAsync( statementText, Values.EmptyMap ); + } +} diff --git a/driver/src/main/java/org/neo4j/driver/internal/AsyncStatementResultCursor.java b/driver/src/main/java/org/neo4j/driver/internal/async/AsyncStatementResultCursor.java similarity index 97% rename from driver/src/main/java/org/neo4j/driver/internal/AsyncStatementResultCursor.java rename to driver/src/main/java/org/neo4j/driver/internal/async/AsyncStatementResultCursor.java index 9b306aa207..dd82c5f2cd 100644 --- a/driver/src/main/java/org/neo4j/driver/internal/AsyncStatementResultCursor.java +++ b/driver/src/main/java/org/neo4j/driver/internal/async/AsyncStatementResultCursor.java @@ -16,7 +16,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package org.neo4j.driver.internal; +package org.neo4j.driver.internal.async; import java.util.List; import java.util.concurrent.CompletableFuture; @@ -27,7 +27,7 @@ import org.neo4j.driver.internal.handlers.PullAllResponseHandler; import org.neo4j.driver.internal.handlers.RunResponseHandler; import org.neo4j.driver.internal.util.Futures; -import org.neo4j.driver.internal.reactive.cursor.InternalStatementResultCursor; +import org.neo4j.driver.internal.cursor.InternalStatementResultCursor; import org.neo4j.driver.Record; import org.neo4j.driver.exceptions.NoSuchRecordException; import org.neo4j.driver.summary.ResultSummary; diff --git a/driver/src/main/java/org/neo4j/driver/internal/ExplicitTransaction.java b/driver/src/main/java/org/neo4j/driver/internal/async/ExplicitTransaction.java similarity index 82% rename from driver/src/main/java/org/neo4j/driver/internal/ExplicitTransaction.java rename to driver/src/main/java/org/neo4j/driver/internal/async/ExplicitTransaction.java index 30efd30958..fc1e848240 100644 --- a/driver/src/main/java/org/neo4j/driver/internal/ExplicitTransaction.java +++ b/driver/src/main/java/org/neo4j/driver/internal/async/ExplicitTransaction.java @@ -16,31 +16,29 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package org.neo4j.driver.internal; +package org.neo4j.driver.internal.async; import java.util.concurrent.CompletionException; import java.util.concurrent.CompletionStage; import java.util.function.BiFunction; -import org.neo4j.driver.async.AsyncTransaction; -import org.neo4j.driver.internal.async.ResultCursorsHolder; -import org.neo4j.driver.internal.messaging.BoltProtocol; -import org.neo4j.driver.internal.spi.Connection; -import org.neo4j.driver.internal.util.Futures; -import org.neo4j.driver.internal.reactive.cursor.InternalStatementResultCursor; -import org.neo4j.driver.internal.reactive.cursor.RxStatementResultCursor; import org.neo4j.driver.Session; import org.neo4j.driver.Statement; -import org.neo4j.driver.StatementResult; -import org.neo4j.driver.async.StatementResultCursor; -import org.neo4j.driver.Transaction; import org.neo4j.driver.TransactionConfig; +import org.neo4j.driver.async.StatementResultCursor; import org.neo4j.driver.exceptions.ClientException; +import org.neo4j.driver.internal.Bookmarks; +import org.neo4j.driver.internal.BookmarksHolder; +import org.neo4j.driver.internal.cursor.InternalStatementResultCursor; +import org.neo4j.driver.internal.cursor.RxStatementResultCursor; +import org.neo4j.driver.internal.messaging.BoltProtocol; +import org.neo4j.driver.internal.spi.Connection; +import org.neo4j.driver.internal.util.Futures; import static org.neo4j.driver.internal.util.Futures.completedWithNull; import static org.neo4j.driver.internal.util.Futures.failedFuture; -public class ExplicitTransaction extends AbstractStatementRunner implements Transaction, AsyncTransaction +public class ExplicitTransaction { private enum State { @@ -96,7 +94,6 @@ public CompletionStage beginAsync( Bookmarks initialBookmar } ); } - @Override public void success() { if ( state == State.ACTIVE ) @@ -105,7 +102,6 @@ public void success() } } - @Override public void failure() { if ( state == State.ACTIVE || state == State.MARKED_SUCCESS ) @@ -114,14 +110,7 @@ public void failure() } } - @Override - public void close() - { - Futures.blockingGet( closeAsync(), - () -> terminateConnectionOnThreadInterrupt( "Thread interrupted while closing the transaction" ) ); - } - - CompletionStage closeAsync() + public CompletionStage closeAsync() { if ( state == State.MARKED_SUCCESS ) { @@ -137,7 +126,6 @@ else if ( state != State.COMMITTED && state != State.ROLLED_BACK ) } } - @Override public CompletionStage commitAsync() { if ( state == State.COMMITTED ) @@ -156,7 +144,6 @@ else if ( state == State.ROLLED_BACK ) } } - @Override public CompletionStage rollbackAsync() { if ( state == State.COMMITTED ) @@ -175,28 +162,13 @@ else if ( state == State.ROLLED_BACK ) } } - @Override - public StatementResult run( Statement statement ) - { - StatementResultCursor cursor = Futures.blockingGet( run( statement, false ), - () -> terminateConnectionOnThreadInterrupt( "Thread interrupted while running query in transaction" ) ); - return new InternalStatementResult( connection, cursor ); - } - - @Override - public CompletionStage runAsync( Statement statement ) - { - //noinspection unchecked - return (CompletionStage) run( statement, true ); - } - - private CompletionStage run( Statement statement, boolean waitForRunResponse ) + public CompletionStage runAsync( Statement statement, boolean waitForRunResponse ) { ensureCanRunQueries(); CompletionStage cursorStage = protocol.runInExplicitTransaction( connection, statement, this, waitForRunResponse ).asyncResult(); resultCursors.add( cursorStage ); - return cursorStage; + return cursorStage.thenApply( cursor -> cursor ); } public CompletionStage runRx( Statement statement ) @@ -208,6 +180,21 @@ public CompletionStage runRx( Statement statement ) return cursorStage; } + public boolean isOpen() + { + return state != State.COMMITTED && state != State.ROLLED_BACK; + } + + public void markTerminated() + { + state = State.TERMINATED; + } + + public Connection connection() + { + return connection; + } + private void ensureCanRunQueries() { if ( state == State.COMMITTED ) @@ -221,26 +208,15 @@ else if ( state == State.ROLLED_BACK ) else if ( state == State.MARKED_FAILED ) { throw new ClientException( "Cannot run more statements in this transaction, it has been marked for failure. " + - "Please either rollback or close this transaction" ); + "Please either rollback or close this transaction" ); } else if ( state == State.TERMINATED ) { throw new ClientException( "Cannot run more statements in this transaction, " + - "it has either experienced an fatal error or was explicitly terminated" ); + "it has either experienced an fatal error or was explicitly terminated" ); } } - @Override - public boolean isOpen() - { - return state != State.COMMITTED && state != State.ROLLED_BACK; - } - - public void markTerminated() - { - state = State.TERMINATED; - } - private CompletionStage doCommitAsync() { if ( state == State.TERMINATED ) @@ -285,9 +261,4 @@ private void transactionClosed( boolean isCommitted ) } connection.release(); // release in background } - - private void terminateConnectionOnThreadInterrupt( String reason ) - { - connection.terminateAndRelease( reason ); - } } diff --git a/driver/src/main/java/org/neo4j/driver/internal/async/InternalAsyncSession.java b/driver/src/main/java/org/neo4j/driver/internal/async/InternalAsyncSession.java new file mode 100644 index 0000000000..7dc5837fba --- /dev/null +++ b/driver/src/main/java/org/neo4j/driver/internal/async/InternalAsyncSession.java @@ -0,0 +1,216 @@ +/* + * Copyright (c) 2002-2019 "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; + +import java.util.Map; +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.CompletionStage; + +import org.neo4j.driver.AccessMode; +import org.neo4j.driver.Statement; +import org.neo4j.driver.TransactionConfig; +import org.neo4j.driver.async.AsyncSession; +import org.neo4j.driver.async.AsyncTransaction; +import org.neo4j.driver.async.AsyncTransactionWork; +import org.neo4j.driver.async.StatementResultCursor; +import org.neo4j.driver.internal.util.Futures; + +import static java.util.Collections.emptyMap; +import static org.neo4j.driver.internal.util.Futures.completedWithNull; +import static org.neo4j.driver.internal.util.Futures.failedFuture; + +public class InternalAsyncSession extends AsyncAbstractStatementRunner implements AsyncSession +{ + private final NetworkSession session; + + public InternalAsyncSession( NetworkSession session ) + { + this.session = session; + } + + @Override + public CompletionStage runAsync( Statement statement ) + { + return runAsync( statement, TransactionConfig.empty() ); + } + + @Override + public CompletionStage runAsync( String statement, TransactionConfig config ) + { + return runAsync( statement, emptyMap(), config ); + } + + @Override + public CompletionStage runAsync( String statement, Map parameters, TransactionConfig config ) + { + return runAsync( new Statement( statement, parameters ), config ); + } + + @Override + public CompletionStage runAsync( Statement statement, TransactionConfig config ) + { + return session.runAsync( statement, config, true ); + } + + @Override + public CompletionStage closeAsync() + { + return session.closeAsync(); + } + + @Override + public CompletionStage beginTransactionAsync() + { + return beginTransactionAsync( TransactionConfig.empty() ); + } + + @Override + public CompletionStage beginTransactionAsync( TransactionConfig config ) + { + return session.beginTransactionAsync( config ).thenApply( InternalAsyncTransaction::new ); + } + + @Override + public CompletionStage readTransactionAsync( AsyncTransactionWork> work ) + { + return readTransactionAsync( work, TransactionConfig.empty() ); + } + + @Override + public CompletionStage readTransactionAsync( AsyncTransactionWork> work, TransactionConfig config ) + { + return transactionAsync( AccessMode.READ, work, config ); + } + + @Override + public CompletionStage writeTransactionAsync( AsyncTransactionWork> work ) + { + return writeTransactionAsync( work, TransactionConfig.empty() ); + } + + @Override + public CompletionStage writeTransactionAsync( AsyncTransactionWork> work, TransactionConfig config ) + { + return transactionAsync( AccessMode.WRITE, work, config ); + } + + @Override + public String lastBookmark() + { + return session.lastBookmark(); + } + + private CompletionStage transactionAsync( AccessMode mode, AsyncTransactionWork> work, TransactionConfig config ) + { + return session.retryLogic().retryAsync( () -> { + CompletableFuture resultFuture = new CompletableFuture<>(); + CompletionStage txFuture = session.beginTransactionAsync( mode, config ); + + txFuture.whenComplete( ( tx, completionError ) -> { + Throwable error = Futures.completionExceptionCause( completionError ); + if ( error != null ) + { + resultFuture.completeExceptionally( error ); + } + else + { + executeWork( resultFuture, tx, work ); + } + } ); + + return resultFuture; + } ); + } + + private void executeWork( CompletableFuture resultFuture, ExplicitTransaction tx, AsyncTransactionWork> work ) + { + CompletionStage workFuture = safeExecuteWork( tx, work ); + workFuture.whenComplete( ( result, completionError ) -> { + Throwable error = Futures.completionExceptionCause( completionError ); + if ( error != null ) + { + rollbackTxAfterFailedTransactionWork( tx, resultFuture, error ); + } + else + { + closeTxAfterSucceededTransactionWork( tx, resultFuture, result ); + } + } ); + } + + private CompletionStage safeExecuteWork( ExplicitTransaction tx, AsyncTransactionWork> work ) + { + // given work might fail in both async and sync way + // async failure will result in a failed future being returned + // sync failure will result in an exception being thrown + try + { + CompletionStage result = work.execute( new InternalAsyncTransaction( tx ) ); + + // protect from given transaction function returning null + return result == null ? completedWithNull() : result; + } + catch ( Throwable workError ) + { + // work threw an exception, wrap it in a future and proceed + return failedFuture( workError ); + } + } + + private void rollbackTxAfterFailedTransactionWork( ExplicitTransaction tx, CompletableFuture resultFuture, Throwable error ) + { + if ( tx.isOpen() ) + { + tx.rollbackAsync().whenComplete( ( ignore, rollbackError ) -> { + if ( rollbackError != null ) + { + error.addSuppressed( rollbackError ); + } + resultFuture.completeExceptionally( error ); + } ); + } + else + { + resultFuture.completeExceptionally( error ); + } + } + + private void closeTxAfterSucceededTransactionWork( ExplicitTransaction tx, CompletableFuture resultFuture, T result ) + { + if ( tx.isOpen() ) + { + tx.success(); + tx.closeAsync().whenComplete( ( ignore, completionError ) -> { + Throwable commitError = Futures.completionExceptionCause( completionError ); + if ( commitError != null ) + { + resultFuture.completeExceptionally( commitError ); + } + else + { + resultFuture.complete( result ); + } + } ); + } + else + { + resultFuture.complete( result ); + } + } +} diff --git a/driver/src/main/java/org/neo4j/driver/internal/async/InternalAsyncTransaction.java b/driver/src/main/java/org/neo4j/driver/internal/async/InternalAsyncTransaction.java new file mode 100644 index 0000000000..75582f6bf4 --- /dev/null +++ b/driver/src/main/java/org/neo4j/driver/internal/async/InternalAsyncTransaction.java @@ -0,0 +1,62 @@ +/* + * Copyright (c) 2002-2019 "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; + +import java.util.concurrent.CompletionStage; + +import org.neo4j.driver.Statement; +import org.neo4j.driver.async.AsyncTransaction; +import org.neo4j.driver.async.StatementResultCursor; + +public class InternalAsyncTransaction extends AsyncAbstractStatementRunner implements AsyncTransaction +{ + private final ExplicitTransaction tx; + public InternalAsyncTransaction( ExplicitTransaction tx ) + { + this.tx = tx; + } + + @Override + public CompletionStage commitAsync() + { + return tx.commitAsync(); + } + + @Override + public CompletionStage rollbackAsync() + { + return tx.rollbackAsync(); + } + + @Override + public CompletionStage runAsync( Statement statement ) + { + return tx.runAsync( statement, true ); + } + + public void markTerminated() + { + tx.markTerminated(); + } + + public boolean isOpen() + { + return tx.isOpen(); + } +} diff --git a/driver/src/main/java/org/neo4j/driver/internal/LeakLoggingNetworkSession.java b/driver/src/main/java/org/neo4j/driver/internal/async/LeakLoggingNetworkSession.java similarity index 88% rename from driver/src/main/java/org/neo4j/driver/internal/LeakLoggingNetworkSession.java rename to driver/src/main/java/org/neo4j/driver/internal/async/LeakLoggingNetworkSession.java index 3a93085aaa..c50f013069 100644 --- a/driver/src/main/java/org/neo4j/driver/internal/LeakLoggingNetworkSession.java +++ b/driver/src/main/java/org/neo4j/driver/internal/async/LeakLoggingNetworkSession.java @@ -16,21 +16,22 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package org.neo4j.driver.internal; +package org.neo4j.driver.internal.async; +import org.neo4j.driver.AccessMode; +import org.neo4j.driver.Logging; +import org.neo4j.driver.internal.BookmarksHolder; import org.neo4j.driver.internal.retry.RetryLogic; import org.neo4j.driver.internal.spi.ConnectionProvider; import org.neo4j.driver.internal.util.Futures; -import org.neo4j.driver.AccessMode; -import org.neo4j.driver.Logging; import static java.lang.System.lineSeparator; -class LeakLoggingNetworkSession extends NetworkSession +public class LeakLoggingNetworkSession extends NetworkSession { private final String stackTrace; - LeakLoggingNetworkSession( ConnectionProvider connectionProvider, RetryLogic retryLogic, String databaseName, AccessMode mode, + public LeakLoggingNetworkSession( ConnectionProvider connectionProvider, RetryLogic retryLogic, String databaseName, AccessMode mode, BookmarksHolder bookmarksHolder, Logging logging ) { super( connectionProvider, retryLogic, databaseName, mode, bookmarksHolder, logging ); diff --git a/driver/src/main/java/org/neo4j/driver/internal/NetworkSession.java b/driver/src/main/java/org/neo4j/driver/internal/async/NetworkSession.java similarity index 51% rename from driver/src/main/java/org/neo4j/driver/internal/NetworkSession.java rename to driver/src/main/java/org/neo4j/driver/internal/async/NetworkSession.java index 6977b357ff..09c21d99e2 100644 --- a/driver/src/main/java/org/neo4j/driver/internal/NetworkSession.java +++ b/driver/src/main/java/org/neo4j/driver/internal/async/NetworkSession.java @@ -16,10 +16,8 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package org.neo4j.driver.internal; +package org.neo4j.driver.internal.async; -import java.util.Map; -import java.util.concurrent.CompletableFuture; import java.util.concurrent.CompletionException; import java.util.concurrent.CompletionStage; import java.util.concurrent.atomic.AtomicBoolean; @@ -27,32 +25,25 @@ import org.neo4j.driver.AccessMode; import org.neo4j.driver.Logger; import org.neo4j.driver.Logging; -import org.neo4j.driver.Session; import org.neo4j.driver.Statement; -import org.neo4j.driver.StatementResult; -import org.neo4j.driver.Transaction; import org.neo4j.driver.TransactionConfig; -import org.neo4j.driver.TransactionWork; -import org.neo4j.driver.async.AsyncSession; -import org.neo4j.driver.async.AsyncTransaction; -import org.neo4j.driver.async.AsyncTransactionWork; import org.neo4j.driver.async.StatementResultCursor; import org.neo4j.driver.exceptions.ClientException; +import org.neo4j.driver.internal.BookmarksHolder; +import org.neo4j.driver.internal.FailableCursor; +import org.neo4j.driver.internal.cursor.InternalStatementResultCursor; +import org.neo4j.driver.internal.cursor.RxStatementResultCursor; +import org.neo4j.driver.internal.cursor.StatementResultCursorFactory; import org.neo4j.driver.internal.logging.PrefixedLogger; -import org.neo4j.driver.internal.reactive.cursor.InternalStatementResultCursor; -import org.neo4j.driver.internal.reactive.cursor.RxStatementResultCursor; -import org.neo4j.driver.internal.reactive.cursor.StatementResultCursorFactory; import org.neo4j.driver.internal.retry.RetryLogic; import org.neo4j.driver.internal.spi.Connection; import org.neo4j.driver.internal.spi.ConnectionProvider; import org.neo4j.driver.internal.util.Futures; -import static java.util.Collections.emptyMap; import static java.util.concurrent.CompletableFuture.completedFuture; import static org.neo4j.driver.internal.util.Futures.completedWithNull; -import static org.neo4j.driver.internal.util.Futures.failedFuture; -public class NetworkSession extends AbstractStatementRunner implements Session, AsyncSession +public class NetworkSession { private static final String LOG_NAME = "Session"; @@ -69,8 +60,8 @@ public class NetworkSession extends AbstractStatementRunner implements Session, private final AtomicBoolean open = new AtomicBoolean( true ); - public NetworkSession( ConnectionProvider connectionProvider, RetryLogic retryLogic, String databaseName, AccessMode mode, BookmarksHolder bookmarksHolder, - Logging logging ) + public NetworkSession( ConnectionProvider connectionProvider, RetryLogic retryLogic, String databaseName, AccessMode mode, + BookmarksHolder bookmarksHolder, Logging logging ) { this.connectionProvider = connectionProvider; this.mode = mode; @@ -80,195 +71,59 @@ public NetworkSession( ConnectionProvider connectionProvider, RetryLogic retryLo this.databaseName = databaseName; } - @Override - public StatementResult run( Statement statement ) + public CompletionStage runAsync( Statement statement, TransactionConfig config, boolean waitForRunResponse ) { - return run( statement, TransactionConfig.empty() ); - } - - @Override - public StatementResult run( String statement, TransactionConfig config ) - { - return run( statement, emptyMap(), config ); - } - - @Override - public StatementResult run( String statement, Map parameters, TransactionConfig config ) - { - return run( new Statement( statement, parameters ), config ); - } - - @Override - public StatementResult run( Statement statement, TransactionConfig config ) - { - StatementResultCursor cursor = Futures.blockingGet( run( statement, config, false ), - () -> terminateConnectionOnThreadInterrupt( "Thread interrupted while running query in session" ) ); - - // query executed, it is safe to obtain a connection in a blocking way - Connection connection = Futures.getNow( connectionStage ); - return new InternalStatementResult( connection, cursor ); - } - - @Override - public CompletionStage runAsync( Statement statement ) - { - return runAsync( statement, TransactionConfig.empty() ); - } + CompletionStage newResultCursorStage = + buildResultCursorFactory( statement, config, waitForRunResponse ).thenCompose( StatementResultCursorFactory::asyncResult ); - @Override - public CompletionStage runAsync( String statement, TransactionConfig config ) - { - return runAsync( statement, emptyMap(), config ); + resultCursorStage = newResultCursorStage.exceptionally( error -> null ); + return newResultCursorStage.thenApply( cursor -> cursor ); // convert the return type } - @Override - public CompletionStage runAsync( String statement, Map parameters, TransactionConfig config ) + public CompletionStage runRx( Statement statement, TransactionConfig config ) { - return runAsync( new Statement( statement, parameters ), config ); - } + CompletionStage newResultCursorStage = + buildResultCursorFactory( statement, config, true ).thenCompose( StatementResultCursorFactory::rxResult ); - @Override - public CompletionStage runAsync( Statement statement, TransactionConfig config ) - { - //noinspection unchecked - return (CompletionStage) run( statement, config, true ); + resultCursorStage = newResultCursorStage.exceptionally( error -> null ); + return newResultCursorStage; } - @Override - public boolean isOpen() + public CompletionStage beginTransactionAsync( TransactionConfig config ) { - return open.get(); + return this.beginTransactionAsync( mode, config ); } - @Override - public void close() + public CompletionStage beginTransactionAsync( AccessMode mode, TransactionConfig config ) { - Futures.blockingGet( closeAsync(), - () -> terminateConnectionOnThreadInterrupt( "Thread interrupted while closing the session" ) ); - } + ensureSessionIsOpen(); - @Override - public CompletionStage closeAsync() - { - if ( open.compareAndSet( true, false ) ) - { - return resultCursorStage.thenCompose( cursor -> - { - if ( cursor != null ) - { - // there exists a cursor with potentially unconsumed error, try to extract and propagate it - return cursor.failureAsync(); - } - // no result cursor exists so no error exists - return completedWithNull(); - } ).thenCompose( cursorError -> closeTransactionAndReleaseConnection().thenApply( txCloseError -> - { - // now we have cursor error, active transaction has been closed and connection has been released - // back to the pool; try to propagate cursor and transaction close errors, if any - CompletionException combinedError = Futures.combineErrors( cursorError, txCloseError ); - if ( combinedError != null ) + // create a chain that acquires connection and starts a transaction + CompletionStage newTransactionStage = ensureNoOpenTxBeforeStartingTx() + .thenCompose( ignore -> acquireConnection( databaseName, mode ) ) + .thenCompose( connection -> { - throw combinedError; - } - return null; - } ) ); - } - return completedWithNull(); - } - - @Override - public Transaction beginTransaction() - { - return beginTransaction( TransactionConfig.empty() ); - } - - @Override - public Transaction beginTransaction( TransactionConfig config ) - { - return beginTransaction( mode, config ); - } - - @Deprecated - @Override - public Transaction beginTransaction( String bookmark ) - { - bookmarksHolder.setBookmarks( Bookmarks.from( bookmark ) ); - return beginTransaction(); - } - - @Override - public CompletionStage beginTransactionAsync() - { - return beginTransactionAsync( TransactionConfig.empty() ); - } - - @Override - public CompletionStage beginTransactionAsync( TransactionConfig config ) - { - //noinspection unchecked - return (CompletionStage) beginTransactionAsync( mode, config ); - } - - @Override - public T readTransaction( TransactionWork work ) - { - return readTransaction( work, TransactionConfig.empty() ); - } - - @Override - public T readTransaction( TransactionWork work, TransactionConfig config ) - { - return transaction( AccessMode.READ, work, config ); - } - - @Override - public CompletionStage readTransactionAsync( AsyncTransactionWork> work ) - { - return readTransactionAsync( work, TransactionConfig.empty() ); - } - - @Override - public CompletionStage readTransactionAsync( AsyncTransactionWork> work, TransactionConfig config ) - { - return transactionAsync( AccessMode.READ, work, config ); - } - - @Override - public T writeTransaction( TransactionWork work ) - { - return writeTransaction( work, TransactionConfig.empty() ); - } - - @Override - public T writeTransaction( TransactionWork work, TransactionConfig config ) - { - return transaction( AccessMode.WRITE, work, config ); - } - - @Override - public CompletionStage writeTransactionAsync( AsyncTransactionWork> work ) - { - return writeTransactionAsync( work, TransactionConfig.empty() ); - } + ExplicitTransaction tx = new ExplicitTransaction( connection, bookmarksHolder ); + return tx.beginAsync( bookmarksHolder.getBookmarks(), config ); + } ); - @Override - public CompletionStage writeTransactionAsync( AsyncTransactionWork> work, TransactionConfig config ) - { - return transactionAsync( AccessMode.WRITE, work, config ); - } + // update the reference to the only known transaction + CompletionStage currentTransactionStage = transactionStage; - @Override - public String lastBookmark() - { - return bookmarksHolder.lastBookmark(); - } + transactionStage = newTransactionStage + .exceptionally( error -> null ) // ignore errors from starting new transaction + .thenCompose( tx -> + { + if ( tx == null ) + { + // failed to begin new transaction, keep reference to the existing one + return currentTransactionStage; + } + // new transaction started, keep reference to it + return completedFuture( tx ); + } ); - @Override - @SuppressWarnings( "deprecation" ) - public void reset() - { - Futures.blockingGet( resetAsync(), - () -> terminateConnectionOnThreadInterrupt( "Thread interrupted while resetting the session" ) ); + return newTransactionStage; } public CompletionStage resetAsync() @@ -293,163 +148,74 @@ public CompletionStage resetAsync() } ); } - CompletionStage currentConnectionIsOpen() - { - return connectionStage.handle( ( connection, error ) -> - error == null && // no acquisition error - connection != null && // some connection has actually been acquired - connection.isOpen() ); // and it's still open - } - - private T transaction( AccessMode mode, TransactionWork work, TransactionConfig config ) + public RetryLogic retryLogic() { - // 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 - return retryLogic.retry( () -> - { - try ( Transaction tx = beginTransaction( mode, config ) ) - { - try - { - T result = work.execute( tx ); - tx.success(); - return result; - } - catch ( Throwable t ) - { - // mark transaction for failure if the given unit of work threw exception - // this will override any success marks that were made by the unit of work - tx.failure(); - throw t; - } - } - } ); + return retryLogic; } - private CompletionStage transactionAsync( AccessMode mode, AsyncTransactionWork> work, TransactionConfig config ) + public String lastBookmark() { - return retryLogic.retryAsync( () -> - { - CompletableFuture resultFuture = new CompletableFuture<>(); - CompletionStage txFuture = beginTransactionAsync( mode, config ); - - txFuture.whenComplete( ( tx, completionError ) -> - { - Throwable error = Futures.completionExceptionCause( completionError ); - if ( error != null ) - { - resultFuture.completeExceptionally( error ); - } - else - { - executeWork( resultFuture, tx, work ); - } - } ); - - return resultFuture; - } ); + return bookmarksHolder.lastBookmark(); } - private void executeWork( CompletableFuture resultFuture, ExplicitTransaction tx, - AsyncTransactionWork> work ) + public CompletionStage releaseConnectionAsync() { - CompletionStage workFuture = safeExecuteWork( tx, work ); - workFuture.whenComplete( ( result, completionError ) -> + return connectionStage.thenCompose( connection -> { - Throwable error = Futures.completionExceptionCause( completionError ); - if ( error != null ) - { - rollbackTxAfterFailedTransactionWork( tx, resultFuture, error ); - } - else + if ( connection != null ) { - closeTxAfterSucceededTransactionWork( tx, resultFuture, result ); + // there exists connection, try to release it back to the pool + return connection.release(); } + // no connection so return null + return completedWithNull(); } ); } - private CompletionStage safeExecuteWork( ExplicitTransaction tx, AsyncTransactionWork> work ) + public CompletionStage connectionAsync() { - // given work might fail in both async and sync way - // async failure will result in a failed future being returned - // sync failure will result in an exception being thrown - try - { - CompletionStage result = work.execute( tx ); - - // protect from given transaction function returning null - return result == null ? completedWithNull() : result; - } - catch ( Throwable workError ) - { - // work threw an exception, wrap it in a future and proceed - return failedFuture( workError ); - } + return connectionStage; } - private void rollbackTxAfterFailedTransactionWork( ExplicitTransaction tx, CompletableFuture resultFuture, - Throwable error ) + public boolean isOpen() { - if ( tx.isOpen() ) - { - tx.rollbackAsync().whenComplete( ( ignore, rollbackError ) -> - { - if ( rollbackError != null ) - { - error.addSuppressed( rollbackError ); - } - resultFuture.completeExceptionally( error ); - } ); - } - else - { - resultFuture.completeExceptionally( error ); - } + return open.get(); } - private void closeTxAfterSucceededTransactionWork( ExplicitTransaction tx, CompletableFuture resultFuture, - T result ) + public CompletionStage closeAsync() { - if ( tx.isOpen() ) + if ( open.compareAndSet( true, false ) ) { - tx.success(); - tx.closeAsync().whenComplete( ( ignore, completionError ) -> + return resultCursorStage.thenCompose( cursor -> { - Throwable commitError = Futures.completionExceptionCause( completionError ); - if ( commitError != null ) + if ( cursor != null ) { - resultFuture.completeExceptionally( commitError ); + // there exists a cursor with potentially unconsumed error, try to extract and propagate it + return cursor.failureAsync(); } - else + // no result cursor exists so no error exists + return completedWithNull(); + } ).thenCompose( cursorError -> closeTransactionAndReleaseConnection().thenApply( txCloseError -> + { + // now we have cursor error, active transaction has been closed and connection has been released + // back to the pool; try to propagate cursor and transaction close errors, if any + CompletionException combinedError = Futures.combineErrors( cursorError, txCloseError ); + if ( combinedError != null ) { - resultFuture.complete( result ); + throw combinedError; } - } ); - } - else - { - resultFuture.complete( result ); + return null; + } ) ); } + return completedWithNull(); } - public CompletionStage runRx( Statement statement, TransactionConfig config ) - { - CompletionStage newResultCursorStage = - buildResultCursorFactory( statement, config, true ).thenCompose( StatementResultCursorFactory::rxResult ); - - resultCursorStage = newResultCursorStage.exceptionally( error -> null ); - return newResultCursorStage; - } - - private CompletionStage run( Statement statement, TransactionConfig config, boolean waitForRunResponse ) + protected CompletionStage currentConnectionIsOpen() { - CompletionStage newResultCursorStage = - buildResultCursorFactory( statement, config, waitForRunResponse ).thenCompose( StatementResultCursorFactory::asyncResult ); - - resultCursorStage = newResultCursorStage.exceptionally( error -> null ); - return newResultCursorStage; + return connectionStage.handle( ( connection, error ) -> + error == null && // no acquisition error + connection != null && // some connection has actually been acquired + connection.isOpen() ); // and it's still open } private CompletionStage buildResultCursorFactory( Statement statement, TransactionConfig config, boolean waitForRunResponse ) @@ -472,44 +238,6 @@ private CompletionStage buildResultCursorFactory( } ); } - private Transaction beginTransaction( AccessMode mode, TransactionConfig config ) - { - return Futures.blockingGet( beginTransactionAsync( mode, config ), - () -> terminateConnectionOnThreadInterrupt( "Thread interrupted while starting a transaction" ) ); - } - - private CompletionStage beginTransactionAsync( AccessMode mode, TransactionConfig config ) - { - ensureSessionIsOpen(); - - // create a chain that acquires connection and starts a transaction - CompletionStage newTransactionStage = ensureNoOpenTxBeforeStartingTx() - .thenCompose( ignore -> acquireConnection( databaseName, mode ) ) - .thenCompose( connection -> - { - ExplicitTransaction tx = new ExplicitTransaction( connection, bookmarksHolder ); - return tx.beginAsync( bookmarksHolder.getBookmarks(), config ); - } ); - - // update the reference to the only known transaction - CompletionStage currentTransactionStage = transactionStage; - - transactionStage = newTransactionStage - .exceptionally( error -> null ) // ignore errors from starting new transaction - .thenCompose( tx -> - { - if ( tx == null ) - { - // failed to begin new transaction, keep reference to the existing one - return currentTransactionStage; - } - // new transaction started, keep reference to it - return completedFuture( tx ); - } ); - - return newTransactionStage; - } - private CompletionStage acquireConnection( String databaseName, AccessMode mode ) { CompletionStage currentConnectionStage = connectionStage; @@ -569,40 +297,7 @@ private CompletionStage closeTransactionAndReleaseConnection() return completedWithNull(); } ).thenCompose( txCloseError -> // then release the connection and propagate transaction close error, if any - releaseConnection().thenApply( ignore -> txCloseError ) ); - } - - public CompletionStage releaseConnection() - { - return connectionStage.thenCompose( connection -> - { - if ( connection != null ) - { - // there exists connection, try to release it back to the pool - return connection.release(); - } - // no connection so return null - return completedWithNull(); - } ); - } - - private void terminateConnectionOnThreadInterrupt( String reason ) - { - // try to get current connection if it has been acquired - Connection connection = null; - try - { - connection = Futures.getNow( connectionStage ); - } - catch ( Throwable ignore ) - { - // ignore errors because handing interruptions is best effort - } - - if ( connection != null ) - { - connection.terminateAndRelease( reason ); - } + releaseConnectionAsync().thenApply( ignore -> txCloseError ) ); } private CompletionStage ensureNoOpenTxBeforeRunningQuery() diff --git a/driver/src/main/java/org/neo4j/driver/internal/async/ResultCursorsHolder.java b/driver/src/main/java/org/neo4j/driver/internal/async/ResultCursorsHolder.java index 2353b13013..319f576fac 100644 --- a/driver/src/main/java/org/neo4j/driver/internal/async/ResultCursorsHolder.java +++ b/driver/src/main/java/org/neo4j/driver/internal/async/ResultCursorsHolder.java @@ -38,7 +38,7 @@ public void add( CompletionStage cursorStage ) cursorStages.add( cursorStage ); } - public CompletionStage retrieveNotConsumedError() + CompletionStage retrieveNotConsumedError() { CompletableFuture[] failures = retrieveAllFailures(); diff --git a/driver/src/main/java/org/neo4j/driver/internal/async/BoltProtocolUtil.java b/driver/src/main/java/org/neo4j/driver/internal/async/connection/BoltProtocolUtil.java similarity index 98% rename from driver/src/main/java/org/neo4j/driver/internal/async/BoltProtocolUtil.java rename to driver/src/main/java/org/neo4j/driver/internal/async/connection/BoltProtocolUtil.java index 1e9001b37a..8602180966 100644 --- a/driver/src/main/java/org/neo4j/driver/internal/async/BoltProtocolUtil.java +++ b/driver/src/main/java/org/neo4j/driver/internal/async/connection/BoltProtocolUtil.java @@ -16,7 +16,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package org.neo4j.driver.internal.async; +package org.neo4j.driver.internal.async.connection; import io.netty.buffer.ByteBuf; diff --git a/driver/src/main/java/org/neo4j/driver/internal/async/BootstrapFactory.java b/driver/src/main/java/org/neo4j/driver/internal/async/connection/BootstrapFactory.java similarity index 96% rename from driver/src/main/java/org/neo4j/driver/internal/async/BootstrapFactory.java rename to driver/src/main/java/org/neo4j/driver/internal/async/connection/BootstrapFactory.java index 73d355d222..3262fba331 100644 --- a/driver/src/main/java/org/neo4j/driver/internal/async/BootstrapFactory.java +++ b/driver/src/main/java/org/neo4j/driver/internal/async/connection/BootstrapFactory.java @@ -16,7 +16,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package org.neo4j.driver.internal.async; +package org.neo4j.driver.internal.async.connection; import io.netty.bootstrap.Bootstrap; import io.netty.channel.ChannelOption; diff --git a/driver/src/main/java/org/neo4j/driver/internal/async/ChannelAttributes.java b/driver/src/main/java/org/neo4j/driver/internal/async/connection/ChannelAttributes.java similarity index 98% rename from driver/src/main/java/org/neo4j/driver/internal/async/ChannelAttributes.java rename to driver/src/main/java/org/neo4j/driver/internal/async/connection/ChannelAttributes.java index 628c99d57f..784f82b645 100644 --- a/driver/src/main/java/org/neo4j/driver/internal/async/ChannelAttributes.java +++ b/driver/src/main/java/org/neo4j/driver/internal/async/connection/ChannelAttributes.java @@ -16,7 +16,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package org.neo4j.driver.internal.async; +package org.neo4j.driver.internal.async.connection; import io.netty.channel.Channel; import io.netty.util.AttributeKey; diff --git a/driver/src/main/java/org/neo4j/driver/internal/async/ChannelConnectedListener.java b/driver/src/main/java/org/neo4j/driver/internal/async/connection/ChannelConnectedListener.java similarity index 92% rename from driver/src/main/java/org/neo4j/driver/internal/async/ChannelConnectedListener.java rename to driver/src/main/java/org/neo4j/driver/internal/async/connection/ChannelConnectedListener.java index 164bd06f88..3a11804401 100644 --- a/driver/src/main/java/org/neo4j/driver/internal/async/ChannelConnectedListener.java +++ b/driver/src/main/java/org/neo4j/driver/internal/async/connection/ChannelConnectedListener.java @@ -16,7 +16,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package org.neo4j.driver.internal.async; +package org.neo4j.driver.internal.async.connection; import io.netty.channel.Channel; import io.netty.channel.ChannelFuture; @@ -31,8 +31,8 @@ import org.neo4j.driver.exceptions.ServiceUnavailableException; import static java.lang.String.format; -import static org.neo4j.driver.internal.async.BoltProtocolUtil.handshakeBuf; -import static org.neo4j.driver.internal.async.BoltProtocolUtil.handshakeString; +import static org.neo4j.driver.internal.async.connection.BoltProtocolUtil.handshakeBuf; +import static org.neo4j.driver.internal.async.connection.BoltProtocolUtil.handshakeString; public class ChannelConnectedListener implements ChannelFutureListener { diff --git a/driver/src/main/java/org/neo4j/driver/internal/async/ChannelConnector.java b/driver/src/main/java/org/neo4j/driver/internal/async/connection/ChannelConnector.java similarity index 94% rename from driver/src/main/java/org/neo4j/driver/internal/async/ChannelConnector.java rename to driver/src/main/java/org/neo4j/driver/internal/async/connection/ChannelConnector.java index aaff221c92..6ebf0b42d3 100644 --- a/driver/src/main/java/org/neo4j/driver/internal/async/ChannelConnector.java +++ b/driver/src/main/java/org/neo4j/driver/internal/async/connection/ChannelConnector.java @@ -16,7 +16,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package org.neo4j.driver.internal.async; +package org.neo4j.driver.internal.async.connection; import io.netty.bootstrap.Bootstrap; import io.netty.channel.ChannelFuture; diff --git a/driver/src/main/java/org/neo4j/driver/internal/async/ChannelConnectorImpl.java b/driver/src/main/java/org/neo4j/driver/internal/async/connection/ChannelConnectorImpl.java similarity index 99% rename from driver/src/main/java/org/neo4j/driver/internal/async/ChannelConnectorImpl.java rename to driver/src/main/java/org/neo4j/driver/internal/async/connection/ChannelConnectorImpl.java index cbd257991c..76bde81a0b 100644 --- a/driver/src/main/java/org/neo4j/driver/internal/async/ChannelConnectorImpl.java +++ b/driver/src/main/java/org/neo4j/driver/internal/async/connection/ChannelConnectorImpl.java @@ -16,7 +16,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package org.neo4j.driver.internal.async; +package org.neo4j.driver.internal.async.connection; import io.netty.bootstrap.Bootstrap; import io.netty.channel.Channel; diff --git a/driver/src/main/java/org/neo4j/driver/internal/async/ChannelPipelineBuilder.java b/driver/src/main/java/org/neo4j/driver/internal/async/connection/ChannelPipelineBuilder.java similarity index 94% rename from driver/src/main/java/org/neo4j/driver/internal/async/ChannelPipelineBuilder.java rename to driver/src/main/java/org/neo4j/driver/internal/async/connection/ChannelPipelineBuilder.java index 4fb537e678..9c7c2d9726 100644 --- a/driver/src/main/java/org/neo4j/driver/internal/async/ChannelPipelineBuilder.java +++ b/driver/src/main/java/org/neo4j/driver/internal/async/connection/ChannelPipelineBuilder.java @@ -16,7 +16,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package org.neo4j.driver.internal.async; +package org.neo4j.driver.internal.async.connection; import io.netty.channel.ChannelPipeline; diff --git a/driver/src/main/java/org/neo4j/driver/internal/async/ChannelPipelineBuilderImpl.java b/driver/src/main/java/org/neo4j/driver/internal/async/connection/ChannelPipelineBuilderImpl.java similarity index 97% rename from driver/src/main/java/org/neo4j/driver/internal/async/ChannelPipelineBuilderImpl.java rename to driver/src/main/java/org/neo4j/driver/internal/async/connection/ChannelPipelineBuilderImpl.java index ea7149e0a0..98535fdbef 100644 --- a/driver/src/main/java/org/neo4j/driver/internal/async/ChannelPipelineBuilderImpl.java +++ b/driver/src/main/java/org/neo4j/driver/internal/async/connection/ChannelPipelineBuilderImpl.java @@ -16,7 +16,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package org.neo4j.driver.internal.async; +package org.neo4j.driver.internal.async.connection; import io.netty.channel.ChannelPipeline; diff --git a/driver/src/main/java/org/neo4j/driver/internal/async/DecoratedConnection.java b/driver/src/main/java/org/neo4j/driver/internal/async/connection/DecoratedConnection.java similarity index 98% rename from driver/src/main/java/org/neo4j/driver/internal/async/DecoratedConnection.java rename to driver/src/main/java/org/neo4j/driver/internal/async/connection/DecoratedConnection.java index 5b52915795..29cacaed80 100644 --- a/driver/src/main/java/org/neo4j/driver/internal/async/DecoratedConnection.java +++ b/driver/src/main/java/org/neo4j/driver/internal/async/connection/DecoratedConnection.java @@ -16,7 +16,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package org.neo4j.driver.internal.async; +package org.neo4j.driver.internal.async.connection; import java.util.concurrent.CompletionStage; diff --git a/driver/src/main/java/org/neo4j/driver/internal/async/DirectConnection.java b/driver/src/main/java/org/neo4j/driver/internal/async/connection/DirectConnection.java similarity index 98% rename from driver/src/main/java/org/neo4j/driver/internal/async/DirectConnection.java rename to driver/src/main/java/org/neo4j/driver/internal/async/connection/DirectConnection.java index b50c5a1338..70820796d0 100644 --- a/driver/src/main/java/org/neo4j/driver/internal/async/DirectConnection.java +++ b/driver/src/main/java/org/neo4j/driver/internal/async/connection/DirectConnection.java @@ -16,7 +16,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package org.neo4j.driver.internal.async; +package org.neo4j.driver.internal.async.connection; import io.netty.channel.Channel; import io.netty.channel.pool.ChannelPool; @@ -40,7 +40,7 @@ import org.neo4j.driver.internal.util.ServerVersion; import static java.util.Collections.emptyMap; -import static org.neo4j.driver.internal.async.ChannelAttributes.setTerminationReason; +import static org.neo4j.driver.internal.async.connection.ChannelAttributes.setTerminationReason; public class DirectConnection implements Connection { diff --git a/driver/src/main/java/org/neo4j/driver/internal/async/EventLoopGroupFactory.java b/driver/src/main/java/org/neo4j/driver/internal/async/connection/EventLoopGroupFactory.java similarity index 99% rename from driver/src/main/java/org/neo4j/driver/internal/async/EventLoopGroupFactory.java rename to driver/src/main/java/org/neo4j/driver/internal/async/connection/EventLoopGroupFactory.java index c4fb71531e..2592f10ed3 100644 --- a/driver/src/main/java/org/neo4j/driver/internal/async/EventLoopGroupFactory.java +++ b/driver/src/main/java/org/neo4j/driver/internal/async/connection/EventLoopGroupFactory.java @@ -16,7 +16,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package org.neo4j.driver.internal.async; +package org.neo4j.driver.internal.async.connection; import io.netty.bootstrap.Bootstrap; import io.netty.channel.Channel; diff --git a/driver/src/main/java/org/neo4j/driver/internal/async/HandshakeCompletedListener.java b/driver/src/main/java/org/neo4j/driver/internal/async/connection/HandshakeCompletedListener.java similarity index 97% rename from driver/src/main/java/org/neo4j/driver/internal/async/HandshakeCompletedListener.java rename to driver/src/main/java/org/neo4j/driver/internal/async/connection/HandshakeCompletedListener.java index 72a14a90f9..a66c603b11 100644 --- a/driver/src/main/java/org/neo4j/driver/internal/async/HandshakeCompletedListener.java +++ b/driver/src/main/java/org/neo4j/driver/internal/async/connection/HandshakeCompletedListener.java @@ -16,7 +16,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package org.neo4j.driver.internal.async; +package org.neo4j.driver.internal.async.connection; import io.netty.channel.ChannelFuture; import io.netty.channel.ChannelFutureListener; diff --git a/driver/src/main/java/org/neo4j/driver/internal/async/HandshakeHandler.java b/driver/src/main/java/org/neo4j/driver/internal/async/connection/HandshakeHandler.java similarity index 96% rename from driver/src/main/java/org/neo4j/driver/internal/async/HandshakeHandler.java rename to driver/src/main/java/org/neo4j/driver/internal/async/connection/HandshakeHandler.java index 637a0fe55d..cd1af387c7 100644 --- a/driver/src/main/java/org/neo4j/driver/internal/async/HandshakeHandler.java +++ b/driver/src/main/java/org/neo4j/driver/internal/async/connection/HandshakeHandler.java @@ -16,7 +16,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package org.neo4j.driver.internal.async; +package org.neo4j.driver.internal.async.connection; import io.netty.buffer.ByteBuf; import io.netty.channel.ChannelHandlerContext; @@ -37,8 +37,8 @@ import org.neo4j.driver.exceptions.SecurityException; import org.neo4j.driver.exceptions.ServiceUnavailableException; -import static org.neo4j.driver.internal.async.BoltProtocolUtil.HTTP; -import static org.neo4j.driver.internal.async.BoltProtocolUtil.NO_PROTOCOL_VERSION; +import static org.neo4j.driver.internal.async.connection.BoltProtocolUtil.HTTP; +import static org.neo4j.driver.internal.async.connection.BoltProtocolUtil.NO_PROTOCOL_VERSION; public class HandshakeHandler extends ReplayingDecoder { diff --git a/driver/src/main/java/org/neo4j/driver/internal/async/NettyChannelInitializer.java b/driver/src/main/java/org/neo4j/driver/internal/async/connection/NettyChannelInitializer.java similarity index 90% rename from driver/src/main/java/org/neo4j/driver/internal/async/NettyChannelInitializer.java rename to driver/src/main/java/org/neo4j/driver/internal/async/connection/NettyChannelInitializer.java index 859d3f5c9e..74ec34be5a 100644 --- a/driver/src/main/java/org/neo4j/driver/internal/async/NettyChannelInitializer.java +++ b/driver/src/main/java/org/neo4j/driver/internal/async/connection/NettyChannelInitializer.java @@ -16,7 +16,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package org.neo4j.driver.internal.async; +package org.neo4j.driver.internal.async.connection; import io.netty.channel.Channel; import io.netty.channel.ChannelInitializer; @@ -32,9 +32,9 @@ import org.neo4j.driver.internal.util.Clock; import org.neo4j.driver.Logging; -import static org.neo4j.driver.internal.async.ChannelAttributes.setCreationTimestamp; -import static org.neo4j.driver.internal.async.ChannelAttributes.setMessageDispatcher; -import static org.neo4j.driver.internal.async.ChannelAttributes.setServerAddress; +import static org.neo4j.driver.internal.async.connection.ChannelAttributes.setCreationTimestamp; +import static org.neo4j.driver.internal.async.connection.ChannelAttributes.setMessageDispatcher; +import static org.neo4j.driver.internal.async.connection.ChannelAttributes.setServerAddress; public class NettyChannelInitializer extends ChannelInitializer { diff --git a/driver/src/main/java/org/neo4j/driver/internal/async/RoutingConnection.java b/driver/src/main/java/org/neo4j/driver/internal/async/connection/RoutingConnection.java similarity index 98% rename from driver/src/main/java/org/neo4j/driver/internal/async/RoutingConnection.java rename to driver/src/main/java/org/neo4j/driver/internal/async/connection/RoutingConnection.java index a6531629a5..16528529a3 100644 --- a/driver/src/main/java/org/neo4j/driver/internal/async/RoutingConnection.java +++ b/driver/src/main/java/org/neo4j/driver/internal/async/connection/RoutingConnection.java @@ -16,7 +16,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package org.neo4j.driver.internal.async; +package org.neo4j.driver.internal.async.connection; import java.util.concurrent.CompletionStage; diff --git a/driver/src/main/java/org/neo4j/driver/internal/async/inbound/ChannelErrorHandler.java b/driver/src/main/java/org/neo4j/driver/internal/async/inbound/ChannelErrorHandler.java index 3f1fc146c6..c80345df4d 100644 --- a/driver/src/main/java/org/neo4j/driver/internal/async/inbound/ChannelErrorHandler.java +++ b/driver/src/main/java/org/neo4j/driver/internal/async/inbound/ChannelErrorHandler.java @@ -31,8 +31,8 @@ import org.neo4j.driver.exceptions.ServiceUnavailableException; import static java.util.Objects.requireNonNull; -import static org.neo4j.driver.internal.async.ChannelAttributes.messageDispatcher; -import static org.neo4j.driver.internal.async.ChannelAttributes.terminationReason; +import static org.neo4j.driver.internal.async.connection.ChannelAttributes.messageDispatcher; +import static org.neo4j.driver.internal.async.connection.ChannelAttributes.terminationReason; public class ChannelErrorHandler extends ChannelInboundHandlerAdapter { diff --git a/driver/src/main/java/org/neo4j/driver/internal/async/inbound/InboundMessageHandler.java b/driver/src/main/java/org/neo4j/driver/internal/async/inbound/InboundMessageHandler.java index 842e4373d3..5bd30ae175 100644 --- a/driver/src/main/java/org/neo4j/driver/internal/async/inbound/InboundMessageHandler.java +++ b/driver/src/main/java/org/neo4j/driver/internal/async/inbound/InboundMessageHandler.java @@ -30,7 +30,7 @@ import static io.netty.buffer.ByteBufUtil.hexDump; import static java.util.Objects.requireNonNull; -import static org.neo4j.driver.internal.async.ChannelAttributes.messageDispatcher; +import static org.neo4j.driver.internal.async.connection.ChannelAttributes.messageDispatcher; public class InboundMessageHandler extends SimpleChannelInboundHandler { diff --git a/driver/src/main/java/org/neo4j/driver/internal/async/outbound/ChunkAwareByteBufOutput.java b/driver/src/main/java/org/neo4j/driver/internal/async/outbound/ChunkAwareByteBufOutput.java index 79d31cb6be..d65d944ce1 100644 --- a/driver/src/main/java/org/neo4j/driver/internal/async/outbound/ChunkAwareByteBufOutput.java +++ b/driver/src/main/java/org/neo4j/driver/internal/async/outbound/ChunkAwareByteBufOutput.java @@ -20,12 +20,12 @@ import io.netty.buffer.ByteBuf; -import org.neo4j.driver.internal.async.BoltProtocolUtil; +import org.neo4j.driver.internal.async.connection.BoltProtocolUtil; import org.neo4j.driver.internal.packstream.PackOutput; import static java.util.Objects.requireNonNull; -import static org.neo4j.driver.internal.async.BoltProtocolUtil.CHUNK_HEADER_SIZE_BYTES; -import static org.neo4j.driver.internal.async.BoltProtocolUtil.DEFAULT_MAX_OUTBOUND_CHUNK_SIZE_BYTES; +import static org.neo4j.driver.internal.async.connection.BoltProtocolUtil.CHUNK_HEADER_SIZE_BYTES; +import static org.neo4j.driver.internal.async.connection.BoltProtocolUtil.DEFAULT_MAX_OUTBOUND_CHUNK_SIZE_BYTES; public class ChunkAwareByteBufOutput implements PackOutput { diff --git a/driver/src/main/java/org/neo4j/driver/internal/async/outbound/OutboundMessageHandler.java b/driver/src/main/java/org/neo4j/driver/internal/async/outbound/OutboundMessageHandler.java index 44b1e70436..97c189fd6f 100644 --- a/driver/src/main/java/org/neo4j/driver/internal/async/outbound/OutboundMessageHandler.java +++ b/driver/src/main/java/org/neo4j/driver/internal/async/outbound/OutboundMessageHandler.java @@ -25,7 +25,7 @@ import java.util.List; -import org.neo4j.driver.internal.async.BoltProtocolUtil; +import org.neo4j.driver.internal.async.connection.BoltProtocolUtil; import org.neo4j.driver.internal.logging.ChannelActivityLogger; import org.neo4j.driver.internal.messaging.Message; import org.neo4j.driver.internal.messaging.MessageFormat; diff --git a/driver/src/main/java/org/neo4j/driver/internal/async/pool/ConnectionPoolImpl.java b/driver/src/main/java/org/neo4j/driver/internal/async/pool/ConnectionPoolImpl.java index 709c348c6e..8a1dd9be72 100644 --- a/driver/src/main/java/org/neo4j/driver/internal/async/pool/ConnectionPoolImpl.java +++ b/driver/src/main/java/org/neo4j/driver/internal/async/pool/ConnectionPoolImpl.java @@ -34,8 +34,8 @@ import java.util.concurrent.atomic.AtomicBoolean; import org.neo4j.driver.internal.BoltServerAddress; -import org.neo4j.driver.internal.async.ChannelConnector; -import org.neo4j.driver.internal.async.DirectConnection; +import org.neo4j.driver.internal.async.connection.ChannelConnector; +import org.neo4j.driver.internal.async.connection.DirectConnection; import org.neo4j.driver.internal.metrics.ListenerEvent; import org.neo4j.driver.internal.metrics.MetricsListener; import org.neo4j.driver.internal.spi.Connection; diff --git a/driver/src/main/java/org/neo4j/driver/internal/async/pool/NettyChannelHealthChecker.java b/driver/src/main/java/org/neo4j/driver/internal/async/pool/NettyChannelHealthChecker.java index 2d18fd998a..12fb6a465b 100644 --- a/driver/src/main/java/org/neo4j/driver/internal/async/pool/NettyChannelHealthChecker.java +++ b/driver/src/main/java/org/neo4j/driver/internal/async/pool/NettyChannelHealthChecker.java @@ -29,9 +29,9 @@ import org.neo4j.driver.Logger; import org.neo4j.driver.Logging; -import static org.neo4j.driver.internal.async.ChannelAttributes.creationTimestamp; -import static org.neo4j.driver.internal.async.ChannelAttributes.lastUsedTimestamp; -import static org.neo4j.driver.internal.async.ChannelAttributes.messageDispatcher; +import static org.neo4j.driver.internal.async.connection.ChannelAttributes.creationTimestamp; +import static org.neo4j.driver.internal.async.connection.ChannelAttributes.lastUsedTimestamp; +import static org.neo4j.driver.internal.async.connection.ChannelAttributes.messageDispatcher; public class NettyChannelHealthChecker implements ChannelHealthChecker { diff --git a/driver/src/main/java/org/neo4j/driver/internal/async/pool/NettyChannelPool.java b/driver/src/main/java/org/neo4j/driver/internal/async/pool/NettyChannelPool.java index 8ca45ff774..8168c09fda 100644 --- a/driver/src/main/java/org/neo4j/driver/internal/async/pool/NettyChannelPool.java +++ b/driver/src/main/java/org/neo4j/driver/internal/async/pool/NettyChannelPool.java @@ -25,7 +25,7 @@ import io.netty.channel.pool.FixedChannelPool; import org.neo4j.driver.internal.BoltServerAddress; -import org.neo4j.driver.internal.async.ChannelConnector; +import org.neo4j.driver.internal.async.connection.ChannelConnector; import org.neo4j.driver.internal.metrics.ListenerEvent; import static java.util.Objects.requireNonNull; diff --git a/driver/src/main/java/org/neo4j/driver/internal/async/pool/NettyChannelTracker.java b/driver/src/main/java/org/neo4j/driver/internal/async/pool/NettyChannelTracker.java index d0f789dccc..949b604965 100644 --- a/driver/src/main/java/org/neo4j/driver/internal/async/pool/NettyChannelTracker.java +++ b/driver/src/main/java/org/neo4j/driver/internal/async/pool/NettyChannelTracker.java @@ -36,7 +36,7 @@ import org.neo4j.driver.Logger; import org.neo4j.driver.Logging; -import static org.neo4j.driver.internal.async.ChannelAttributes.serverAddress; +import static org.neo4j.driver.internal.async.connection.ChannelAttributes.serverAddress; public class NettyChannelTracker implements ChannelPoolHandler { diff --git a/driver/src/main/java/org/neo4j/driver/internal/cluster/RoutingProcedureRunner.java b/driver/src/main/java/org/neo4j/driver/internal/cluster/RoutingProcedureRunner.java index 4b86a751d0..db688e9918 100644 --- a/driver/src/main/java/org/neo4j/driver/internal/cluster/RoutingProcedureRunner.java +++ b/driver/src/main/java/org/neo4j/driver/internal/cluster/RoutingProcedureRunner.java @@ -24,7 +24,7 @@ import org.neo4j.driver.AccessMode; import org.neo4j.driver.internal.BookmarksHolder; -import org.neo4j.driver.internal.async.DecoratedConnection; +import org.neo4j.driver.internal.async.connection.DecoratedConnection; import org.neo4j.driver.internal.spi.Connection; import org.neo4j.driver.internal.util.Futures; import org.neo4j.driver.internal.util.ServerVersion; diff --git a/driver/src/main/java/org/neo4j/driver/internal/cluster/loadbalancing/LoadBalancer.java b/driver/src/main/java/org/neo4j/driver/internal/cluster/loadbalancing/LoadBalancer.java index 466679f052..93adf8fd6b 100644 --- a/driver/src/main/java/org/neo4j/driver/internal/cluster/loadbalancing/LoadBalancer.java +++ b/driver/src/main/java/org/neo4j/driver/internal/cluster/loadbalancing/LoadBalancer.java @@ -25,8 +25,8 @@ import org.neo4j.driver.internal.BoltServerAddress; import org.neo4j.driver.internal.RoutingErrorHandler; -import org.neo4j.driver.internal.async.DecoratedConnection; -import org.neo4j.driver.internal.async.RoutingConnection; +import org.neo4j.driver.internal.async.connection.DecoratedConnection; +import org.neo4j.driver.internal.async.connection.RoutingConnection; import org.neo4j.driver.internal.cluster.AddressSet; import org.neo4j.driver.internal.cluster.ClusterComposition; import org.neo4j.driver.internal.cluster.ClusterCompositionProvider; diff --git a/driver/src/main/java/org/neo4j/driver/internal/reactive/cursor/AsyncResultCursorOnlyFactory.java b/driver/src/main/java/org/neo4j/driver/internal/cursor/AsyncResultCursorOnlyFactory.java similarity index 96% rename from driver/src/main/java/org/neo4j/driver/internal/reactive/cursor/AsyncResultCursorOnlyFactory.java rename to driver/src/main/java/org/neo4j/driver/internal/cursor/AsyncResultCursorOnlyFactory.java index 4a4f2d1b62..6d657afc7e 100644 --- a/driver/src/main/java/org/neo4j/driver/internal/reactive/cursor/AsyncResultCursorOnlyFactory.java +++ b/driver/src/main/java/org/neo4j/driver/internal/cursor/AsyncResultCursorOnlyFactory.java @@ -16,11 +16,11 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package org.neo4j.driver.internal.reactive.cursor; +package org.neo4j.driver.internal.cursor; import java.util.concurrent.CompletionStage; -import org.neo4j.driver.internal.AsyncStatementResultCursor; +import org.neo4j.driver.internal.async.AsyncStatementResultCursor; import org.neo4j.driver.internal.handlers.PullAllResponseHandler; import org.neo4j.driver.internal.handlers.RunResponseHandler; import org.neo4j.driver.internal.messaging.Message; diff --git a/driver/src/main/java/org/neo4j/driver/internal/reactive/cursor/InternalStatementResultCursor.java b/driver/src/main/java/org/neo4j/driver/internal/cursor/InternalStatementResultCursor.java similarity index 94% rename from driver/src/main/java/org/neo4j/driver/internal/reactive/cursor/InternalStatementResultCursor.java rename to driver/src/main/java/org/neo4j/driver/internal/cursor/InternalStatementResultCursor.java index e17ac81bbe..4560931daf 100644 --- a/driver/src/main/java/org/neo4j/driver/internal/reactive/cursor/InternalStatementResultCursor.java +++ b/driver/src/main/java/org/neo4j/driver/internal/cursor/InternalStatementResultCursor.java @@ -16,7 +16,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package org.neo4j.driver.internal.reactive.cursor; +package org.neo4j.driver.internal.cursor; import org.neo4j.driver.internal.FailableCursor; import org.neo4j.driver.async.StatementResultCursor; diff --git a/driver/src/main/java/org/neo4j/driver/internal/reactive/cursor/InternalStatementResultCursorFactory.java b/driver/src/main/java/org/neo4j/driver/internal/cursor/InternalStatementResultCursorFactory.java similarity index 96% rename from driver/src/main/java/org/neo4j/driver/internal/reactive/cursor/InternalStatementResultCursorFactory.java rename to driver/src/main/java/org/neo4j/driver/internal/cursor/InternalStatementResultCursorFactory.java index 5ddf09941d..ab4fce646b 100644 --- a/driver/src/main/java/org/neo4j/driver/internal/reactive/cursor/InternalStatementResultCursorFactory.java +++ b/driver/src/main/java/org/neo4j/driver/internal/cursor/InternalStatementResultCursorFactory.java @@ -16,11 +16,11 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package org.neo4j.driver.internal.reactive.cursor; +package org.neo4j.driver.internal.cursor; import java.util.concurrent.CompletionStage; -import org.neo4j.driver.internal.AsyncStatementResultCursor; +import org.neo4j.driver.internal.async.AsyncStatementResultCursor; import org.neo4j.driver.internal.handlers.PullAllResponseHandler; import org.neo4j.driver.internal.handlers.RunResponseHandler; import org.neo4j.driver.internal.handlers.pulln.BasicPullResponseHandler; diff --git a/driver/src/main/java/org/neo4j/driver/internal/reactive/cursor/RxStatementResultCursor.java b/driver/src/main/java/org/neo4j/driver/internal/cursor/RxStatementResultCursor.java similarity index 98% rename from driver/src/main/java/org/neo4j/driver/internal/reactive/cursor/RxStatementResultCursor.java rename to driver/src/main/java/org/neo4j/driver/internal/cursor/RxStatementResultCursor.java index 9eff8a906d..788d419b91 100644 --- a/driver/src/main/java/org/neo4j/driver/internal/reactive/cursor/RxStatementResultCursor.java +++ b/driver/src/main/java/org/neo4j/driver/internal/cursor/RxStatementResultCursor.java @@ -16,7 +16,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package org.neo4j.driver.internal.reactive.cursor; +package org.neo4j.driver.internal.cursor; import org.reactivestreams.Subscription; diff --git a/driver/src/main/java/org/neo4j/driver/internal/reactive/cursor/StatementResultCursorFactory.java b/driver/src/main/java/org/neo4j/driver/internal/cursor/StatementResultCursorFactory.java similarity index 94% rename from driver/src/main/java/org/neo4j/driver/internal/reactive/cursor/StatementResultCursorFactory.java rename to driver/src/main/java/org/neo4j/driver/internal/cursor/StatementResultCursorFactory.java index 6ab84663a9..200d362925 100644 --- a/driver/src/main/java/org/neo4j/driver/internal/reactive/cursor/StatementResultCursorFactory.java +++ b/driver/src/main/java/org/neo4j/driver/internal/cursor/StatementResultCursorFactory.java @@ -16,7 +16,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package org.neo4j.driver.internal.reactive.cursor; +package org.neo4j.driver.internal.cursor; import java.util.concurrent.CompletionStage; diff --git a/driver/src/main/java/org/neo4j/driver/internal/handlers/ChannelReleasingResetResponseHandler.java b/driver/src/main/java/org/neo4j/driver/internal/handlers/ChannelReleasingResetResponseHandler.java index 6d61bfb9f6..d96dfc4bf3 100644 --- a/driver/src/main/java/org/neo4j/driver/internal/handlers/ChannelReleasingResetResponseHandler.java +++ b/driver/src/main/java/org/neo4j/driver/internal/handlers/ChannelReleasingResetResponseHandler.java @@ -27,7 +27,7 @@ import org.neo4j.driver.internal.async.inbound.InboundMessageDispatcher; import org.neo4j.driver.internal.util.Clock; -import static org.neo4j.driver.internal.async.ChannelAttributes.setLastUsedTimestamp; +import static org.neo4j.driver.internal.async.connection.ChannelAttributes.setLastUsedTimestamp; public class ChannelReleasingResetResponseHandler extends ResetResponseHandler { diff --git a/driver/src/main/java/org/neo4j/driver/internal/handlers/HelloResponseHandler.java b/driver/src/main/java/org/neo4j/driver/internal/handlers/HelloResponseHandler.java index 13ca717886..ec088f9304 100644 --- a/driver/src/main/java/org/neo4j/driver/internal/handlers/HelloResponseHandler.java +++ b/driver/src/main/java/org/neo4j/driver/internal/handlers/HelloResponseHandler.java @@ -27,8 +27,8 @@ import org.neo4j.driver.internal.util.ServerVersion; import org.neo4j.driver.Value; -import static org.neo4j.driver.internal.async.ChannelAttributes.setConnectionId; -import static org.neo4j.driver.internal.async.ChannelAttributes.setServerVersion; +import static org.neo4j.driver.internal.async.connection.ChannelAttributes.setConnectionId; +import static org.neo4j.driver.internal.async.connection.ChannelAttributes.setServerVersion; import static org.neo4j.driver.internal.util.MetadataExtractor.extractNeo4jServerVersion; public class HelloResponseHandler implements ResponseHandler diff --git a/driver/src/main/java/org/neo4j/driver/internal/handlers/InitResponseHandler.java b/driver/src/main/java/org/neo4j/driver/internal/handlers/InitResponseHandler.java index 4e6bb9dfcf..f03414bf3d 100644 --- a/driver/src/main/java/org/neo4j/driver/internal/handlers/InitResponseHandler.java +++ b/driver/src/main/java/org/neo4j/driver/internal/handlers/InitResponseHandler.java @@ -29,7 +29,7 @@ import org.neo4j.driver.internal.util.ServerVersion; import org.neo4j.driver.Value; -import static org.neo4j.driver.internal.async.ChannelAttributes.setServerVersion; +import static org.neo4j.driver.internal.async.connection.ChannelAttributes.setServerVersion; import static org.neo4j.driver.internal.util.MetadataExtractor.extractNeo4jServerVersion; public class InitResponseHandler implements ResponseHandler diff --git a/driver/src/main/java/org/neo4j/driver/internal/handlers/PullHandlers.java b/driver/src/main/java/org/neo4j/driver/internal/handlers/PullHandlers.java index a3b6f2a120..fef1eac382 100644 --- a/driver/src/main/java/org/neo4j/driver/internal/handlers/PullHandlers.java +++ b/driver/src/main/java/org/neo4j/driver/internal/handlers/PullHandlers.java @@ -18,15 +18,15 @@ */ package org.neo4j.driver.internal.handlers; +import org.neo4j.driver.Statement; import org.neo4j.driver.internal.BookmarksHolder; -import org.neo4j.driver.internal.ExplicitTransaction; +import org.neo4j.driver.internal.async.ExplicitTransaction; import org.neo4j.driver.internal.handlers.pulln.BasicPullResponseHandler; import org.neo4j.driver.internal.handlers.pulln.SessionPullResponseHandler; import org.neo4j.driver.internal.handlers.pulln.TransactionPullResponseHandler; import org.neo4j.driver.internal.messaging.v1.BoltProtocolV1; import org.neo4j.driver.internal.messaging.v3.BoltProtocolV3; import org.neo4j.driver.internal.spi.Connection; -import org.neo4j.driver.Statement; public class PullHandlers { diff --git a/driver/src/main/java/org/neo4j/driver/internal/handlers/TransactionPullAllResponseHandler.java b/driver/src/main/java/org/neo4j/driver/internal/handlers/TransactionPullAllResponseHandler.java index b9b7e8843e..c9ee224487 100644 --- a/driver/src/main/java/org/neo4j/driver/internal/handlers/TransactionPullAllResponseHandler.java +++ b/driver/src/main/java/org/neo4j/driver/internal/handlers/TransactionPullAllResponseHandler.java @@ -20,11 +20,11 @@ import java.util.Map; -import org.neo4j.driver.internal.ExplicitTransaction; -import org.neo4j.driver.internal.spi.Connection; -import org.neo4j.driver.internal.util.MetadataExtractor; import org.neo4j.driver.Statement; import org.neo4j.driver.Value; +import org.neo4j.driver.internal.async.ExplicitTransaction; +import org.neo4j.driver.internal.spi.Connection; +import org.neo4j.driver.internal.util.MetadataExtractor; import static java.util.Objects.requireNonNull; diff --git a/driver/src/main/java/org/neo4j/driver/internal/handlers/pulln/TransactionPullResponseHandler.java b/driver/src/main/java/org/neo4j/driver/internal/handlers/pulln/TransactionPullResponseHandler.java index eb253abb99..5944d850cb 100644 --- a/driver/src/main/java/org/neo4j/driver/internal/handlers/pulln/TransactionPullResponseHandler.java +++ b/driver/src/main/java/org/neo4j/driver/internal/handlers/pulln/TransactionPullResponseHandler.java @@ -20,12 +20,12 @@ import java.util.Map; -import org.neo4j.driver.internal.ExplicitTransaction; +import org.neo4j.driver.Statement; +import org.neo4j.driver.Value; +import org.neo4j.driver.internal.async.ExplicitTransaction; import org.neo4j.driver.internal.handlers.RunResponseHandler; import org.neo4j.driver.internal.spi.Connection; import org.neo4j.driver.internal.util.MetadataExtractor; -import org.neo4j.driver.Statement; -import org.neo4j.driver.Value; import static java.util.Objects.requireNonNull; diff --git a/driver/src/main/java/org/neo4j/driver/internal/logging/ChannelActivityLogger.java b/driver/src/main/java/org/neo4j/driver/internal/logging/ChannelActivityLogger.java index 59f4dac4f2..d45b9e47a3 100644 --- a/driver/src/main/java/org/neo4j/driver/internal/logging/ChannelActivityLogger.java +++ b/driver/src/main/java/org/neo4j/driver/internal/logging/ChannelActivityLogger.java @@ -21,7 +21,7 @@ import io.netty.channel.Channel; import org.neo4j.driver.internal.BoltServerAddress; -import org.neo4j.driver.internal.async.ChannelAttributes; +import org.neo4j.driver.internal.async.connection.ChannelAttributes; import org.neo4j.driver.Logger; import org.neo4j.driver.Logging; diff --git a/driver/src/main/java/org/neo4j/driver/internal/messaging/BoltProtocol.java b/driver/src/main/java/org/neo4j/driver/internal/messaging/BoltProtocol.java index 4cd63a1fbe..c34b1c0d82 100644 --- a/driver/src/main/java/org/neo4j/driver/internal/messaging/BoltProtocol.java +++ b/driver/src/main/java/org/neo4j/driver/internal/messaging/BoltProtocol.java @@ -24,23 +24,23 @@ import java.util.Map; import java.util.concurrent.CompletionStage; +import org.neo4j.driver.Session; +import org.neo4j.driver.Statement; +import org.neo4j.driver.Transaction; +import org.neo4j.driver.TransactionConfig; +import org.neo4j.driver.Value; +import org.neo4j.driver.exceptions.ClientException; import org.neo4j.driver.internal.Bookmarks; import org.neo4j.driver.internal.BookmarksHolder; -import org.neo4j.driver.internal.ExplicitTransaction; +import org.neo4j.driver.internal.async.ExplicitTransaction; +import org.neo4j.driver.internal.cursor.StatementResultCursorFactory; import org.neo4j.driver.internal.messaging.v1.BoltProtocolV1; import org.neo4j.driver.internal.messaging.v2.BoltProtocolV2; import org.neo4j.driver.internal.messaging.v3.BoltProtocolV3; import org.neo4j.driver.internal.messaging.v4.BoltProtocolV4; import org.neo4j.driver.internal.spi.Connection; -import org.neo4j.driver.internal.reactive.cursor.StatementResultCursorFactory; -import org.neo4j.driver.Session; -import org.neo4j.driver.Statement; -import org.neo4j.driver.Transaction; -import org.neo4j.driver.TransactionConfig; -import org.neo4j.driver.Value; -import org.neo4j.driver.exceptions.ClientException; -import static org.neo4j.driver.internal.async.ChannelAttributes.protocolVersion; +import static org.neo4j.driver.internal.async.connection.ChannelAttributes.protocolVersion; public interface BoltProtocol { diff --git a/driver/src/main/java/org/neo4j/driver/internal/messaging/v1/BoltProtocolV1.java b/driver/src/main/java/org/neo4j/driver/internal/messaging/v1/BoltProtocolV1.java index add6cfcff5..b4d140c0e5 100644 --- a/driver/src/main/java/org/neo4j/driver/internal/messaging/v1/BoltProtocolV1.java +++ b/driver/src/main/java/org/neo4j/driver/internal/messaging/v1/BoltProtocolV1.java @@ -31,7 +31,9 @@ import org.neo4j.driver.exceptions.ClientException; import org.neo4j.driver.internal.Bookmarks; import org.neo4j.driver.internal.BookmarksHolder; -import org.neo4j.driver.internal.ExplicitTransaction; +import org.neo4j.driver.internal.async.ExplicitTransaction; +import org.neo4j.driver.internal.cursor.AsyncResultCursorOnlyFactory; +import org.neo4j.driver.internal.cursor.StatementResultCursorFactory; import org.neo4j.driver.internal.handlers.AbstractPullAllResponseHandler; import org.neo4j.driver.internal.handlers.BeginTxResponseHandler; import org.neo4j.driver.internal.handlers.CommitTxResponseHandler; @@ -46,15 +48,13 @@ import org.neo4j.driver.internal.messaging.request.InitMessage; import org.neo4j.driver.internal.messaging.request.PullAllMessage; import org.neo4j.driver.internal.messaging.request.RunMessage; -import org.neo4j.driver.internal.reactive.cursor.AsyncResultCursorOnlyFactory; -import org.neo4j.driver.internal.reactive.cursor.StatementResultCursorFactory; import org.neo4j.driver.internal.spi.Connection; import org.neo4j.driver.internal.spi.ResponseHandler; import org.neo4j.driver.internal.util.Futures; import org.neo4j.driver.internal.util.MetadataExtractor; import static org.neo4j.driver.Values.ofValue; -import static org.neo4j.driver.internal.async.ChannelAttributes.messageDispatcher; +import static org.neo4j.driver.internal.async.connection.ChannelAttributes.messageDispatcher; import static org.neo4j.driver.internal.messaging.request.MultiDatabaseUtil.assertEmptyDatabaseName; public class BoltProtocolV1 implements BoltProtocol diff --git a/driver/src/main/java/org/neo4j/driver/internal/messaging/v3/BoltProtocolV3.java b/driver/src/main/java/org/neo4j/driver/internal/messaging/v3/BoltProtocolV3.java index fa24cd708a..1fa439f571 100644 --- a/driver/src/main/java/org/neo4j/driver/internal/messaging/v3/BoltProtocolV3.java +++ b/driver/src/main/java/org/neo4j/driver/internal/messaging/v3/BoltProtocolV3.java @@ -30,7 +30,9 @@ import org.neo4j.driver.Value; import org.neo4j.driver.internal.Bookmarks; import org.neo4j.driver.internal.BookmarksHolder; -import org.neo4j.driver.internal.ExplicitTransaction; +import org.neo4j.driver.internal.async.ExplicitTransaction; +import org.neo4j.driver.internal.cursor.AsyncResultCursorOnlyFactory; +import org.neo4j.driver.internal.cursor.StatementResultCursorFactory; import org.neo4j.driver.internal.handlers.AbstractPullAllResponseHandler; import org.neo4j.driver.internal.handlers.BeginTxResponseHandler; import org.neo4j.driver.internal.handlers.CommitTxResponseHandler; @@ -44,13 +46,11 @@ import org.neo4j.driver.internal.messaging.request.GoodbyeMessage; import org.neo4j.driver.internal.messaging.request.HelloMessage; import org.neo4j.driver.internal.messaging.request.RunWithMetadataMessage; -import org.neo4j.driver.internal.reactive.cursor.AsyncResultCursorOnlyFactory; -import org.neo4j.driver.internal.reactive.cursor.StatementResultCursorFactory; import org.neo4j.driver.internal.spi.Connection; import org.neo4j.driver.internal.util.Futures; import org.neo4j.driver.internal.util.MetadataExtractor; -import static org.neo4j.driver.internal.async.ChannelAttributes.messageDispatcher; +import static org.neo4j.driver.internal.async.connection.ChannelAttributes.messageDispatcher; import static org.neo4j.driver.internal.handlers.PullHandlers.newBoltV3PullAllHandler; import static org.neo4j.driver.internal.messaging.request.CommitMessage.COMMIT; import static org.neo4j.driver.internal.messaging.request.MultiDatabaseUtil.assertEmptyDatabaseName; diff --git a/driver/src/main/java/org/neo4j/driver/internal/messaging/v4/BoltProtocolV4.java b/driver/src/main/java/org/neo4j/driver/internal/messaging/v4/BoltProtocolV4.java index 68f86ca5b4..9a2b33b20d 100644 --- a/driver/src/main/java/org/neo4j/driver/internal/messaging/v4/BoltProtocolV4.java +++ b/driver/src/main/java/org/neo4j/driver/internal/messaging/v4/BoltProtocolV4.java @@ -18,11 +18,11 @@ */ package org.neo4j.driver.internal.messaging.v4; -import java.util.concurrent.CompletableFuture; - import org.neo4j.driver.Statement; import org.neo4j.driver.internal.BookmarksHolder; -import org.neo4j.driver.internal.ExplicitTransaction; +import org.neo4j.driver.internal.async.ExplicitTransaction; +import org.neo4j.driver.internal.cursor.InternalStatementResultCursorFactory; +import org.neo4j.driver.internal.cursor.StatementResultCursorFactory; import org.neo4j.driver.internal.handlers.AbstractPullAllResponseHandler; import org.neo4j.driver.internal.handlers.RunResponseHandler; import org.neo4j.driver.internal.handlers.pulln.BasicPullResponseHandler; @@ -30,8 +30,6 @@ import org.neo4j.driver.internal.messaging.MessageFormat; import org.neo4j.driver.internal.messaging.request.RunWithMetadataMessage; import org.neo4j.driver.internal.messaging.v3.BoltProtocolV3; -import org.neo4j.driver.internal.reactive.cursor.InternalStatementResultCursorFactory; -import org.neo4j.driver.internal.reactive.cursor.StatementResultCursorFactory; import org.neo4j.driver.internal.spi.Connection; import static org.neo4j.driver.internal.handlers.PullHandlers.newBoltV3PullAllHandler; @@ -52,9 +50,7 @@ public MessageFormat createMessageFormat() protected StatementResultCursorFactory buildResultCursorFactory( Connection connection, Statement statement, BookmarksHolder bookmarksHolder, ExplicitTransaction tx, RunWithMetadataMessage runMessage, boolean waitForRunResponse ) { - CompletableFuture runCompletedFuture = new CompletableFuture<>(); - - RunResponseHandler runHandler = new RunResponseHandler( runCompletedFuture, METADATA_EXTRACTOR ); + RunResponseHandler runHandler = new RunResponseHandler( METADATA_EXTRACTOR ); AbstractPullAllResponseHandler pullAllHandler = newBoltV3PullAllHandler( statement, runHandler, connection, bookmarksHolder, tx ); BasicPullResponseHandler pullHandler = newBoltV4PullHandler( statement, runHandler, connection, bookmarksHolder, tx ); diff --git a/driver/src/main/java/org/neo4j/driver/internal/metrics/MetricsListener.java b/driver/src/main/java/org/neo4j/driver/internal/metrics/MetricsListener.java index 006a1e3434..0d5e9288c1 100644 --- a/driver/src/main/java/org/neo4j/driver/internal/metrics/MetricsListener.java +++ b/driver/src/main/java/org/neo4j/driver/internal/metrics/MetricsListener.java @@ -21,7 +21,7 @@ import java.util.concurrent.TimeUnit; import org.neo4j.driver.internal.BoltServerAddress; -import org.neo4j.driver.internal.async.DirectConnection; +import org.neo4j.driver.internal.async.connection.DirectConnection; import org.neo4j.driver.internal.async.pool.ConnectionPoolImpl; import org.neo4j.driver.Config; diff --git a/driver/src/main/java/org/neo4j/driver/internal/reactive/InternalRxResult.java b/driver/src/main/java/org/neo4j/driver/internal/reactive/InternalRxResult.java index 7e136a56d5..d24fa9b45d 100644 --- a/driver/src/main/java/org/neo4j/driver/internal/reactive/InternalRxResult.java +++ b/driver/src/main/java/org/neo4j/driver/internal/reactive/InternalRxResult.java @@ -26,9 +26,9 @@ import java.util.function.Supplier; import org.neo4j.driver.Record; -import org.neo4j.driver.internal.reactive.cursor.RxStatementResultCursor; import org.neo4j.driver.internal.util.Futures; import org.neo4j.driver.reactive.RxResult; +import org.neo4j.driver.internal.cursor.RxStatementResultCursor; import org.neo4j.driver.summary.ResultSummary; public class InternalRxResult implements RxResult @@ -44,7 +44,8 @@ public InternalRxResult( Supplier> curs @Override public Publisher keys() { - return Flux.defer( () -> Mono.fromCompletionStage( getCursorFuture() ).flatMapIterable( RxStatementResultCursor::keys ).onErrorMap( Futures::completionExceptionCause ) ); + return Flux.defer( () -> Mono.fromCompletionStage( getCursorFuture() ) + .flatMapIterable( RxStatementResultCursor::keys ).onErrorMap( Futures::completionExceptionCause ) ); } @Override diff --git a/driver/src/main/java/org/neo4j/driver/internal/reactive/InternalRxSession.java b/driver/src/main/java/org/neo4j/driver/internal/reactive/InternalRxSession.java index 0948599c11..fb1fdb4649 100644 --- a/driver/src/main/java/org/neo4j/driver/internal/reactive/InternalRxSession.java +++ b/driver/src/main/java/org/neo4j/driver/internal/reactive/InternalRxSession.java @@ -20,23 +20,20 @@ import org.reactivestreams.Publisher; import reactor.core.publisher.Flux; -import reactor.core.publisher.Mono; import java.util.Map; import java.util.concurrent.CompletableFuture; -import org.neo4j.driver.internal.ExplicitTransaction; -import org.neo4j.driver.internal.NetworkSession; +import org.neo4j.driver.AccessMode; +import org.neo4j.driver.Statement; +import org.neo4j.driver.TransactionConfig; +import org.neo4j.driver.internal.async.NetworkSession; +import org.neo4j.driver.internal.cursor.RxStatementResultCursor; import org.neo4j.driver.internal.util.Futures; import org.neo4j.driver.reactive.RxResult; import org.neo4j.driver.reactive.RxSession; import org.neo4j.driver.reactive.RxTransaction; import org.neo4j.driver.reactive.RxTransactionWork; -import org.neo4j.driver.internal.reactive.cursor.RxStatementResultCursor; -import org.neo4j.driver.AccessMode; -import org.neo4j.driver.Statement; -import org.neo4j.driver.TransactionConfig; -import org.neo4j.driver.exceptions.TransientException; import static org.neo4j.driver.internal.reactive.RxUtils.createEmptyPublisher; import static org.neo4j.driver.internal.reactive.RxUtils.createMono; @@ -68,7 +65,26 @@ public Publisher beginTransaction( TransactionConfig config ) session.beginTransactionAsync( config ).whenComplete( ( tx, completionError ) -> { if ( tx != null ) { - txFuture.complete( new InternalRxTransaction( (ExplicitTransaction) tx ) ); + txFuture.complete( new InternalRxTransaction( tx ) ); + } + else + { + releaseConnectionBeforeReturning( txFuture, completionError ); + } + } ); + return txFuture; + } ); + } + + private Publisher beginTransaction( AccessMode mode, TransactionConfig config ) + { + return createMono( () -> + { + CompletableFuture txFuture = new CompletableFuture<>(); + session.beginTransactionAsync( mode, config ).whenComplete( ( tx, completionError ) -> { + if ( tx != null ) + { + txFuture.complete( new InternalRxTransaction( tx ) ); } else { @@ -105,21 +121,8 @@ public Publisher writeTransaction( RxTransactionWork> work, private Publisher runTransaction( AccessMode mode, RxTransactionWork> work, TransactionConfig config ) { - // TODO read and write - Publisher publisher = beginTransaction( config ); - Flux txExecutor = Mono.from( publisher ).flatMapMany( tx -> Flux.from( work.execute( tx ) ).flatMap( t -> Mono.create( sink -> sink.success( t ) ), - throwable -> Mono.from( tx.rollback() ).then( Mono.error( throwable ) ), // TODO chain errors from rollback to throwable - () -> Mono.from( tx.commit() ).then( Mono.empty() ) ) ); - return txExecutor.retry( throwable -> { - if ( throwable instanceof TransientException ) - { - return true; - } - else - { - return false; - } - } ); // TODO retry + Flux repeatableWork = Flux.usingWhen( beginTransaction( mode, config ), work::execute, RxTransaction::commit, RxTransaction::rollback ); + return session.retryLogic().retryRx( repeatableWork ); } @Override @@ -167,7 +170,7 @@ private void releaseConnectionBeforeReturning( CompletableFuture returnFu // The reason we need to release connection in session is that we do not have a `rxSession.close()`; // Otherwise, session.close shall handle everything for us. Throwable error = Futures.completionExceptionCause( completionError ); - session.releaseConnection().whenComplete( ( ignored, closeError ) -> + session.releaseConnectionAsync().whenComplete( ( ignored, closeError ) -> returnFuture.completeExceptionally( Futures.combineErrors( error, closeError ) ) ); } diff --git a/driver/src/main/java/org/neo4j/driver/internal/reactive/InternalRxTransaction.java b/driver/src/main/java/org/neo4j/driver/internal/reactive/InternalRxTransaction.java index d869601b81..7a5322a477 100644 --- a/driver/src/main/java/org/neo4j/driver/internal/reactive/InternalRxTransaction.java +++ b/driver/src/main/java/org/neo4j/driver/internal/reactive/InternalRxTransaction.java @@ -22,22 +22,22 @@ import java.util.concurrent.CompletableFuture; -import org.neo4j.driver.internal.ExplicitTransaction; +import org.neo4j.driver.Statement; +import org.neo4j.driver.internal.async.ExplicitTransaction; +import org.neo4j.driver.internal.cursor.RxStatementResultCursor; import org.neo4j.driver.internal.util.Futures; import org.neo4j.driver.reactive.RxResult; import org.neo4j.driver.reactive.RxTransaction; -import org.neo4j.driver.internal.reactive.cursor.RxStatementResultCursor; -import org.neo4j.driver.Statement; import static org.neo4j.driver.internal.reactive.RxUtils.createEmptyPublisher; public class InternalRxTransaction extends AbstractRxStatementRunner implements RxTransaction { - private final ExplicitTransaction asyncTx; + private final ExplicitTransaction tx; - public InternalRxTransaction( ExplicitTransaction asyncTx ) + public InternalRxTransaction( ExplicitTransaction tx ) { - this.asyncTx = asyncTx; + this.tx = tx; } @Override @@ -45,7 +45,7 @@ public RxResult run( Statement statement ) { return new InternalRxResult( () -> { CompletableFuture cursorFuture = new CompletableFuture<>(); - asyncTx.runRx( statement ).whenComplete( ( cursor, completionError ) -> { + tx.runRx( statement ).whenComplete( ( cursor, completionError ) -> { if ( cursor != null ) { cursorFuture.complete( cursor ); @@ -54,9 +54,9 @@ public RxResult run( Statement statement ) { // We failed to create a result cursor so we cannot rely on result cursor to handle failure. // The logic here shall be the same as `TransactionPullResponseHandler#afterFailure` as that is where cursor handling failure - // This is optional as asyncTx still holds a reference to all cursor futures and they will be clean up properly in commit + // This is optional as tx still holds a reference to all cursor futures and they will be clean up properly in commit Throwable error = Futures.completionExceptionCause( completionError ); - asyncTx.markTerminated(); + tx.markTerminated(); cursorFuture.completeExceptionally( error ); } } ); @@ -81,11 +81,11 @@ private Publisher close( boolean commit ) return createEmptyPublisher( () -> { if ( commit ) { - return asyncTx.commitAsync(); + return tx.commitAsync(); } else { - return asyncTx.rollbackAsync(); + return tx.rollbackAsync(); } } ); } diff --git a/driver/src/main/java/org/neo4j/driver/internal/retry/ExponentialBackoffRetryLogic.java b/driver/src/main/java/org/neo4j/driver/internal/retry/ExponentialBackoffRetryLogic.java index d48f653f19..6791d66244 100644 --- a/driver/src/main/java/org/neo4j/driver/internal/retry/ExponentialBackoffRetryLogic.java +++ b/driver/src/main/java/org/neo4j/driver/internal/retry/ExponentialBackoffRetryLogic.java @@ -20,22 +20,30 @@ import io.netty.util.concurrent.EventExecutor; import io.netty.util.concurrent.EventExecutorGroup; - +import org.reactivestreams.Publisher; +import reactor.core.publisher.Flux; +import reactor.core.publisher.Mono; +import reactor.core.scheduler.Schedulers; +import reactor.util.context.Context; +import reactor.util.function.Tuples; + +import java.time.Duration; import java.util.ArrayList; import java.util.List; import java.util.concurrent.CompletableFuture; import java.util.concurrent.CompletionStage; import java.util.concurrent.ThreadLocalRandom; import java.util.concurrent.TimeUnit; +import java.util.function.Function; import java.util.function.Supplier; -import org.neo4j.driver.internal.util.Clock; -import org.neo4j.driver.internal.util.Futures; import org.neo4j.driver.Logger; import org.neo4j.driver.Logging; import org.neo4j.driver.exceptions.ServiceUnavailableException; import org.neo4j.driver.exceptions.SessionExpiredException; import org.neo4j.driver.exceptions.TransientException; +import org.neo4j.driver.internal.util.Clock; +import org.neo4j.driver.internal.util.Futures; import static java.util.concurrent.TimeUnit.SECONDS; @@ -128,6 +136,12 @@ public CompletionStage retryAsync( Supplier> work ) return resultFuture; } + @Override + public Publisher retryRx( Publisher work ) + { + return Flux.from( work ).retryWhen( retryRxCondition() ); + } + protected boolean canRetryOn( Throwable error ) { return error instanceof SessionExpiredException || @@ -135,6 +149,52 @@ protected boolean canRetryOn( Throwable error ) isTransientError( error ); } + private Function,Publisher> retryRxCondition() + { + return errorCurrentAttempt -> errorCurrentAttempt.flatMap( e -> Mono.subscriberContext().map( ctx -> Tuples.of( e, ctx ) ) ).flatMap( t2 -> { + Throwable lastError = t2.getT1(); + Context ctx = t2.getT2(); + + List errors = ctx.getOrDefault( "errors", null ); + + long startTime = ctx.getOrDefault( "startTime", -1L ); + long nextDelayMs = ctx.getOrDefault( "nextDelayMs", initialRetryDelayMs ); + + if( !canRetryOn( lastError ) ) + { + addSuppressed( lastError, errors ); + return Mono.error( lastError ); + } + + long currentTime = clock.millis(); + if ( startTime == -1 ) + { + startTime = currentTime; + } + + long elapsedTime = currentTime - startTime; + if ( elapsedTime < maxRetryTimeMs ) + { + long delayWithJitterMs = computeDelayWithJitter( nextDelayMs ); + log.warn( "Reactive transaction failed and is scheduled to retry in " + delayWithJitterMs + "ms", lastError ); + + nextDelayMs = (long) (nextDelayMs * multiplier); + errors = recordError( lastError, errors ); + + // retry on netty event loop thread + EventExecutor eventExecutor = eventExecutorGroup.next(); + return Mono.just( ctx.put( "errors", errors ).put( "startTime", startTime ).put( "nextDelayMs", nextDelayMs ) ) + .delayElement( Duration.ofMillis( delayWithJitterMs ), Schedulers.fromExecutorService( eventExecutor ) ); + } + else + { + addSuppressed( lastError, errors ); + + return Mono.error( lastError ); + } + } ); + } + private void executeWorkInEventLoop( CompletableFuture resultFuture, Supplier> work ) { // this is the very first time we execute given work diff --git a/driver/src/main/java/org/neo4j/driver/internal/retry/RetryLogic.java b/driver/src/main/java/org/neo4j/driver/internal/retry/RetryLogic.java index d5ba6956f7..332626d1d3 100644 --- a/driver/src/main/java/org/neo4j/driver/internal/retry/RetryLogic.java +++ b/driver/src/main/java/org/neo4j/driver/internal/retry/RetryLogic.java @@ -18,6 +18,8 @@ */ package org.neo4j.driver.internal.retry; +import org.reactivestreams.Publisher; + import java.util.concurrent.CompletionStage; import java.util.function.Supplier; @@ -26,4 +28,6 @@ public interface RetryLogic T retry( Supplier work ); CompletionStage retryAsync( Supplier> work ); + + Publisher retryRx( Publisher work ); } diff --git a/driver/src/main/java/org/neo4j/driver/internal/util/Futures.java b/driver/src/main/java/org/neo4j/driver/internal/util/Futures.java index d5d14b3165..e0a50d4a6b 100644 --- a/driver/src/main/java/org/neo4j/driver/internal/util/Futures.java +++ b/driver/src/main/java/org/neo4j/driver/internal/util/Futures.java @@ -26,7 +26,7 @@ import java.util.function.BiConsumer; import java.util.function.BiFunction; -import org.neo4j.driver.internal.async.EventLoopGroupFactory; +import org.neo4j.driver.internal.async.connection.EventLoopGroupFactory; import static java.util.concurrent.CompletableFuture.completedFuture; diff --git a/driver/src/main/java/org/neo4j/driver/reactive/RxResult.java b/driver/src/main/java/org/neo4j/driver/reactive/RxResult.java index 231f0aae1c..2ccf52a9ee 100644 --- a/driver/src/main/java/org/neo4j/driver/reactive/RxResult.java +++ b/driver/src/main/java/org/neo4j/driver/reactive/RxResult.java @@ -46,8 +46,8 @@ public interface RxResult * When this publisher is {@linkplain Publisher#subscribe(Subscriber) subscribed}, the query statement is sent to the server and get executed. * This method does not start the record streaming nor publish query execution error. * To retrieve the execution result, either {@link #records()} or {@link #summary()} can be used. - * {@link #records()} starts record streaming and report query execution error. - * {@link #summary()} skips record streaming and directly report query execution error. + * {@link #records()} starts record streaming and reports query execution error. + * {@link #summary()} skips record streaming and directly reports query execution error. *

* Consuming of execution result ensures the resources (such as network connections) used by this result is freed correctly. * Consuming the keys without consuming the execution result will result in resource leak. @@ -66,7 +66,7 @@ public interface RxResult *

* When the record publisher is {@linkplain Publisher#subscribe(Subscriber) subscribed}, * the query statement is executed and the query result is streamed back as a record stream followed by a result summary. - * This record publisher publishes all records in the result and signal the completion. + * This record publisher publishes all records in the result and signals the completion. * However before completion or error reporting if any, a cleanup of result resources such as network connection will be carried out automatically. *

* Therefore the {@link Subscriber} of this record publisher shall wait for the termination signal (complete or error) @@ -77,13 +77,13 @@ public interface RxResult * But it will not cancel the query execution. * As a result, a termination signal (complete or error) will still be sent to the {@link Subscriber} after the query execution is finished. *

- * The record publishing event by default runs in Netty IO thread, as a result no blocking operation is allowed in this thread. + * The record publishing event by default runs in an Network IO thread, as a result no blocking operation is allowed in this thread. * Otherwise network IO might be blocked by application logic. *

* This publisher can only be subscribed by one {@link Subscriber} once. *

- * If this publisher is subscribed after {@link #keys()}, then the publish of records is carried out once each record arrives. - * If this publisher is subscribed after {@link #summary()}, then the publish of records has been cancelled + * If this publisher is subscribed after {@link #keys()}, then the publish of records is carried out after the arrival of keys. + * If this publisher is subscribed after {@link #summary()}, then the publish of records is already cancelled * and an empty publisher of zero record will be return. * @return a cold unicast publisher of records. */ @@ -94,7 +94,7 @@ public interface RxResult *

* {@linkplain Publisher#subscribe(Subscriber) Subscribing} the summary publisher results in the execution of the query followed by the result summary returned. * The summary publisher cancels record publishing if not yet subscribed and directly streams back the summary on query execution completion. - * As a result, the invocation of {@link #records()} after this method, would receive a empty publisher. + * As a result, the invocation of {@link #records()} after this method, would receive an empty publisher. *

* If subscribed after {@link #keys()}, then the result summary will be published after the query execution without streaming any record to client. * If subscribed after {@link #records()}, then the result summary will be published after the query execution and the streaming of records. diff --git a/driver/src/main/java/org/neo4j/driver/reactive/RxSession.java b/driver/src/main/java/org/neo4j/driver/reactive/RxSession.java index 2879860eae..9c04f060f0 100644 --- a/driver/src/main/java/org/neo4j/driver/reactive/RxSession.java +++ b/driver/src/main/java/org/neo4j/driver/reactive/RxSession.java @@ -21,7 +21,9 @@ import org.reactivestreams.Publisher; import java.util.Map; +import java.util.concurrent.CompletableFuture; +import org.neo4j.driver.AccessMode; import org.neo4j.driver.Session; import org.neo4j.driver.Statement; import org.neo4j.driver.TransactionConfig; @@ -43,7 +45,7 @@ public interface RxSession extends RxStatementRunner * maintain multiple concurrent transactions, use multiple concurrent * sessions. *

- * It by default is executed in Netty IO thread, as a result no blocking operation is allowed in this thread. + * It by default is executed in a Network IO thread, as a result no blocking operation is allowed in this thread. * * @return a new {@link RxTransaction} */ @@ -52,22 +54,99 @@ public interface RxSession extends RxStatementRunner /** * Begin a new explicit {@linkplain RxTransaction transaction} with the specified {@link TransactionConfig configuration}. * At most one transaction may exist in a session at any point in time. To - * maintain multiple concurrent transactions, use multiple concurrent - * sessions. + * maintain multiple concurrent transactions, use multiple concurrent sessions. *

- * It by default is executed in Netty IO thread, as a result no blocking operation is allowed in this thread. + * It by default is executed in a Network IO thread, as a result no blocking operation is allowed in this thread. * * @param config configuration for the new transaction. * @return a new {@link RxTransaction} */ Publisher beginTransaction( TransactionConfig config ); + /** + * Execute given unit of reactive work in a {@link AccessMode#READ read} reactive transaction. +

+ * Transaction will automatically be committed unless given unit of work fails or + * {@link RxTransaction#commit() transaction commit} fails. + * It will also not be committed if explicitly rolled back via {@link RxTransaction#rollback()}. + *

+ * Returned publisher and given {@link RxTransactionWork} is completed/executed by an IO thread which should never block. + * Otherwise IO operations on this and potentially other network connections might deadlock. + * Please do not chain blocking operations like {@link CompletableFuture#get()} on the returned publisher and do not use them inside the + * {@link RxTransactionWork}. + * + * @param work the {@link RxTransactionWork} to be applied to a new read transaction. + * Operation executed by the given work must NOT include any blocking operation. + * @param the return type of the given unit of work. + * @return a {@link Publisher publisher} completed with the same result as returned by the given unit of work. + * publisher can be completed exceptionally if given work or commit fails. + * + */ Publisher readTransaction( RxTransactionWork> work ); + /** + * Execute given unit of reactive work in a {@link AccessMode#READ read} reactive transaction with + * the specified {@link TransactionConfig configuration}. +

+ * Transaction will automatically be committed unless given unit of work fails or + * {@link RxTransaction#commit() transaction commit} fails. + * It will also not be committed if explicitly rolled back via {@link RxTransaction#rollback()}. + *

+ * Returned publisher and given {@link RxTransactionWork} is completed/executed by an IO thread which should never block. + * Otherwise IO operations on this and potentially other network connections might deadlock. + * Please do not chain blocking operations like {@link CompletableFuture#get()} on the returned publisher and do not use them inside the + * {@link RxTransactionWork}. + * + * @param work the {@link RxTransactionWork} to be applied to a new read transaction. + * Operation executed by the given work must NOT include any blocking operation. + * @param the return type of the given unit of work. + * @return a {@link Publisher publisher} completed with the same result as returned by the given unit of work. + * publisher can be completed exceptionally if given work or commit fails. + * + */ Publisher readTransaction( RxTransactionWork> work, TransactionConfig config ); + /** + * Execute given unit of reactive work in a {@link AccessMode#WRITE write} reactive transaction. +

+ * Transaction will automatically be committed unless given unit of work fails or + * {@link RxTransaction#commit() transaction commit} fails. + * It will also not be committed if explicitly rolled back via {@link RxTransaction#rollback()}. + *

+ * Returned publisher and given {@link RxTransactionWork} is completed/executed by an IO thread which should never block. + * Otherwise IO operations on this and potentially other network connections might deadlock. + * Please do not chain blocking operations like {@link CompletableFuture#get()} on the returned publisher and do not use them inside the + * {@link RxTransactionWork}. + * + * @param work the {@link RxTransactionWork} to be applied to a new read transaction. + * Operation executed by the given work must NOT include any blocking operation. + * @param the return type of the given unit of work. + * @return a {@link Publisher publisher} completed with the same result as returned by the given unit of work. + * publisher can be completed exceptionally if given work or commit fails. + * + */ Publisher writeTransaction( RxTransactionWork> work ); + /** + * Execute given unit of reactive work in a {@link AccessMode#WRITE write} reactive transaction with + * the specified {@link TransactionConfig configuration}. +

+ * Transaction will automatically be committed unless given unit of work fails or + * {@link RxTransaction#commit() transaction commit} fails. + * It will also not be committed if explicitly rolled back via {@link RxTransaction#rollback()}. + *

+ * Returned publisher and given {@link RxTransactionWork} is completed/executed by an IO thread which should never block. + * Otherwise IO operations on this and potentially other network connections might deadlock. + * Please do not chain blocking operations like {@link CompletableFuture#get()} on the returned publisher and do not use them inside the + * {@link RxTransactionWork}. + * + * @param work the {@link RxTransactionWork} to be applied to a new read transaction. + * Operation executed by the given work must NOT include any blocking operation. + * @param the return type of the given unit of work. + * @return a {@link Publisher publisher} completed with the same result as returned by the given unit of work. + * publisher can be completed exceptionally if given work or commit fails. + * + */ Publisher writeTransaction( RxTransactionWork> work, TransactionConfig config ); /** diff --git a/driver/src/main/java/org/neo4j/driver/reactive/RxTransactionWork.java b/driver/src/main/java/org/neo4j/driver/reactive/RxTransactionWork.java index 9cda2a246d..449607c7b3 100644 --- a/driver/src/main/java/org/neo4j/driver/reactive/RxTransactionWork.java +++ b/driver/src/main/java/org/neo4j/driver/reactive/RxTransactionWork.java @@ -18,7 +18,21 @@ */ package org.neo4j.driver.reactive; +/** + * Callback that executes operations against a given {@link RxTransaction}. + * To be used with {@link RxSession#readTransaction(RxTransactionWork)} and + * {@link RxSession#writeTransaction(RxTransactionWork)} methods. + * + * @param the return type of this work. + * @since 2.0 + */ public interface RxTransactionWork { + /** + * Executes all given operations against the same transaction. + * + * @param tx the transaction to use. + * @return some result object or {@code null} if none. + */ T execute( RxTransaction tx ); } diff --git a/driver/src/test/java/org/neo4j/driver/ParametersTest.java b/driver/src/test/java/org/neo4j/driver/ParametersTest.java index 7dff56e929..ead3036281 100644 --- a/driver/src/test/java/org/neo4j/driver/ParametersTest.java +++ b/driver/src/test/java/org/neo4j/driver/ParametersTest.java @@ -24,12 +24,13 @@ import java.util.stream.Stream; +import org.neo4j.driver.exceptions.ClientException; import org.neo4j.driver.internal.DefaultBookmarksHolder; import org.neo4j.driver.internal.InternalRecord; -import org.neo4j.driver.internal.NetworkSession; +import org.neo4j.driver.internal.InternalSession; +import org.neo4j.driver.internal.async.NetworkSession; import org.neo4j.driver.internal.retry.RetryLogic; import org.neo4j.driver.internal.spi.ConnectionProvider; -import org.neo4j.driver.exceptions.ClientException; import static java.util.Collections.singletonList; import static java.util.Collections.singletonMap; @@ -39,12 +40,12 @@ import static org.junit.jupiter.api.Assertions.assertThrows; import static org.junit.jupiter.api.Assumptions.assumeTrue; import static org.mockito.Mockito.mock; +import static org.neo4j.driver.Values.parameters; import static org.neo4j.driver.internal.logging.DevNullLogging.DEV_NULL_LOGGING; import static org.neo4j.driver.internal.messaging.request.MultiDatabaseUtil.ABSENT_DB_NAME; import static org.neo4j.driver.internal.util.ValueFactory.emptyNodeValue; import static org.neo4j.driver.internal.util.ValueFactory.emptyRelationshipValue; import static org.neo4j.driver.internal.util.ValueFactory.filledPathValue; -import static org.neo4j.driver.Values.parameters; class ParametersTest { @@ -107,6 +108,8 @@ private Session mockedSession() { ConnectionProvider provider = mock( ConnectionProvider.class ); RetryLogic retryLogic = mock( RetryLogic.class ); - return new NetworkSession( provider, retryLogic, ABSENT_DB_NAME, AccessMode.WRITE, new DefaultBookmarksHolder(), DEV_NULL_LOGGING ); + NetworkSession session = + new NetworkSession( provider, retryLogic, ABSENT_DB_NAME, AccessMode.WRITE, new DefaultBookmarksHolder(), DEV_NULL_LOGGING ); + return new InternalSession( session ); } } diff --git a/driver/src/test/java/org/neo4j/driver/integration/SessionAsyncIT.java b/driver/src/test/java/org/neo4j/driver/integration/AsyncSessionIT.java similarity index 99% rename from driver/src/test/java/org/neo4j/driver/integration/SessionAsyncIT.java rename to driver/src/test/java/org/neo4j/driver/integration/AsyncSessionIT.java index 221328ad51..2c5bb64efe 100644 --- a/driver/src/test/java/org/neo4j/driver/integration/SessionAsyncIT.java +++ b/driver/src/test/java/org/neo4j/driver/integration/AsyncSessionIT.java @@ -83,7 +83,7 @@ import static org.neo4j.driver.util.TestUtil.awaitAll; @ParallelizableIT -class SessionAsyncIT +class AsyncSessionIT { @RegisterExtension static final DatabaseExtension neo4j = new DatabaseExtension(); diff --git a/driver/src/test/java/org/neo4j/driver/integration/TransactionAsyncIT.java b/driver/src/test/java/org/neo4j/driver/integration/AsyncTransactionIT.java similarity index 99% rename from driver/src/test/java/org/neo4j/driver/integration/TransactionAsyncIT.java rename to driver/src/test/java/org/neo4j/driver/integration/AsyncTransactionIT.java index beacf7d9bc..6faca07fab 100644 --- a/driver/src/test/java/org/neo4j/driver/integration/TransactionAsyncIT.java +++ b/driver/src/test/java/org/neo4j/driver/integration/AsyncTransactionIT.java @@ -42,7 +42,6 @@ import org.neo4j.driver.exceptions.ClientException; import org.neo4j.driver.exceptions.NoSuchRecordException; import org.neo4j.driver.exceptions.ServiceUnavailableException; -import org.neo4j.driver.internal.util.EnabledOnNeo4jWith; import org.neo4j.driver.summary.ResultSummary; import org.neo4j.driver.summary.StatementType; import org.neo4j.driver.types.Node; @@ -69,7 +68,7 @@ import static org.neo4j.driver.util.TestUtil.await; @ParallelizableIT -class TransactionAsyncIT +class AsyncTransactionIT { @RegisterExtension static final DatabaseExtension neo4j = new DatabaseExtension(); diff --git a/driver/src/test/java/org/neo4j/driver/integration/BookmarkIT.java b/driver/src/test/java/org/neo4j/driver/integration/BookmarkIT.java index a1b776ae8e..83e54d0665 100644 --- a/driver/src/test/java/org/neo4j/driver/integration/BookmarkIT.java +++ b/driver/src/test/java/org/neo4j/driver/integration/BookmarkIT.java @@ -28,8 +28,6 @@ import org.neo4j.driver.Session; import org.neo4j.driver.Transaction; import org.neo4j.driver.exceptions.ClientException; -import org.neo4j.driver.exceptions.TransientException; -import org.neo4j.driver.internal.util.EnabledOnNeo4jWith; import org.neo4j.driver.util.ParallelizableIT; import org.neo4j.driver.util.SessionExtension; @@ -82,16 +80,6 @@ void shouldThrowForInvalidBookmark() } } - @SuppressWarnings( "deprecation" ) - @Test - void shouldThrowForUnreachableBookmark() - { - createNodeInTx( session ); - - TransientException e = assertThrows( TransientException.class, () -> session.beginTransaction( session.lastBookmark() + 42 ) ); - assertThat( e.getMessage(), startsWith( "Database not up to the requested version" ) ); - } - @Test void bookmarkRemainsAfterRolledBackTx() { diff --git a/driver/src/test/java/org/neo4j/driver/integration/CausalClusteringIT.java b/driver/src/test/java/org/neo4j/driver/integration/CausalClusteringIT.java index ddaa10fb0a..c38deecc1f 100644 --- a/driver/src/test/java/org/neo4j/driver/integration/CausalClusteringIT.java +++ b/driver/src/test/java/org/neo4j/driver/integration/CausalClusteringIT.java @@ -56,7 +56,6 @@ import org.neo4j.driver.exceptions.Neo4jException; import org.neo4j.driver.exceptions.ServiceUnavailableException; import org.neo4j.driver.exceptions.SessionExpiredException; -import org.neo4j.driver.exceptions.TransientException; import org.neo4j.driver.internal.cluster.RoutingSettings; import org.neo4j.driver.internal.retry.RetrySettings; import org.neo4j.driver.internal.util.DisabledOnNeo4jWith; @@ -77,7 +76,6 @@ import static org.hamcrest.Matchers.containsString; import static org.hamcrest.Matchers.greaterThan; import static org.hamcrest.Matchers.is; -import static org.hamcrest.Matchers.startsWith; import static org.hamcrest.junit.MatcherAssert.assertThat; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertFalse; @@ -317,30 +315,6 @@ void beginTransactionThrowsForInvalidBookmark() } } - @SuppressWarnings( "deprecation" ) - @Test - void beginTransactionThrowsForUnreachableBookmark() - { - ClusterMember leader = clusterRule.getCluster().leader(); - - try ( Driver driver = createDriver( leader.getBoltUri() ); - Session session = driver.session() ) - { - try ( Transaction tx = session.beginTransaction() ) - { - tx.run( "CREATE ()" ); - tx.success(); - } - - String bookmark = session.lastBookmark(); - assertNotNull( bookmark ); - String newBookmark = bookmark + "0"; - - TransientException e = assertThrows( TransientException.class, () -> session.beginTransaction( newBookmark ) ); - assertThat( e.getMessage(), startsWith( "Database not up to the requested version" ) ); - } - } - @Test void shouldHandleGracefulLeaderSwitch() throws Exception { diff --git a/driver/src/test/java/org/neo4j/driver/integration/ConnectionHandlingIT.java b/driver/src/test/java/org/neo4j/driver/integration/ConnectionHandlingIT.java index d85b9ac05f..863394a2ff 100644 --- a/driver/src/test/java/org/neo4j/driver/integration/ConnectionHandlingIT.java +++ b/driver/src/test/java/org/neo4j/driver/integration/ConnectionHandlingIT.java @@ -36,7 +36,7 @@ import org.neo4j.driver.internal.BoltServerAddress; import org.neo4j.driver.internal.ConnectionSettings; import org.neo4j.driver.internal.DriverFactory; -import org.neo4j.driver.internal.async.ChannelConnector; +import org.neo4j.driver.internal.async.connection.ChannelConnector; import org.neo4j.driver.internal.async.pool.ConnectionPoolImpl; import org.neo4j.driver.internal.async.pool.PoolSettings; import org.neo4j.driver.internal.cluster.RoutingSettings; diff --git a/driver/src/test/java/org/neo4j/driver/integration/ExplicitTransactionIT.java b/driver/src/test/java/org/neo4j/driver/integration/ExplicitTransactionIT.java index 054ad28529..d51afb8bc2 100644 --- a/driver/src/test/java/org/neo4j/driver/integration/ExplicitTransactionIT.java +++ b/driver/src/test/java/org/neo4j/driver/integration/ExplicitTransactionIT.java @@ -31,15 +31,15 @@ import org.neo4j.driver.Config; import org.neo4j.driver.Driver; -import org.neo4j.driver.Session; -import org.neo4j.driver.Transaction; -import org.neo4j.driver.async.AsyncSession; -import org.neo4j.driver.async.AsyncTransaction; +import org.neo4j.driver.Statement; +import org.neo4j.driver.TransactionConfig; import org.neo4j.driver.async.StatementResultCursor; import org.neo4j.driver.exceptions.ClientException; import org.neo4j.driver.exceptions.ServiceUnavailableException; -import org.neo4j.driver.internal.ExplicitTransaction; -import org.neo4j.driver.internal.async.EventLoopGroupFactory; +import org.neo4j.driver.internal.InternalDriver; +import org.neo4j.driver.internal.SessionParameters; +import org.neo4j.driver.internal.async.ExplicitTransaction; +import org.neo4j.driver.internal.async.NetworkSession; import org.neo4j.driver.internal.cluster.RoutingSettings; import org.neo4j.driver.internal.retry.RetrySettings; import org.neo4j.driver.internal.util.Clock; @@ -47,7 +47,6 @@ import org.neo4j.driver.util.DatabaseExtension; import org.neo4j.driver.util.ParallelizableIT; -import static org.hamcrest.Matchers.is; import static org.hamcrest.Matchers.startsWith; import static org.hamcrest.junit.MatcherAssert.assertThat; import static org.junit.jupiter.api.Assertions.assertEquals; @@ -57,13 +56,8 @@ import static org.junit.jupiter.api.Assertions.assertTrue; import static org.neo4j.driver.Values.parameters; import static org.neo4j.driver.internal.logging.DevNullLogging.DEV_NULL_LOGGING; -import static org.neo4j.driver.internal.util.Matchers.blockingOperationInEventLoopError; import static org.neo4j.driver.util.TestUtil.await; -/** - * We leave the question whether we want to let {@link ExplicitTransaction} both implements blocking tx and async tx or not later. - * But as how it is right not, here are some tests for using mixes blocking and async API. - */ @ParallelizableIT class ExplicitTransactionIT @@ -71,12 +65,12 @@ class ExplicitTransactionIT @RegisterExtension static final DatabaseExtension neo4j = new DatabaseExtension(); - private AsyncSession session; + private NetworkSession session; @BeforeEach void setUp() { - session = neo4j.driver().asyncSession(); + session = ((InternalDriver) neo4j.driver()).newSession( SessionParameters.empty() ); } @AfterEach @@ -85,35 +79,55 @@ void tearDown() session.closeAsync(); } + private ExplicitTransaction beginTransaction() + { + return beginTransaction( session ); + } + + private ExplicitTransaction beginTransaction( NetworkSession session ) + { + return await( session.beginTransactionAsync( TransactionConfig.empty() ) ); + } + + private StatementResultCursor sessionRun( NetworkSession session, Statement statement ) + { + return await( session.runAsync( statement, TransactionConfig.empty(), true ) ); + } + + private StatementResultCursor txRun( ExplicitTransaction tx, String statement ) + { + return await( tx.runAsync( new Statement( statement ), true ) ); + } + @Test void shouldDoNothingWhenCommittedSecondTime() { - AsyncTransaction tx = await( session.beginTransactionAsync() ); + ExplicitTransaction tx = beginTransaction(); assertNull( await( tx.commitAsync() ) ); assertTrue( tx.commitAsync().toCompletableFuture().isDone() ); - assertFalse( ((ExplicitTransaction) tx).isOpen() ); + assertFalse( tx.isOpen() ); } @Test void shouldFailToCommitAfterRollback() { - AsyncTransaction tx = await( session.beginTransactionAsync() ); + ExplicitTransaction tx = beginTransaction(); assertNull( await( tx.rollbackAsync() ) ); ClientException e = assertThrows( ClientException.class, () -> await( tx.commitAsync() ) ); assertEquals( "Can't commit, transaction has been rolled back", e.getMessage() ); - assertFalse( ((ExplicitTransaction) tx).isOpen() ); + assertFalse( tx.isOpen() ); } @Test void shouldFailToCommitAfterTermination() { - AsyncTransaction tx = await( session.beginTransactionAsync() ); + ExplicitTransaction tx = beginTransaction(); - ((ExplicitTransaction) tx).markTerminated(); + tx.markTerminated(); ClientException e = assertThrows( ClientException.class, () -> await( tx.commitAsync() ) ); assertThat( e.getMessage(), startsWith( "Transaction can't be committed" ) ); @@ -122,129 +136,89 @@ void shouldFailToCommitAfterTermination() @Test void shouldDoNothingWhenRolledBackSecondTime() { - AsyncTransaction tx = await( session.beginTransactionAsync() ); + ExplicitTransaction tx = beginTransaction(); assertNull( await( tx.rollbackAsync() ) ); assertTrue( tx.rollbackAsync().toCompletableFuture().isDone() ); - assertFalse( ((ExplicitTransaction) tx).isOpen() ); + assertFalse( tx.isOpen() ); } @Test void shouldFailToRollbackAfterCommit() { - AsyncTransaction tx = await( session.beginTransactionAsync() ); + ExplicitTransaction tx = beginTransaction(); assertNull( await( tx.commitAsync() ) ); ClientException e = assertThrows( ClientException.class, () -> await( tx.rollbackAsync() ) ); assertEquals( "Can't rollback, transaction has been committed", e.getMessage() ); - assertFalse( ((ExplicitTransaction) tx).isOpen() ); + assertFalse( tx.isOpen() ); } @Test void shouldRollbackAfterTermination() { - AsyncTransaction tx = await( session.beginTransactionAsync() ); + ExplicitTransaction tx = beginTransaction(); - ((ExplicitTransaction) tx).markTerminated(); + tx.markTerminated(); assertNull( await( tx.rollbackAsync() ) ); - assertFalse( ((ExplicitTransaction) tx).isOpen() ); + assertFalse( tx.isOpen() ); } @Test void shouldFailToRunQueryWhenMarkedForFailure() { - AsyncTransaction tx = await( session.beginTransactionAsync() ); - tx.runAsync( "CREATE (:MyLabel)" ); - ((ExplicitTransaction) tx).failure(); + ExplicitTransaction tx = beginTransaction(); + txRun( tx, "CREATE (:MyLabel)" ); + tx.failure(); - ClientException e = assertThrows( ClientException.class, () -> await( tx.runAsync( "CREATE (:MyOtherLabel)" ) ) ); + ClientException e = assertThrows( ClientException.class, () -> txRun( tx, "CREATE (:MyOtherLabel)" ) ); assertThat( e.getMessage(), startsWith( "Cannot run more statements in this transaction" ) ); } @Test void shouldFailToRunQueryWhenTerminated() { - AsyncTransaction tx = await( session.beginTransactionAsync() ); - tx.runAsync( "CREATE (:MyLabel)" ); - ((ExplicitTransaction) tx).markTerminated(); + ExplicitTransaction tx = beginTransaction(); + txRun( tx, "CREATE (:MyLabel)" ); + tx.markTerminated(); - ClientException e = assertThrows( ClientException.class, () -> await( tx.runAsync( "CREATE (:MyOtherLabel)" ) ) ); + ClientException e = assertThrows( ClientException.class, () -> txRun( tx, "CREATE (:MyOtherLabel)" ) ); assertThat( e.getMessage(), startsWith( "Cannot run more statements in this transaction" ) ); } @Test void shouldAllowQueriesWhenMarkedForSuccess() { - AsyncTransaction tx = await( session.beginTransactionAsync() ); - tx.runAsync( "CREATE (:MyLabel)" ); + ExplicitTransaction tx = beginTransaction(); + txRun( tx, "CREATE (:MyLabel)" ); - ((ExplicitTransaction) tx).success(); + tx.success(); - tx.runAsync( "CREATE (:MyLabel)" ); + txRun( tx, "CREATE (:MyLabel)" ); assertNull( await( tx.commitAsync() ) ); - StatementResultCursor cursor = await( session.runAsync( "MATCH (n:MyLabel) RETURN count(n)" ) ); + StatementResultCursor cursor = sessionRun( session, new Statement( "MATCH (n:MyLabel) RETURN count(n)" ) ); assertEquals( 2, await( cursor.singleAsync() ).get( 0 ).asInt() ); } - @Test - void shouldFailToExecuteBlockingRunChainedWithAsyncTransaction() - { - CompletionStage result = session.beginTransactionAsync() - .thenApply( tx -> - { - if ( EventLoopGroupFactory.isEventLoopThread( Thread.currentThread() ) ) - { - IllegalStateException e = assertThrows( IllegalStateException.class, () -> ((ExplicitTransaction) tx).run( "CREATE ()" ) ); - assertThat( e, is( blockingOperationInEventLoopError() ) ); - } - return null; - } ); - - assertNull( await( result ) ); - } - - @Test - void shouldAllowUsingBlockingApiInCommonPoolWhenChaining() - { - CompletionStage txStage = session.beginTransactionAsync() - // move execution to ForkJoinPool.commonPool() - .thenApplyAsync( asyncTx -> - { - Transaction tx = (ExplicitTransaction) asyncTx; - tx.run( "UNWIND [1,1,2] AS x CREATE (:Node {id: x})" ); - tx.run( "CREATE (:Node {id: 42})" ); - tx.success(); - tx.close(); - return asyncTx; - } ); - - AsyncTransaction tx = await( txStage ); - - assertFalse( ((ExplicitTransaction) tx).isOpen() ); - assertEquals( 2, countNodes( 1 ) ); - assertEquals( 1, countNodes( 2 ) ); - assertEquals( 1, countNodes( 42 ) ); - } - @Test void shouldBePossibleToRunMoreTransactionsAfterOneIsTerminated() { - AsyncTransaction tx1 = await( session.beginTransactionAsync() ); - ((ExplicitTransaction) tx1).markTerminated(); + ExplicitTransaction tx1 = beginTransaction(); + tx1.markTerminated(); // commit should fail, make session forget about this transaction and release the connection to the pool ClientException e = assertThrows( ClientException.class, () -> await( tx1.commitAsync() ) ); assertThat( e.getMessage(), startsWith( "Transaction can't be committed" ) ); - await( session.beginTransactionAsync() - .thenCompose( tx -> tx.runAsync( "CREATE (:Node {id: 42})" ) + await( session.beginTransactionAsync( TransactionConfig.empty() ) + .thenCompose( tx -> tx.runAsync( new Statement( "CREATE (:Node {id: 42})" ), true ) .thenCompose( StatementResultCursor::consumeAsync ) .thenApply( ignore -> tx ) - ).thenCompose( AsyncTransaction::commitAsync ) ); + ).thenCompose( ExplicitTransaction::commitAsync ) ); assertEquals( 1, countNodes( 42 ) ); } @@ -263,7 +237,8 @@ void shouldPropagateRollbackFailureAfterFatalError() private int countNodes( Object id ) { - StatementResultCursor cursor = await( session.runAsync( "MATCH (n:Node {id: $id}) RETURN count(n)", parameters( "id", id ) ) ); + Statement statement = new Statement( "MATCH (n:Node {id: $id}) RETURN count(n)", parameters( "id", id ) ); + StatementResultCursor cursor = sessionRun( session, statement ); return await( cursor.singleAsync() ).get( 0 ).asInt(); } @@ -274,12 +249,12 @@ private void testCommitAndRollbackFailurePropagation( boolean commit ) try ( Driver driver = driverFactory.newInstance( neo4j.uri(), neo4j.authToken(), RoutingSettings.DEFAULT, RetrySettings.DEFAULT, config ) ) { - try ( Session session = driver.session() ) + NetworkSession session = ((InternalDriver) driver).newSession( SessionParameters.empty() ); { - Transaction tx = session.beginTransaction(); + ExplicitTransaction tx = beginTransaction( session ); // run query but do not consume the result - tx.run( "UNWIND range(0, 10000) AS x RETURN x + 1" ); + txRun( tx, "UNWIND range(0, 10000) AS x RETURN x + 1" ); IOException ioError = new IOException( "Connection reset by peer" ); for ( Channel channel : driverFactory.channels() ) @@ -290,8 +265,7 @@ private void testCommitAndRollbackFailurePropagation( boolean commit ) await( future ); } - AsyncTransaction asyncTx = (ExplicitTransaction) tx; - CompletionStage commitOrRollback = commit ? asyncTx.commitAsync() : asyncTx.rollbackAsync(); + CompletionStage commitOrRollback = commit ? tx.commitAsync() : tx.rollbackAsync(); // commit/rollback should fail and propagate the network error ServiceUnavailableException e = assertThrows( ServiceUnavailableException.class, () -> await( commitOrRollback ) ); diff --git a/driver/src/test/java/org/neo4j/driver/integration/SessionBoltV3IT.java b/driver/src/test/java/org/neo4j/driver/integration/SessionBoltV3IT.java index e3e5a319da..cddc6567e9 100644 --- a/driver/src/test/java/org/neo4j/driver/integration/SessionBoltV3IT.java +++ b/driver/src/test/java/org/neo4j/driver/integration/SessionBoltV3IT.java @@ -19,6 +19,7 @@ package org.neo4j.driver.integration; import io.netty.channel.Channel; +import org.hamcrest.MatcherAssert; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.RegisterExtension; @@ -29,6 +30,14 @@ import java.util.Map; import java.util.concurrent.CompletionStage; +import org.neo4j.driver.Driver; +import org.neo4j.driver.Session; +import org.neo4j.driver.StatementResult; +import org.neo4j.driver.Transaction; +import org.neo4j.driver.TransactionConfig; +import org.neo4j.driver.async.AsyncSession; +import org.neo4j.driver.async.StatementResultCursor; +import org.neo4j.driver.exceptions.TransientException; import org.neo4j.driver.internal.cluster.RoutingSettings; import org.neo4j.driver.internal.messaging.Message; import org.neo4j.driver.internal.messaging.request.GoodbyeMessage; @@ -36,16 +45,9 @@ import org.neo4j.driver.internal.retry.RetrySettings; import org.neo4j.driver.internal.util.EnabledOnNeo4jWith; import org.neo4j.driver.internal.util.MessageRecordingDriverFactory; -import org.neo4j.driver.Driver; -import org.neo4j.driver.Session; -import org.neo4j.driver.StatementResult; -import org.neo4j.driver.async.StatementResultCursor; -import org.neo4j.driver.Transaction; -import org.neo4j.driver.TransactionConfig; -import org.neo4j.driver.exceptions.TransientException; import org.neo4j.driver.summary.ResultSummary; +import org.neo4j.driver.util.DriverExtension; import org.neo4j.driver.util.ParallelizableIT; -import org.neo4j.driver.util.SessionExtension; import static java.time.Duration.ofMillis; import static java.util.Arrays.asList; @@ -57,8 +59,8 @@ import static org.junit.jupiter.api.Assertions.assertNotEquals; import static org.junit.jupiter.api.Assertions.assertNotNull; import static org.junit.jupiter.api.Assertions.assertThrows; -import static org.neo4j.driver.internal.util.Neo4jFeature.BOLT_V3; import static org.neo4j.driver.Config.defaultConfig; +import static org.neo4j.driver.internal.util.Neo4jFeature.BOLT_V3; import static org.neo4j.driver.util.TestUtil.await; @EnabledOnNeo4jWith( BOLT_V3 ) @@ -66,7 +68,7 @@ class SessionBoltV3IT { @RegisterExtension - static final SessionExtension session = new SessionExtension(); + static final DriverExtension driver = new DriverExtension(); @Test void shouldSetTransactionMetadata() @@ -81,7 +83,7 @@ void shouldSetTransactionMetadata() .build(); // call listTransactions procedure that should list itself with the specified metadata - StatementResult result = session.run( "CALL dbms.listTransactions()", config ); + StatementResult result = driver.session().run( "CALL dbms.listTransactions()", config ); Map receivedMetadata = result.single().get( "metaData" ).asMap(); assertEquals( metadata, receivedMetadata ); @@ -99,7 +101,8 @@ void shouldSetTransactionMetadataAsync() .build(); // call listTransactions procedure that should list itself with the specified metadata - CompletionStage> metadataFuture = session.runAsync( "CALL dbms.listTransactions()", config ) + CompletionStage> metadataFuture = driver.asyncSession() + .runAsync( "CALL dbms.listTransactions()", config ) .thenCompose( StatementResultCursor::singleAsync ) .thenApply( record -> record.get( "metaData" ).asMap() ); @@ -110,9 +113,10 @@ void shouldSetTransactionMetadataAsync() void shouldSetTransactionTimeout() { // create a dummy node + Session session = driver.session(); session.run( "CREATE (:Node)" ).consume(); - try ( Session otherSession = session.driver().session() ) + try ( Session otherSession = driver.driver().session() ) { try ( Transaction otherTx = otherSession.beginTransaction() ) { @@ -136,9 +140,10 @@ void shouldSetTransactionTimeout() void shouldSetTransactionTimeoutAsync() { // create a dummy node - session.run( "CREATE (:Node)" ).consume(); + AsyncSession asyncSession = driver.asyncSession(); + await( await( asyncSession.runAsync( "CREATE (:Node)" ) ).consumeAsync() ); - try ( Session otherSession = session.driver().session() ) + try ( Session otherSession = driver.driver().session() ) { try ( Transaction otherTx = otherSession.beginTransaction() ) { @@ -150,43 +155,44 @@ void shouldSetTransactionTimeoutAsync() .build(); // run a query in an auto-commit transaction with timeout and try to update the locked dummy node - CompletionStage resultFuture = session.runAsync( "MATCH (n:Node) SET n.prop = 2", config ) + CompletionStage resultFuture = asyncSession.runAsync( "MATCH (n:Node) SET n.prop = 2", config ) .thenCompose( StatementResultCursor::consumeAsync ); TransientException error = assertThrows( TransientException.class, () -> await( resultFuture ) ); - assertThat( error.getMessage(), containsString( "terminated" ) ); + MatcherAssert.assertThat( error.getMessage(), containsString( "terminated" ) ); } } } @Test - void shouldSetTransactionMetadataWithReadTransactionFunction() + void shouldSetTransactionMetadataWithAsyncReadTransactionFunction() { - testTransactionMetadataWithTransactionFunctions( true ); + testTransactionMetadataWithAsyncTransactionFunctions( true ); } @Test - void shouldSetTransactionMetadataWithWriteTransactionFunction() + void shouldSetTransactionMetadataWithAsyncWriteTransactionFunction() { - testTransactionMetadataWithTransactionFunctions( false ); + testTransactionMetadataWithAsyncTransactionFunctions( false ); } @Test - void shouldSetTransactionMetadataWithAsyncReadTransactionFunction() + void shouldSetTransactionMetadataWithReadTransactionFunction() { - testTransactionMetadataWithAsyncTransactionFunctions( true ); + testTransactionMetadataWithTransactionFunctions( true ); } @Test - void shouldSetTransactionMetadataWithAsyncWriteTransactionFunction() + void shouldSetTransactionMetadataWithWriteTransactionFunction() { - testTransactionMetadataWithAsyncTransactionFunctions( false ); + testTransactionMetadataWithTransactionFunctions( false ); } @Test void shouldUseBookmarksForAutoCommitTransactions() { + Session session = driver.session(); String initialBookmark = session.lastBookmark(); session.run( "CREATE ()" ).consume(); @@ -211,6 +217,7 @@ void shouldUseBookmarksForAutoCommitTransactions() @Test void shouldUseBookmarksForAutoCommitAndExplicitTransactions() { + Session session = driver.session(); String initialBookmark = session.lastBookmark(); try ( Transaction tx = session.beginTransaction() ) @@ -243,6 +250,7 @@ void shouldUseBookmarksForAutoCommitAndExplicitTransactions() @Test void shouldUseBookmarksForAutoCommitTransactionsAndTransactionFunctions() { + Session session = driver.session(); String initialBookmark = session.lastBookmark(); session.writeTransaction( tx -> tx.run( "CREATE ()" ) ); @@ -270,13 +278,13 @@ void shouldSendGoodbyeWhenClosingDriver() int txCount = 13; MessageRecordingDriverFactory driverFactory = new MessageRecordingDriverFactory(); - try ( Driver driver = driverFactory.newInstance( session.uri(), session.authToken(), RoutingSettings.DEFAULT, RetrySettings.DEFAULT, defaultConfig() ) ) + try ( Driver otherDriver = driverFactory.newInstance( driver.uri(), driver.authToken(), RoutingSettings.DEFAULT, RetrySettings.DEFAULT, defaultConfig() ) ) { List sessions = new ArrayList<>(); List txs = new ArrayList<>(); for ( int i = 0; i < txCount; i++ ) { - Session session = driver.session(); + Session session = otherDriver.session(); sessions.add( session ); Transaction tx = session.beginTransaction(); txs.add( tx ); @@ -305,8 +313,9 @@ void shouldSendGoodbyeWhenClosingDriver() } } - private static void testTransactionMetadataWithTransactionFunctions( boolean read ) + private static void testTransactionMetadataWithAsyncTransactionFunctions( boolean read ) { + AsyncSession asyncSession = driver.asyncSession(); Map metadata = new HashMap<>(); metadata.put( "foo", "bar" ); metadata.put( "baz", true ); @@ -317,16 +326,19 @@ private static void testTransactionMetadataWithTransactionFunctions( boolean rea .build(); // call listTransactions procedure that should list itself with the specified metadata - StatementResult result = read ? session.readTransaction( tx -> tx.run( "CALL dbms.listTransactions()" ), config ) - : session.writeTransaction( tx -> tx.run( "CALL dbms.listTransactions()" ), config ); + CompletionStage cursorFuture = + read ? asyncSession.readTransactionAsync( tx -> tx.runAsync( "CALL dbms.listTransactions()" ), config ) + : asyncSession.writeTransactionAsync( tx -> tx.runAsync( "CALL dbms.listTransactions()" ), config ); - Map receivedMetadata = result.single().get( "metaData" ).asMap(); + CompletionStage> metadataFuture = cursorFuture.thenCompose( StatementResultCursor::singleAsync ) + .thenApply( record -> record.get( "metaData" ).asMap() ); - assertEquals( metadata, receivedMetadata ); + assertEquals( metadata, await( metadataFuture ) ); } - private static void testTransactionMetadataWithAsyncTransactionFunctions( boolean read ) + private static void testTransactionMetadataWithTransactionFunctions( boolean read ) { + Session session = driver.session(); Map metadata = new HashMap<>(); metadata.put( "foo", "bar" ); metadata.put( "baz", true ); @@ -337,14 +349,12 @@ private static void testTransactionMetadataWithAsyncTransactionFunctions( boolea .build(); // call listTransactions procedure that should list itself with the specified metadata - CompletionStage cursorFuture = - read ? session.readTransactionAsync( tx -> tx.runAsync( "CALL dbms.listTransactions()" ), config ) - : session.writeTransactionAsync( tx -> tx.runAsync( "CALL dbms.listTransactions()" ), config ); + StatementResult result = read ? session.readTransaction( tx -> tx.run( "CALL dbms.listTransactions()" ), config ) + : session.writeTransaction( tx -> tx.run( "CALL dbms.listTransactions()" ), config ); - CompletionStage> metadataFuture = cursorFuture.thenCompose( StatementResultCursor::singleAsync ) - .thenApply( record -> record.get( "metaData" ).asMap() ); + Map receivedMetadata = result.single().get( "metaData" ).asMap(); - assertEquals( metadata, await( metadataFuture ) ); + assertEquals( metadata, receivedMetadata ); } } diff --git a/driver/src/test/java/org/neo4j/driver/integration/NetworkSessionIT.java b/driver/src/test/java/org/neo4j/driver/integration/SessionMixIT.java similarity index 62% rename from driver/src/test/java/org/neo4j/driver/integration/NetworkSessionIT.java rename to driver/src/test/java/org/neo4j/driver/integration/SessionMixIT.java index c060bd1c70..70274c759f 100644 --- a/driver/src/test/java/org/neo4j/driver/integration/NetworkSessionIT.java +++ b/driver/src/test/java/org/neo4j/driver/integration/SessionMixIT.java @@ -28,12 +28,15 @@ import java.util.concurrent.CompletionStage; import org.neo4j.driver.Record; +import org.neo4j.driver.Session; +import org.neo4j.driver.Statement; import org.neo4j.driver.StatementResult; +import org.neo4j.driver.TransactionConfig; +import org.neo4j.driver.async.AsyncSession; +import org.neo4j.driver.async.AsyncTransaction; import org.neo4j.driver.async.AsyncTransactionWork; import org.neo4j.driver.async.StatementResultCursor; -import org.neo4j.driver.internal.ExplicitTransaction; -import org.neo4j.driver.internal.NetworkSession; -import org.neo4j.driver.internal.async.EventLoopGroupFactory; +import org.neo4j.driver.internal.async.connection.EventLoopGroupFactory; import org.neo4j.driver.internal.util.Futures; import org.neo4j.driver.types.Node; import org.neo4j.driver.util.DatabaseExtension; @@ -49,48 +52,77 @@ import static org.neo4j.driver.internal.util.Matchers.blockingOperationInEventLoopError; import static org.neo4j.driver.util.TestUtil.await; -/** - * We leave the question whether we want to let {@link NetworkSession} both implements blocking session and async session or not later. - * But as how it is right not, here are some tests for using mixes blocking and async API. - */ @ParallelizableIT -class NetworkSessionIT +class SessionMixIT { @RegisterExtension static final DatabaseExtension neo4j = new DatabaseExtension(); - private NetworkSession session; + private AsyncSession asyncSession; + private Session session; @BeforeEach void setUp() { + asyncSession = newAsyncSession(); session = newSession(); } @AfterEach void tearDown() { - session.closeAsync(); + await( asyncSession.closeAsync() ); + session.close(); + } + + private AsyncSession newAsyncSession() + { + return neo4j.driver().asyncSession(); } - private NetworkSession newSession() + private Session newSession() { - return (NetworkSession) neo4j.driver().session(); + return neo4j.driver().session(); } @Test - void shouldBePossibleToMixRunAsyncAndBlockingSessionClose() + void shouldFailToExecuteBlockingRunChainedWithAsyncTransaction() { - long nodeCount = 5_000; + CompletionStage result = asyncSession.beginTransactionAsync( TransactionConfig.empty() ) + .thenApply( tx -> + { + if ( EventLoopGroupFactory.isEventLoopThread( Thread.currentThread() ) ) + { + IllegalStateException e = assertThrows( IllegalStateException.class, () -> session.run( "CREATE ()" ) ); + assertThat( e, is( blockingOperationInEventLoopError() ) ); + } + return null; + } ); - try ( NetworkSession session = newSession() ) - { - session.runAsync( "UNWIND range(1, " + nodeCount + ") AS x CREATE (n:AsyncNode {x: x}) RETURN n" ); - } + assertNull( await( result ) ); + } - assertEquals( nodeCount, countNodesByLabel( "AsyncNode" ) ); + @Test + void shouldAllowUsingBlockingApiInCommonPoolWhenChaining() + { + CompletionStage txStage = asyncSession.beginTransactionAsync() + // move execution to ForkJoinPool.commonPool() + .thenApplyAsync( tx -> + { + session.run( "UNWIND [1,1,2] AS x CREATE (:Node {id: x})" ).consume(); + session.run( "CREATE (:Node {id: 42})" ).consume(); + tx.commitAsync(); + return tx; + } ); + + await( txStage ); + + assertEquals( 2, countNodes( 1 ) ); + assertEquals( 1, countNodes( 2 ) ); + assertEquals( 1, countNodes( 42 ) ); } + @Test void shouldFailToExecuteBlockingRunInAsyncTransactionFunction() { @@ -98,21 +130,21 @@ void shouldFailToExecuteBlockingRunInAsyncTransactionFunction() if ( EventLoopGroupFactory.isEventLoopThread( Thread.currentThread() ) ) { IllegalStateException e = assertThrows( IllegalStateException.class, - () -> ((ExplicitTransaction) tx).run( "UNWIND range(1, 10000) AS x CREATE (n:AsyncNode {x: x}) RETURN n" ) ); + () -> session.run( "UNWIND range(1, 10000) AS x CREATE (n:AsyncNode {x: x}) RETURN n" ) ); assertThat( e, is( blockingOperationInEventLoopError() ) ); } return completedFuture( null ); }; - CompletionStage result = session.readTransactionAsync( completionStageTransactionWork ); + CompletionStage result = asyncSession.readTransactionAsync( completionStageTransactionWork ); assertNull( await( result ) ); } @Test void shouldFailToExecuteBlockingRunChainedWithAsyncRun() { - CompletionStage result = session.runAsync( "RETURN 1" ).thenCompose( StatementResultCursor::singleAsync ).thenApply( record -> { + CompletionStage result = asyncSession.runAsync( "RETURN 1" ).thenCompose( StatementResultCursor::singleAsync ).thenApply( record -> { if ( EventLoopGroupFactory.isEventLoopThread( Thread.currentThread() ) ) { IllegalStateException e = @@ -129,10 +161,11 @@ void shouldFailToExecuteBlockingRunChainedWithAsyncRun() @Test void shouldAllowBlockingOperationInCommonPoolWhenChaining() { - CompletionStage nodeStage = session.runAsync( "RETURN 42 AS value" ).thenCompose( StatementResultCursor::singleAsync ) + CompletionStage nodeStage = asyncSession.runAsync( "RETURN 42 AS value" ).thenCompose( StatementResultCursor::singleAsync ) // move execution to ForkJoinPool.commonPool() - .thenApplyAsync( record -> session.run( "CREATE (n:Node {value: $value}) RETURN n", record ) ).thenApply( StatementResult::single ).thenApply( - record -> record.get( 0 ).asNode() ); + .thenApplyAsync( record -> session.run( "CREATE (n:Node {value: $value}) RETURN n", record ) ) + .thenApply( StatementResult::single ) + .thenApply( record -> record.get( 0 ).asNode() ); Node node = await( nodeStage ); @@ -170,7 +203,7 @@ private void runNestedQuery( StatementResultCursor inputCursor, Record record, L long age = id * 10; CompletionStage response = - session.runAsync( "MATCH (p:Person {id: $id}) SET p.age = $age RETURN p", parameters( "id", id, "age", age ) ); + asyncSession.runAsync( "MATCH (p:Person {id: $id}) SET p.age = $age RETURN p", parameters( "id", id, "age", age ) ); response.whenComplete( ( cursor, error ) -> { if ( error != null ) @@ -188,9 +221,16 @@ private void runNestedQuery( StatementResultCursor inputCursor, Record record, L private long countNodesByLabel( String label ) { CompletionStage countStage = - session.runAsync( "MATCH (n:" + label + ") RETURN count(n)" ).thenCompose( StatementResultCursor::singleAsync ).thenApply( + asyncSession.runAsync( "MATCH (n:" + label + ") RETURN count(n)" ).thenCompose( StatementResultCursor::singleAsync ).thenApply( record -> record.get( 0 ).asLong() ); return await( countStage ); } + + private int countNodes( Object id ) + { + Statement statement = new Statement( "MATCH (n:Node {id: $id}) RETURN count(n)", parameters( "id", id ) ); + Record record = session.run( statement ).single(); + return record.get( 0 ).asInt(); + } } diff --git a/driver/src/test/java/org/neo4j/driver/integration/TransactionBoltV3IT.java b/driver/src/test/java/org/neo4j/driver/integration/TransactionBoltV3IT.java index b550839375..61949664df 100644 --- a/driver/src/test/java/org/neo4j/driver/integration/TransactionBoltV3IT.java +++ b/driver/src/test/java/org/neo4j/driver/integration/TransactionBoltV3IT.java @@ -31,12 +31,13 @@ import org.neo4j.driver.Transaction; import org.neo4j.driver.TransactionConfig; import org.neo4j.driver.Value; +import org.neo4j.driver.async.AsyncSession; import org.neo4j.driver.async.AsyncTransaction; import org.neo4j.driver.async.StatementResultCursor; import org.neo4j.driver.exceptions.TransientException; import org.neo4j.driver.internal.util.EnabledOnNeo4jWith; +import org.neo4j.driver.util.DriverExtension; import org.neo4j.driver.util.ParallelizableIT; -import org.neo4j.driver.util.SessionExtension; import static java.time.Duration.ofMillis; import static org.hamcrest.MatcherAssert.assertThat; @@ -51,7 +52,7 @@ class TransactionBoltV3IT { @RegisterExtension - static final SessionExtension session = new SessionExtension(); + static final DriverExtension driver = new DriverExtension(); @Test void shouldSetTransactionMetadata() @@ -65,7 +66,7 @@ void shouldSetTransactionMetadata() .withMetadata( metadata ) .build(); - try ( Transaction tx = session.beginTransaction( config ) ) + try ( Transaction tx = driver.session().beginTransaction( config ) ) { tx.run( "RETURN 1" ).consume(); @@ -84,7 +85,7 @@ void shouldSetTransactionMetadataAsync() .withMetadata( metadata ) .build(); - CompletionStage txFuture = session.beginTransactionAsync( config ) + CompletionStage txFuture = driver.asyncSession().beginTransactionAsync( config ) .thenCompose( tx -> tx.runAsync( "RETURN 1" ) .thenCompose( StatementResultCursor::consumeAsync ) .thenApply( ignore -> tx ) ); @@ -104,9 +105,10 @@ void shouldSetTransactionMetadataAsync() void shouldSetTransactionTimeout() { // create a dummy node + Session session = driver.session(); session.run( "CREATE (:Node)" ).consume(); - try ( Session otherSession = session.driver().session() ) + try ( Session otherSession = driver.driver().session() ) { try ( Transaction otherTx = otherSession.beginTransaction() ) { @@ -136,9 +138,12 @@ void shouldSetTransactionTimeout() void shouldSetTransactionTimeoutAsync() { // create a dummy node + Session session = driver.session(); + AsyncSession asyncSession = driver.asyncSession(); + session.run( "CREATE (:Node)" ).consume(); - try ( Session otherSession = session.driver().session() ) + try ( Session otherSession = driver.driver().session() ) { try ( Transaction otherTx = otherSession.beginTransaction() ) { @@ -150,7 +155,7 @@ void shouldSetTransactionTimeoutAsync() .build(); // start a new transaction with timeout and try to update the locked dummy node - CompletionStage txCommitFuture = session.beginTransactionAsync( config ) + CompletionStage txCommitFuture = asyncSession.beginTransactionAsync( config ) .thenCompose( tx -> tx.runAsync( "MATCH (n:Node) SET n.prop = 2" ) .thenCompose( ignore -> tx.commitAsync() ) ); @@ -162,7 +167,7 @@ void shouldSetTransactionTimeoutAsync() private static void verifyTransactionMetadata( Map metadata ) { - try ( Session session = TransactionBoltV3IT.session.driver().session() ) + try ( Session session = driver.driver().session() ) { StatementResult result = session.run( "CALL dbms.listTransactions()" ); diff --git a/driver/src/test/java/org/neo4j/driver/integration/UnsupportedBoltV3IT.java b/driver/src/test/java/org/neo4j/driver/integration/UnsupportedBoltV3IT.java index 770ac5d057..f601b093dc 100644 --- a/driver/src/test/java/org/neo4j/driver/integration/UnsupportedBoltV3IT.java +++ b/driver/src/test/java/org/neo4j/driver/integration/UnsupportedBoltV3IT.java @@ -24,11 +24,11 @@ import java.util.concurrent.CompletionStage; -import org.neo4j.driver.internal.util.DisabledOnNeo4jWith; import org.neo4j.driver.TransactionConfig; import org.neo4j.driver.exceptions.ClientException; +import org.neo4j.driver.internal.util.DisabledOnNeo4jWith; +import org.neo4j.driver.util.DriverExtension; import org.neo4j.driver.util.ParallelizableIT; -import org.neo4j.driver.util.SessionExtension; import static java.time.Duration.ofSeconds; import static java.util.Collections.singletonMap; @@ -43,7 +43,7 @@ class UnsupportedBoltV3IT { @RegisterExtension - static final SessionExtension session = new SessionExtension(); + static final DriverExtension driver = new DriverExtension(); private final TransactionConfig txConfig = TransactionConfig.builder() .withTimeout( ofSeconds( 4 ) ) @@ -53,37 +53,37 @@ class UnsupportedBoltV3IT @Test void shouldNotSupportAutoCommitQueriesWithTransactionConfig() { - assertTxConfigNotSupported( () -> session.run( "RETURN 42", txConfig ) ); + assertTxConfigNotSupported( () -> driver.session().run( "RETURN 42", txConfig ) ); } @Test void shouldNotSupportAsyncAutoCommitQueriesWithTransactionConfig() { - assertTxConfigNotSupported( session.runAsync( "RETURN 42", txConfig ) ); + assertTxConfigNotSupported( driver.asyncSession().runAsync( "RETURN 42", txConfig ) ); } @Test void shouldNotSupportTransactionFunctionsWithTransactionConfig() { - assertTxConfigNotSupported( () -> session.readTransaction( tx -> tx.run( "RETURN 42" ), txConfig ) ); + assertTxConfigNotSupported( () -> driver.session().readTransaction( tx -> tx.run( "RETURN 42" ), txConfig ) ); } @Test void shouldNotSupportAsyncTransactionFunctionsWithTransactionConfig() { - assertTxConfigNotSupported( session.readTransactionAsync( tx -> tx.runAsync( "RETURN 42" ), txConfig ) ); + assertTxConfigNotSupported( driver.asyncSession().readTransactionAsync( tx -> tx.runAsync( "RETURN 42" ), txConfig ) ); } @Test void shouldNotSupportExplicitTransactionsWithTransactionConfig() { - assertTxConfigNotSupported( () -> session.beginTransaction( txConfig ) ); + assertTxConfigNotSupported( () -> driver.session().beginTransaction( txConfig ) ); } @Test void shouldNotSupportAsyncExplicitTransactionsWithTransactionConfig() { - assertTxConfigNotSupported( session.beginTransactionAsync( txConfig ) ); + assertTxConfigNotSupported( driver.asyncSession().beginTransactionAsync( txConfig ) ); } /** diff --git a/driver/src/test/java/org/neo4j/driver/integration/reactive/RxSessionIT.java b/driver/src/test/java/org/neo4j/driver/integration/reactive/RxSessionIT.java index 1302894e8f..0f5531a802 100644 --- a/driver/src/test/java/org/neo4j/driver/integration/reactive/RxSessionIT.java +++ b/driver/src/test/java/org/neo4j/driver/integration/reactive/RxSessionIT.java @@ -20,16 +20,36 @@ import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.RegisterExtension; +import org.reactivestreams.Publisher; import reactor.core.publisher.Flux; +import reactor.core.publisher.Mono; import reactor.test.StepVerifier; +import java.util.Arrays; +import java.util.Iterator; +import java.util.Set; +import java.util.concurrent.atomic.AtomicInteger; + +import org.neo4j.driver.Session; +import org.neo4j.driver.StatementResult; +import org.neo4j.driver.exceptions.ClientException; +import org.neo4j.driver.exceptions.DatabaseException; +import org.neo4j.driver.exceptions.ServiceUnavailableException; +import org.neo4j.driver.exceptions.SessionExpiredException; +import org.neo4j.driver.exceptions.TransientException; import org.neo4j.driver.internal.util.EnabledOnNeo4jWith; import org.neo4j.driver.reactive.RxResult; import org.neo4j.driver.reactive.RxSession; -import org.neo4j.driver.exceptions.ClientException; +import org.neo4j.driver.reactive.RxTransaction; +import org.neo4j.driver.reactive.RxTransactionWork; import org.neo4j.driver.util.DatabaseExtension; import org.neo4j.driver.util.ParallelizableIT; +import static java.util.Collections.emptyIterator; +import static org.hamcrest.CoreMatchers.instanceOf; +import static org.hamcrest.CoreMatchers.not; +import static org.hamcrest.CoreMatchers.startsWith; +import static org.hamcrest.MatcherAssert.assertThat; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.neo4j.driver.internal.util.Neo4jFeature.BOLT_V4; @@ -74,4 +94,160 @@ void shouldBeAbleToReuseSessionAfterFailure() assertEquals( record.get("1").asLong(), 1L ); } ).expectComplete().verify(); } + + @Test + void shouldRunAsyncTransactionWithoutRetries() + { + RxSession session = neo4j.driver().rxSession(); + InvocationTrackingWork work = new InvocationTrackingWork( "CREATE (:Apa) RETURN 42" ); + Publisher publisher = session.writeTransaction( work ); + + StepVerifier.create( publisher ).expectNext( 42 ).verifyComplete(); + + assertEquals( 1, work.invocationCount() ); + assertEquals( 1, countNodesByLabel( "Apa" ) ); + } + + @Test + void shouldRunAsyncTransactionWithRetriesOnAsyncFailures() + { + RxSession session = neo4j.driver().rxSession(); + InvocationTrackingWork work = new InvocationTrackingWork( "CREATE (:Node) RETURN 24" ).withAsyncFailures( + new ServiceUnavailableException( "Oh!" ), + new SessionExpiredException( "Ah!" ), + new TransientException( "Code", "Message" ) ); + + Publisher publisher = session.writeTransaction( work ); + StepVerifier.create( publisher ).expectNext( 24 ).verifyComplete(); + + assertEquals( 4, work.invocationCount() ); + assertEquals( 1, countNodesByLabel( "Node" ) ); + assertNoParallelScheduler(); + } + + @Test + void shouldRunAsyncTransactionWithRetriesOnSyncFailures() + { + RxSession session = neo4j.driver().rxSession(); + InvocationTrackingWork work = new InvocationTrackingWork( "CREATE (:Test) RETURN 12" ).withSyncFailures( + new TransientException( "Oh!", "Deadlock!" ), + new ServiceUnavailableException( "Oh! Network Failure" ) ); + + Publisher publisher = session.writeTransaction( work ); + StepVerifier.create( publisher ).expectNext( 12 ).verifyComplete(); + + assertEquals( 3, work.invocationCount() ); + assertEquals( 1, countNodesByLabel( "Test" ) ); + assertNoParallelScheduler(); + } + + @Test + void shouldRunAsyncTransactionThatCanNotBeRetried() + { + RxSession session = neo4j.driver().rxSession(); + InvocationTrackingWork work = new InvocationTrackingWork( "UNWIND [10, 5, 0] AS x CREATE (:Hi) RETURN 10/x" ); + Publisher publisher = session.writeTransaction( work ); + + StepVerifier.create( publisher ) + .expectNext( 1 ).expectNext( 2 ) + .expectErrorSatisfies( error -> assertThat( error, instanceOf( ClientException.class ) ) ) + .verify(); + + assertEquals( 1, work.invocationCount() ); + assertEquals( 0, countNodesByLabel( "Hi" ) ); + assertNoParallelScheduler(); + } + + @Test + void shouldRunAsyncTransactionThatCanNotBeRetriedAfterATransientFailure() + { + RxSession session = neo4j.driver().rxSession(); + // first throw TransientException directly from work, retry can happen afterwards + // then return a future failed with DatabaseException, retry can't happen afterwards + InvocationTrackingWork work = new InvocationTrackingWork( "CREATE (:Person) RETURN 1" ) + .withSyncFailures( new TransientException( "Oh!", "Deadlock!" ) ) + .withAsyncFailures( new DatabaseException( "Oh!", "OutOfMemory!" ) ); + Publisher publisher = session.writeTransaction( work ); + + StepVerifier.create( publisher ) + .expectErrorSatisfies( e -> { + assertThat( e, instanceOf( DatabaseException.class ) ); + assertEquals( 1, e.getSuppressed().length ); + assertThat( e.getSuppressed()[0], instanceOf( TransientException.class ) ); + } ) + .verify(); + + assertEquals( 2, work.invocationCount() ); + assertEquals( 0, countNodesByLabel( "Person" ) ); + assertNoParallelScheduler(); + } + + private void assertNoParallelScheduler() + { + Set threadSet = Thread.getAllStackTraces().keySet(); + for ( Thread t : threadSet ) + { + String name = t.getName(); + assertThat( name, not( startsWith( "parallel" ) ) ); + } + } + + private long countNodesByLabel( String label ) + { + try ( Session session = neo4j.driver().session() ) + { + StatementResult result = session.run( "MATCH (n:" + label + ") RETURN count(n)" ); + return result.single().get( 0 ).asLong(); + } + } + + private static class InvocationTrackingWork implements RxTransactionWork> + { + final String query; + final AtomicInteger invocationCount; + + Iterator asyncFailures = emptyIterator(); + Iterator syncFailures = emptyIterator(); + + InvocationTrackingWork( String query ) + { + this.query = query; + this.invocationCount = new AtomicInteger(); + } + + InvocationTrackingWork withAsyncFailures( RuntimeException... failures ) + { + asyncFailures = Arrays.asList( failures ).iterator(); + return this; + } + + InvocationTrackingWork withSyncFailures( RuntimeException... failures ) + { + syncFailures = Arrays.asList( failures ).iterator(); + return this; + } + + int invocationCount() + { + return invocationCount.get(); + } + + @Override + public Publisher execute( RxTransaction tx ) + { + invocationCount.incrementAndGet(); + + if ( syncFailures.hasNext() ) + { + throw syncFailures.next(); + } + + if ( asyncFailures.hasNext() ) + { + return Mono.error( asyncFailures.next() ); + } + + return Flux.from( tx.run( query ).records() ).map( r -> r.get( 0 ).asInt() ); + } + } } diff --git a/driver/src/test/java/org/neo4j/driver/integration/reactive/RxTransactionIT.java b/driver/src/test/java/org/neo4j/driver/integration/reactive/RxTransactionIT.java index d051b6d916..b2e1ead44f 100644 --- a/driver/src/test/java/org/neo4j/driver/integration/reactive/RxTransactionIT.java +++ b/driver/src/test/java/org/neo4j/driver/integration/reactive/RxTransactionIT.java @@ -38,15 +38,15 @@ import java.util.concurrent.atomic.AtomicInteger; import java.util.stream.Stream; -import org.neo4j.driver.internal.util.EnabledOnNeo4jWith; -import org.neo4j.driver.reactive.RxResult; -import org.neo4j.driver.reactive.RxSession; -import org.neo4j.driver.reactive.RxTransaction; import org.neo4j.driver.Record; import org.neo4j.driver.Statement; import org.neo4j.driver.Value; import org.neo4j.driver.exceptions.ClientException; import org.neo4j.driver.exceptions.ServiceUnavailableException; +import org.neo4j.driver.internal.util.EnabledOnNeo4jWith; +import org.neo4j.driver.reactive.RxResult; +import org.neo4j.driver.reactive.RxSession; +import org.neo4j.driver.reactive.RxTransaction; import org.neo4j.driver.summary.ResultSummary; import org.neo4j.driver.summary.StatementType; import org.neo4j.driver.types.Node; @@ -67,11 +67,11 @@ import static org.junit.jupiter.api.Assertions.assertNull; import static org.junit.jupiter.api.Assertions.assertThrows; import static org.junit.jupiter.api.Assertions.assertTrue; +import static org.neo4j.driver.Values.parameters; import static org.neo4j.driver.internal.util.Iterables.single; import static org.neo4j.driver.internal.util.Matchers.containsResultAvailableAfterAndResultConsumedAfter; import static org.neo4j.driver.internal.util.Matchers.syntaxError; import static org.neo4j.driver.internal.util.Neo4jFeature.BOLT_V4; -import static org.neo4j.driver.Values.parameters; import static org.neo4j.driver.util.TestUtil.await; @EnabledOnNeo4jWith( BOLT_V4 ) @@ -118,14 +118,10 @@ void shouldBePossibleToRollbackEmptyTx() @Test void shouldBePossibleToRunSingleStatementAndCommit() { - - Flux ids = Mono.from( session.beginTransaction() ).flatMapMany( tx -> { - RxResult result = tx.run( "CREATE (n:Node {id: 42}) RETURN n" ); - return Flux.from( result.records() ) - .map( record -> record.get( 0 ).asNode().get( "id" ).asInt() ) - .concatWith( tx.commit() ) - .onErrorResume( error -> Mono.from( tx.rollback() ).then( Mono.error( error ) ) ); - } ); + Flux ids = Flux.usingWhen( session.beginTransaction(), + tx -> Flux.from( tx.run( "CREATE (n:Node {id: 42}) RETURN n" ).records() ) + .map( record -> record.get( 0 ).asNode().get( "id" ).asInt() ), + RxTransaction::commit, RxTransaction::rollback ); StepVerifier.create( ids ).expectNext( 42 ).verifyComplete(); assertEquals( 1, countNodes( 42 ) ); @@ -309,13 +305,9 @@ void shouldFailToRollbackWhenCommitted() @Test void shouldAllowRollbackAfterFailedCommit() { - Flux records = Mono.from( session.beginTransaction() ).flatMapMany( tx -> { - RxResult result = tx.run( "WRONG" ); - return Flux.from( result.records() ) - .concatWith( tx.commit() ) // if we completed without error then we commit - .onErrorResume( error -> Mono.from( tx.rollback() ) // otherwise we rollback and then return the original error - .then( Mono.error( error ) ) ); - } ); + Flux records = Flux.usingWhen( session.beginTransaction(), + tx -> Flux.from( tx.run( "WRONG" ).records() ), + RxTransaction::commit, RxTransaction::rollback ); StepVerifier.create( records ).verifyErrorSatisfies( error -> assertThat( error.getMessage(), containsString( "Invalid input" ) ) ); @@ -465,17 +457,12 @@ void shouldFailForEachWhenActionFails() { RuntimeException e = new RuntimeException(); - Flux records = Mono.from( session.beginTransaction() ).flatMapMany( tx -> { - RxResult result = tx.run( "RETURN 'Hi!'" ); - return Flux.from( result.records() ) - .doOnNext( record -> { throw e; } ) - .concatWith( tx.commit() ) - .onErrorResume( error -> Mono.from( tx.rollback() ).then( Mono.error( error ) ) ); - } ); + Flux records = Flux.usingWhen( session.beginTransaction(), + tx -> Flux.from( tx.run( "RETURN 'Hi!'" ).records() ).doOnNext( record -> { throw e; } ), + RxTransaction::commit, + RxTransaction::rollback ); - StepVerifier.create( records ).expectErrorSatisfies( error -> { - assertEquals( e, error ); - } ).verify(); + StepVerifier.create( records ).expectErrorSatisfies( error -> assertEquals( e, error ) ).verify(); } @Test @@ -516,14 +503,9 @@ void shouldFailWhenListTransformationFunctionFails() { RuntimeException e = new RuntimeException(); - Flux records = Mono.from( session.beginTransaction() ).flatMapMany( tx -> { - RxResult result = tx.run( "RETURN 'Hi!'" ); - return Flux.from( result.records() ) - .map( record -> { throw e; } ) - .concatWith( tx.commit() ) - .onErrorResume( error -> Mono.from( tx.rollback() ).then( Mono.error( error ) ) ); - - } ); + Flux records = Flux.usingWhen( session.beginTransaction(), + tx -> Flux.from( tx.run( "RETURN 'Hi!'" ).records() ).map( record -> { throw e; } ), + RxTransaction::commit, RxTransaction::rollback ); StepVerifier.create( records ).expectErrorSatisfies( error -> { assertEquals( e, error ); @@ -715,12 +697,10 @@ void shouldUpdateSessionBookmarkAfterCommit() { String bookmarkBefore = session.lastBookmark(); - RxTransaction tx = await( Mono.from( session.beginTransaction() ) ); - RxResult result = tx.run( "CREATE (:MyNode)" ); - await( Flux.from( result.records() ) - .concatWith( tx.commit() ) - .onErrorResume( error -> Mono.from( tx.rollback() ).then( Mono.error( error ) ) ) ); - + await( Flux.usingWhen( session.beginTransaction(), + tx -> tx.run( "CREATE (:MyNode)" ).records(), + RxTransaction::commit, + RxTransaction::rollback ) ); String bookmarkAfter = session.lastBookmark(); @@ -822,19 +802,17 @@ void shouldHandleNestedQueries() throws Throwable { int size = 12555; - Flux nodeIds = Mono.from( session.beginTransaction() ).flatMapMany( tx -> { - RxResult result = tx.run( "UNWIND range(1, $size) AS x RETURN x", Collections.singletonMap( "size", size ) ); - return Flux.from( result.records() ) - .limitRate( 20 ) // batch size - .flatMap( record -> { + Flux nodeIds = Flux.usingWhen( session.beginTransaction(), + tx -> { + RxResult result = tx.run( "UNWIND range(1, $size) AS x RETURN x", Collections.singletonMap( "size", size ) ); + return Flux.from( result.records() ).limitRate( 20 ).flatMap( record -> { int x = record.get( "x" ).asInt(); RxResult innerResult = tx.run( "CREATE (n:Node {id: $x}) RETURN n.id", Collections.singletonMap( "x", x ) ); return innerResult.records(); - } ) - .concatWith( tx.commit() ) - .onErrorResume( error -> Mono.from( tx.rollback() ).then( Mono.error( error ) ) ) - .map( record -> record.get( 0 ).asInt() ); - } ); + } ).map( record -> record.get( 0 ).asInt() ); + }, + RxTransaction::commit, RxTransaction::rollback + ); StepVerifier.create( nodeIds ).expectNextCount( size ).verifyComplete(); } @@ -847,7 +825,8 @@ private int countNodes( Object id ) private void testForEach( String query, int expectedSeenRecords ) { - Flux summary = Mono.from( session.beginTransaction() ).flatMapMany( tx -> { + + Flux summary = Flux.usingWhen( session.beginTransaction(), tx -> { RxResult result = tx.run( query ); AtomicInteger recordsSeen = new AtomicInteger(); return Flux.from( result.records() ) @@ -858,10 +837,8 @@ private void testForEach( String query, int expectedSeenRecords ) assertEquals( query, s.statement().text() ); assertEquals( emptyMap(), s.statement().parameters().asMap() ); assertEquals( expectedSeenRecords, recordsSeen.get() ); - } ) - .concatWith( tx.commit() ) - .onErrorResume( error -> Mono.from( tx.rollback() ).then( Mono.error( error ) ) ); - } ); + } ); + }, RxTransaction::commit, RxTransaction::rollback ); StepVerifier.create( summary ).expectNextCount( 1 ).verifyComplete(); // we indeed get a summary. } @@ -870,13 +847,10 @@ private void testList( String query, List expectedList ) { List actualList = new ArrayList<>(); - Flux> records = Mono.from( session.beginTransaction() ).flatMapMany( tx -> { - RxResult result = tx.run( query ); - return Flux.from( result.records() ) - .collectList() - .concatWith( tx.commit() ) - .onErrorResume( error -> Mono.from( tx.rollback() ).then( Mono.error( error ) ) ); - } ); + Flux> records = Flux.usingWhen( session.beginTransaction(), + tx -> Flux.from( tx.run( query ).records() ).collectList(), + RxTransaction::commit, + RxTransaction::rollback ); StepVerifier.create( records.single() ).consumeNextWith( allRecords -> { for ( Record record : allRecords ) @@ -890,13 +864,11 @@ private void testList( String query, List expectedList ) private void testConsume( String query ) { - Flux summary = Mono.from( session.beginTransaction() ).flatMapMany( tx -> { - RxResult result = tx.run( query ); - return Mono.from( result.summary() ) - .concatWith( tx.commit() ) - .onErrorResume( error -> Mono.from( tx.rollback() ).then( Mono.error( error ) ) ); - - } ); + Flux summary = Flux.usingWhen( session.beginTransaction(), tx -> + tx.run( query ).summary(), + RxTransaction::commit, + RxTransaction::rollback + ); StepVerifier.create( summary.single() ).consumeNextWith( Assertions::assertNotNull ).verifyComplete(); } @@ -935,7 +907,6 @@ private void assertCanCommit( RxTransaction tx ) private void assertCanRollback( RxTransaction tx ) { assertThat( await( tx.rollback() ), equalTo( emptyList() ) ); - System.out.println("rolled back"); } private static Stream commit() diff --git a/driver/src/test/java/org/neo4j/driver/internal/DirectConnectionProviderTest.java b/driver/src/test/java/org/neo4j/driver/internal/DirectConnectionProviderTest.java index 01c8bcc425..37ac1632ae 100644 --- a/driver/src/test/java/org/neo4j/driver/internal/DirectConnectionProviderTest.java +++ b/driver/src/test/java/org/neo4j/driver/internal/DirectConnectionProviderTest.java @@ -27,7 +27,7 @@ import java.util.stream.Stream; import org.neo4j.driver.AccessMode; -import org.neo4j.driver.internal.async.DecoratedConnection; +import org.neo4j.driver.internal.async.connection.DecoratedConnection; import org.neo4j.driver.internal.spi.Connection; import org.neo4j.driver.internal.spi.ConnectionPool; diff --git a/driver/src/test/java/org/neo4j/driver/internal/DriverFactoryTest.java b/driver/src/test/java/org/neo4j/driver/internal/DriverFactoryTest.java index 1f46343098..32f4924dba 100644 --- a/driver/src/test/java/org/neo4j/driver/internal/DriverFactoryTest.java +++ b/driver/src/test/java/org/neo4j/driver/internal/DriverFactoryTest.java @@ -32,7 +32,9 @@ import org.neo4j.driver.Config; import org.neo4j.driver.Driver; import org.neo4j.driver.exceptions.ServiceUnavailableException; -import org.neo4j.driver.internal.async.BootstrapFactory; +import org.neo4j.driver.internal.async.NetworkSession; +import org.neo4j.driver.internal.async.LeakLoggingNetworkSession; +import org.neo4j.driver.internal.async.connection.BootstrapFactory; import org.neo4j.driver.internal.cluster.RoutingSettings; import org.neo4j.driver.internal.cluster.loadbalancing.LoadBalancer; import org.neo4j.driver.internal.metrics.InternalMetricsProvider; diff --git a/driver/src/test/java/org/neo4j/driver/internal/messaging/v2/NetworkSessionTest.java b/driver/src/test/java/org/neo4j/driver/internal/InternalSessionTest.java similarity index 60% rename from driver/src/test/java/org/neo4j/driver/internal/messaging/v2/NetworkSessionTest.java rename to driver/src/test/java/org/neo4j/driver/internal/InternalSessionTest.java index 4a7d32240a..057154fe36 100644 --- a/driver/src/test/java/org/neo4j/driver/internal/messaging/v2/NetworkSessionTest.java +++ b/driver/src/test/java/org/neo4j/driver/internal/InternalSessionTest.java @@ -16,41 +16,43 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package org.neo4j.driver.internal.messaging.v2; +package org.neo4j.driver.internal; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.MethodSource; import org.mockito.InOrder; -import org.mockito.invocation.InvocationOnMock; -import org.mockito.stubbing.Answer; -import org.mockito.verification.VerificationMode; -import java.util.Map; +import java.util.function.Function; import java.util.function.Supplier; +import java.util.stream.Stream; -import org.neo4j.driver.internal.BoltServerAddress; -import org.neo4j.driver.internal.Bookmarks; -import org.neo4j.driver.internal.DefaultBookmarksHolder; -import org.neo4j.driver.internal.NetworkSession; -import org.neo4j.driver.internal.messaging.BoltProtocol; -import org.neo4j.driver.internal.messaging.request.PullAllMessage; -import org.neo4j.driver.internal.messaging.request.RunMessage; -import org.neo4j.driver.internal.retry.RetryLogic; -import org.neo4j.driver.internal.spi.Connection; -import org.neo4j.driver.internal.spi.ConnectionProvider; -import org.neo4j.driver.internal.spi.ResponseHandler; -import org.neo4j.driver.internal.util.FixedRetryLogic; -import org.neo4j.driver.internal.util.ServerVersion; import org.neo4j.driver.AccessMode; import org.neo4j.driver.Session; +import org.neo4j.driver.Statement; +import org.neo4j.driver.StatementResult; import org.neo4j.driver.Transaction; +import org.neo4j.driver.TransactionConfig; import org.neo4j.driver.TransactionWork; import org.neo4j.driver.Value; import org.neo4j.driver.exceptions.ClientException; import org.neo4j.driver.exceptions.ServiceUnavailableException; import org.neo4j.driver.exceptions.SessionExpiredException; +import org.neo4j.driver.internal.messaging.BoltProtocol; +import org.neo4j.driver.internal.messaging.request.CommitMessage; +import org.neo4j.driver.internal.messaging.request.PullMessage; +import org.neo4j.driver.internal.messaging.request.RunWithMetadataMessage; +import org.neo4j.driver.internal.messaging.v4.BoltProtocolV4; +import org.neo4j.driver.internal.retry.RetryLogic; +import org.neo4j.driver.internal.spi.Connection; +import org.neo4j.driver.internal.spi.ConnectionProvider; +import org.neo4j.driver.internal.util.FixedRetryLogic; +import org.neo4j.driver.internal.value.IntegerValue; +import org.neo4j.driver.summary.ResultSummary; -import static java.util.Collections.emptyMap; +import static java.util.Collections.singletonList; +import static java.util.Collections.singletonMap; import static java.util.concurrent.CompletableFuture.completedFuture; import static org.hamcrest.CoreMatchers.containsString; import static org.hamcrest.CoreMatchers.equalTo; @@ -62,11 +64,8 @@ import static org.junit.jupiter.api.Assertions.assertThrows; import static org.junit.jupiter.api.Assertions.assertTrue; import static org.mockito.ArgumentMatchers.any; -import static org.mockito.ArgumentMatchers.argThat; import static org.mockito.ArgumentMatchers.eq; -import static org.mockito.Mockito.RETURNS_MOCKS; import static org.mockito.Mockito.atLeastOnce; -import static org.mockito.Mockito.doAnswer; import static org.mockito.Mockito.doReturn; import static org.mockito.Mockito.inOrder; import static org.mockito.Mockito.mock; @@ -75,43 +74,103 @@ import static org.mockito.Mockito.times; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; -import static org.neo4j.driver.internal.logging.DevNullLogging.DEV_NULL_LOGGING; -import static org.neo4j.driver.internal.messaging.request.MultiDatabaseUtil.ABSENT_DB_NAME; -import static org.neo4j.driver.internal.messaging.v2.ExplicitTransactionTest.connectionMock; -import static org.neo4j.driver.internal.util.Futures.completedWithNull; -import static org.neo4j.driver.internal.util.Futures.failedFuture; import static org.neo4j.driver.AccessMode.READ; import static org.neo4j.driver.AccessMode.WRITE; -import static org.neo4j.driver.util.TestUtil.await; -import static org.neo4j.driver.util.TestUtil.runMessageWithStatementMatcher; - -class NetworkSessionTest +import static org.neo4j.driver.TransactionConfig.empty; +import static org.neo4j.driver.Values.parameters; +import static org.neo4j.driver.internal.messaging.request.MultiDatabaseUtil.ABSENT_DB_NAME; +import static org.neo4j.driver.internal.util.Futures.failedFuture; +import static org.neo4j.driver.util.TestUtil.connectionMock; +import static org.neo4j.driver.util.TestUtil.newSession; +import static org.neo4j.driver.util.TestUtil.setupFailingBegin; +import static org.neo4j.driver.util.TestUtil.setupFailingCommit; +import static org.neo4j.driver.util.TestUtil.setupSuccessfulRunAndPull; +import static org.neo4j.driver.util.TestUtil.verifyBeginTx; +import static org.neo4j.driver.util.TestUtil.verifyCommitTx; +import static org.neo4j.driver.util.TestUtil.verifyRollbackTx; +import static org.neo4j.driver.util.TestUtil.verifyRunAndPull; + +class InternalSessionTest { private Connection connection; private ConnectionProvider connectionProvider; - private NetworkSession session; + private InternalSession session; @BeforeEach void setUp() { - connection = connectionMock(); - when( connection.release() ).thenReturn( completedWithNull() ); - when( connection.reset() ).thenReturn( completedWithNull() ); - when( connection.serverAddress() ).thenReturn( BoltServerAddress.LOCAL_DEFAULT ); - when( connection.serverVersion() ).thenReturn( ServerVersion.v3_2_0 ); - when( connection.protocol() ).thenReturn( BoltProtocolV2.INSTANCE ); + connection = connectionMock( BoltProtocolV4.INSTANCE ); connectionProvider = mock( ConnectionProvider.class ); - when( connectionProvider.acquireConnection( eq( ABSENT_DB_NAME ), any( AccessMode.class ) ) ) + when( connectionProvider.acquireConnection( any( String.class ), any( AccessMode.class ) ) ) .thenReturn( completedFuture( connection ) ); - session = newSession( connectionProvider, READ ); + session = new InternalSession( newSession( connectionProvider ) ); } - @Test - void shouldFlushOnRun() + private static Stream> allSessionRunMethods() { - session.run( "RETURN 1" ); + return Stream.of( + session -> session.run( "RETURN 1" ), + session -> session.run( "RETURN $x", parameters( "x", 1 ) ), + session -> session.run( "RETURN $x", singletonMap( "x", 1 ) ), + session -> session.run( "RETURN $x", + new InternalRecord( singletonList( "x" ), new Value[]{new IntegerValue( 1 )} ) ), + session -> session.run( new Statement( "RETURN $x", parameters( "x", 1 ) ) ), + session -> session.run( new Statement( "RETURN $x", parameters( "x", 1 ) ), empty() ), + session -> session.run( "RETURN $x", singletonMap( "x", 1 ), empty() ), + session -> session.run( "RETURN 1", empty() ) + ); + } + + private static Stream> allBeginTxMethods() + { + return Stream.of( + session -> session.beginTransaction(), + session -> session.beginTransaction( TransactionConfig.empty() ) + ); + } + + private static Stream> allRunTxMethods() + { + return Stream.of( + session -> session.readTransaction( tx -> "a" ), + session -> session.writeTransaction( tx -> "a" ), + session -> session.readTransaction( tx -> "a", empty() ), + session -> session.writeTransaction( tx -> "a", empty() ) + ); + } + + @ParameterizedTest + @MethodSource( "allSessionRunMethods" ) + void shouldFlushOnRun( Function runReturnOne ) throws Throwable + { + setupSuccessfulRunAndPull( connection ); + + StatementResult result = runReturnOne.apply( session ); + ResultSummary summary = result.summary(); + + verifyRunAndPull( connection, summary.statement().text() ); + } + + @ParameterizedTest + @MethodSource( "allBeginTxMethods" ) + void shouldBeginTx( Function beginTx ) throws Throwable + { + Transaction tx = beginTx.apply( session ); + + verifyBeginTx( connection ); + assertNotNull( tx ); + } - verify( connection ).writeAndFlush( eq( new RunMessage( "RETURN 1" ) ), any(), any(), any() ); + @ParameterizedTest + @MethodSource( "allRunTxMethods" ) + void runTxShouldBeginTxAndCloseTx( Function runTx ) throws Throwable + { + String string = runTx.apply( session ); + + verifyBeginTx( connection ); + verify( connection ).writeAndFlush( any( CommitMessage.class ), any() ); + verify( connection ).release(); + assertThat( string, equalTo( "a" ) ); } @Test @@ -157,7 +216,7 @@ void shouldBeAbleToUseSessionAgainWhenTransactionIsClosed() session.run( "RETURN 1" ); // Then - verify( connection ).writeAndFlush( eq( new RunMessage( "RETURN 1" ) ), any(), any(), any() ); + verifyRunAndPull( connection, "RETURN 1" ); } @Test @@ -169,15 +228,12 @@ void shouldNotCloseAlreadyClosedSession() session.close(); session.close(); - verifyRollbackTx( connection, times( 1 ) ); + verifyRollbackTx( connection ); } @Test void runThrowsWhenSessionIsClosed() { - ConnectionProvider connectionProvider = mock( ConnectionProvider.class, RETURNS_MOCKS ); - NetworkSession session = newSession( connectionProvider, READ ); - session.close(); Exception e = assertThrows( Exception.class, () -> session.run( "CREATE ()" ) ); @@ -188,29 +244,22 @@ void runThrowsWhenSessionIsClosed() @Test void acquiresNewConnectionForRun() { - ConnectionProvider connectionProvider = mock( ConnectionProvider.class ); - Connection connection = connectionMock(); - when( connectionProvider.acquireConnection( ABSENT_DB_NAME, READ ) ).thenReturn( completedFuture( connection ) ); - NetworkSession session = newSession( connectionProvider, READ ); - session.run( "RETURN 1" ); - verify( connectionProvider ).acquireConnection( ABSENT_DB_NAME, READ ); - verify( connection ).writeAndFlush( eq( new RunMessage( "RETURN 1" ) ), any(), eq( PullAllMessage.PULL_ALL ), any() ); + verify( connectionProvider ).acquireConnection( any( String.class ), any( AccessMode.class ) ); } @Test void releasesOpenConnectionUsedForRunWhenSessionIsClosed() { String query = "RETURN 1"; - setupSuccessfulPullAll( query ); + setupSuccessfulRunAndPull( connection, query ); session.run( query ); - - await( session.closeAsync() ); + session.close(); InOrder inOrder = inOrder( connection ); - inOrder.verify( connection ).writeAndFlush( eq( new RunMessage( "RETURN 1" ) ), any(), eq( PullAllMessage.PULL_ALL ), any() ); + inOrder.verify( connection ).writeAndFlush( any( RunWithMetadataMessage.class ), any(), any( PullMessage.class ), any() ); inOrder.verify( connection, atLeastOnce() ).release(); } @@ -218,9 +267,6 @@ void releasesOpenConnectionUsedForRunWhenSessionIsClosed() @Test void resetDoesNothingWhenNoTransactionAndNoConnection() { - ConnectionProvider connectionProvider = mock( ConnectionProvider.class ); - NetworkSession session = newSession( connectionProvider, READ ); - session.reset(); verify( connectionProvider, never() ).acquireConnection( any( String.class ), any( AccessMode.class ) ); @@ -229,9 +275,6 @@ void resetDoesNothingWhenNoTransactionAndNoConnection() @Test void closeWithoutConnection() { - ConnectionProvider connectionProvider = mock( ConnectionProvider.class ); - NetworkSession session = newSession( connectionProvider, READ ); - session.close(); verify( connectionProvider, never() ).acquireConnection( any( String.class ), any( AccessMode.class ) ); @@ -243,7 +286,7 @@ void acquiresNewConnectionForBeginTx() Transaction tx = session.beginTransaction(); assertNotNull( tx ); - verify( connectionProvider ).acquireConnection( ABSENT_DB_NAME, READ ); + verify( connectionProvider ).acquireConnection( any( String.class ), any( AccessMode.class ) ); } @Test @@ -251,7 +294,7 @@ void updatesBookmarkWhenTxIsClosed() { Bookmarks bookmarkAfterCommit = Bookmarks.from( "TheBookmark" ); - BoltProtocol protocol = spy( BoltProtocolV2.INSTANCE ); + BoltProtocol protocol = spy( BoltProtocolV4.INSTANCE ); doReturn( completedFuture( bookmarkAfterCommit ) ).when( protocol ).commitTransaction( any( Connection.class ) ); when( connection.protocol() ).thenReturn( protocol ); @@ -268,13 +311,13 @@ void updatesBookmarkWhenTxIsClosed() void releasesConnectionWhenTxIsClosed() { String query = "RETURN 42"; - setupSuccessfulPullAll( query ); + setupSuccessfulRunAndPull( connection, query ); Transaction tx = session.beginTransaction(); tx.run( query ); - verify( connectionProvider ).acquireConnection( ABSENT_DB_NAME, READ ); - verify( connection ).writeAndFlush( eq( new RunMessage( query ) ), any(), any(), any() ); + verify( connectionProvider ).acquireConnection( any( String.class ), any( AccessMode.class ) ); + verifyRunAndPull( connection, query ); tx.close(); verify( connection ).release(); @@ -284,18 +327,7 @@ void releasesConnectionWhenTxIsClosed() void bookmarkIsPropagatedFromSession() { Bookmarks bookmarks = Bookmarks.from( "Bookmarks" ); - NetworkSession session = newSession( connectionProvider, READ, bookmarks ); - - Transaction tx = session.beginTransaction(); - assertNotNull( tx ); - verifyBeginTx( connection, bookmarks ); - } - - @Test - void bookmarkIsPropagatedInBeginTransaction() - { - Bookmarks bookmarks = Bookmarks.from( "Bookmarks" ); - NetworkSession session = newSession( connectionProvider, READ, bookmarks ); + Session session = new InternalSession( newSession( connectionProvider, bookmarks ) ); Transaction tx = session.beginTransaction(); assertNotNull( tx ); @@ -308,9 +340,9 @@ void bookmarkIsPropagatedBetweenTransactions() Bookmarks bookmarks1 = Bookmarks.from( "Bookmark1" ); Bookmarks bookmarks2 = Bookmarks.from( "Bookmark2" ); - NetworkSession session = newSession( connectionProvider, READ ); + Session session = new InternalSession( newSession( connectionProvider) ); - BoltProtocol protocol = spy( BoltProtocolV2.INSTANCE ); + BoltProtocol protocol = spy( BoltProtocolV4.INSTANCE ); doReturn( completedFuture( bookmarks1 ), completedFuture( bookmarks2 ) ).when( protocol ).commitTransaction( any( Connection.class ) ); when( connection.protocol() ).thenReturn( protocol ); @@ -332,34 +364,23 @@ void bookmarkIsPropagatedBetweenTransactions() @Test void accessModeUsedToAcquireConnections() { - NetworkSession session1 = newSession( connectionProvider, READ ); + Session session1 = new InternalSession( newSession( connectionProvider, READ ) ); session1.beginTransaction(); verify( connectionProvider ).acquireConnection( ABSENT_DB_NAME, READ ); - NetworkSession session2 = newSession( connectionProvider, WRITE ); + Session session2 = new InternalSession( newSession( connectionProvider, WRITE ) ); session2.beginTransaction(); verify( connectionProvider ).acquireConnection( ABSENT_DB_NAME, WRITE ); } - - @Test void testPassingNoBookmarkShouldRetainBookmark() { - NetworkSession session = newSession( connectionProvider, READ, Bookmarks.from( "X" ) ); + Session session = new InternalSession( newSession( connectionProvider, Bookmarks.from( "X" ) ) ); session.beginTransaction(); assertThat( session.lastBookmark(), equalTo( "X" ) ); } - - @SuppressWarnings( "deprecation" ) - @Test - void testPassingNullBookmarkShouldRetainBookmark() - { - NetworkSession session = newSession( connectionProvider, READ, Bookmarks.from( "X" ) ); - session.beginTransaction( (String) null ); - assertThat( session.lastBookmark(), equalTo( "X" ) ); - } - + @Test void acquiresReadConnectionForReadTxInReadSession() { @@ -471,7 +492,6 @@ void writeTxRetriedUntilFailureWhenTxCloseThrows() @Test void connectionShouldBeResetAfterSessionReset() { - NetworkSession session = newSession( connectionProvider, READ ); session.run( "RETURN 1" ); verify( connection, never() ).reset(); @@ -485,7 +505,6 @@ void connectionShouldBeResetAfterSessionReset() @Test void shouldHaveNullLastBookmarkInitially() { - NetworkSession session = newSession( mock( ConnectionProvider.class ), READ ); assertNull( session.lastBookmark() ); } @@ -493,7 +512,7 @@ void shouldHaveNullLastBookmarkInitially() void shouldDoNothingWhenClosingWithoutAcquiredConnection() { RuntimeException error = new RuntimeException( "Hi" ); - when( connectionProvider.acquireConnection( ABSENT_DB_NAME, READ ) ).thenReturn( failedFuture( error ) ); + when( connectionProvider.acquireConnection( any( String.class ), any( AccessMode.class ) ) ).thenReturn( failedFuture( error ) ); Exception e = assertThrows( Exception.class, () -> session.run( "RETURN 1" ) ); assertEquals( error, e ); @@ -505,7 +524,7 @@ void shouldDoNothingWhenClosingWithoutAcquiredConnection() void shouldRunAfterRunFailureToAcquireConnection() { RuntimeException error = new RuntimeException( "Hi" ); - when( connectionProvider.acquireConnection( ABSENT_DB_NAME, READ ) ) + when( connectionProvider.acquireConnection( any( String.class ), any( AccessMode.class ) ) ) .thenReturn( failedFuture( error ) ).thenReturn( completedFuture( connection ) ); Exception e = assertThrows( Exception.class, () -> session.run( "RETURN 1" ) ); @@ -513,54 +532,54 @@ void shouldRunAfterRunFailureToAcquireConnection() session.run( "RETURN 2" ); - verify( connectionProvider, times( 2 ) ).acquireConnection( ABSENT_DB_NAME, READ ); - verifyRunAndFlush( connection, "RETURN 2", times( 1 ) ); + verify( connectionProvider, times( 2 ) ).acquireConnection( any( String.class ), any( AccessMode.class ) ); + verifyRunAndPull( connection, "RETURN 2" ); } @Test void shouldRunAfterBeginTxFailureOnBookmark() { RuntimeException error = new RuntimeException( "Hi" ); - Connection connection1 = connectionMock(); + Connection connection1 = connectionMock( BoltProtocolV4.INSTANCE ); setupFailingBegin( connection1, error ); - Connection connection2 = connectionMock(); + Connection connection2 = connectionMock( BoltProtocolV4.INSTANCE ); - when( connectionProvider.acquireConnection( ABSENT_DB_NAME, READ ) ) + when( connectionProvider.acquireConnection( any( String.class ), any( AccessMode.class ) ) ) .thenReturn( completedFuture( connection1 ) ).thenReturn( completedFuture( connection2 ) ); Bookmarks bookmarks = Bookmarks.from( "neo4j:bookmark:v1:tx42" ); - NetworkSession session = newSession( connectionProvider, READ, bookmarks ); + Session session = new InternalSession( newSession( connectionProvider, bookmarks ) ); Exception e = assertThrows( Exception.class, session::beginTransaction ); assertEquals( error, e ); session.run( "RETURN 2" ); - verify( connectionProvider, times( 2 ) ).acquireConnection( ABSENT_DB_NAME, READ ); + verify( connectionProvider, times( 2 ) ).acquireConnection( any( String.class ), any( AccessMode.class ) ); verifyBeginTx( connection1, bookmarks ); - verifyRunAndFlush( connection2, "RETURN 2", times( 1 ) ); + verifyRunAndPull( connection2, "RETURN 2" ); } @Test void shouldBeginTxAfterBeginTxFailureOnBookmark() { RuntimeException error = new RuntimeException( "Hi" ); - Connection connection1 = connectionMock(); + Connection connection1 = connectionMock( BoltProtocolV4.INSTANCE ); setupFailingBegin( connection1, error ); - Connection connection2 = connectionMock(); + Connection connection2 = connectionMock( BoltProtocolV4.INSTANCE ); - when( connectionProvider.acquireConnection( ABSENT_DB_NAME, READ ) ) + when( connectionProvider.acquireConnection( any( String.class ), any( AccessMode.class ) ) ) .thenReturn( completedFuture( connection1 ) ).thenReturn( completedFuture( connection2 ) ); Bookmarks bookmarks = Bookmarks.from( "neo4j:bookmark:v1:tx42" ); - NetworkSession session = newSession( connectionProvider, READ, bookmarks ); + Session session = new InternalSession( newSession( connectionProvider, bookmarks ) ); Exception e = assertThrows( Exception.class, session::beginTransaction ); assertEquals( error, e ); session.beginTransaction(); - verify( connectionProvider, times( 2 ) ).acquireConnection( ABSENT_DB_NAME, READ ); + verify( connectionProvider, times( 2 ) ).acquireConnection( any( String.class ), any( AccessMode.class ) ); verifyBeginTx( connection1, bookmarks ); verifyBeginTx( connection2, bookmarks ); } @@ -569,7 +588,7 @@ void shouldBeginTxAfterBeginTxFailureOnBookmark() void shouldBeginTxAfterRunFailureToAcquireConnection() { RuntimeException error = new RuntimeException( "Hi" ); - when( connectionProvider.acquireConnection( ABSENT_DB_NAME, READ ) ) + when( connectionProvider.acquireConnection( any( String.class ), any( AccessMode.class ) ) ) .thenReturn( failedFuture( error ) ).thenReturn( completedFuture( connection ) ); Exception e = assertThrows( Exception.class, () -> session.run( "RETURN 1" ) ); @@ -577,14 +596,13 @@ void shouldBeginTxAfterRunFailureToAcquireConnection() session.beginTransaction(); - verify( connectionProvider, times( 2 ) ).acquireConnection( ABSENT_DB_NAME, READ ); - verifyBeginTx( connection, times( 1 ) ); + verify( connectionProvider, times( 2 ) ).acquireConnection( any( String.class ), any( AccessMode.class ) ); + verifyBeginTx( connection ); } @Test void shouldMarkTransactionAsTerminatedAndThenResetConnectionOnReset() { - NetworkSession session = newSession( connectionProvider, READ ); Transaction tx = session.beginTransaction(); assertTrue( tx.isOpen() ); @@ -595,64 +613,27 @@ void shouldMarkTransactionAsTerminatedAndThenResetConnectionOnReset() verify( connection ).reset(); } - @Test - void shouldNotAllowStartingMultipleTransactions() - { - NetworkSession session = newSession( connectionProvider, READ ); - - Transaction tx = session.beginTransaction(); - assertNotNull( tx ); - - for ( int i = 0; i < 5; i++ ) - { - ClientException e = assertThrows( ClientException.class, session::beginTransaction ); - assertThat( e.getMessage(), - containsString( "You cannot begin a transaction on a session with an open transaction" ) ); - } - } - - @Test - void shouldAllowStartingTransactionAfterCurrentOneIsClosed() - { - NetworkSession session = newSession( connectionProvider, READ ); - - Transaction tx = session.beginTransaction(); - assertNotNull( tx ); - - ClientException e = assertThrows( ClientException.class, session::beginTransaction ); - assertThat( e.getMessage(), - containsString( "You cannot begin a transaction on a session with an open transaction" ) ); - - tx.close(); - - assertNotNull( session.beginTransaction() ); - } - private void testConnectionAcquisition( AccessMode sessionMode, AccessMode transactionMode ) { - NetworkSession session = newSession( connectionProvider, sessionMode ); + Session session = new InternalSession( newSession( connectionProvider, sessionMode ) ); TxWork work = new TxWork( 42 ); int result = executeTransaction( session, transactionMode, work ); - verify( connectionProvider ).acquireConnection( ABSENT_DB_NAME, transactionMode ); - verifyBeginTx( connection, times( 1 ) ); - verifyCommitTx( connection, times( 1 ) ); + verify( connectionProvider ).acquireConnection( any( String.class ), eq( transactionMode ) ); + verifyBeginTx( connection ); + verifyCommitTx( connection ); assertEquals( 42, result ); } private void testTxCommitOrRollback( AccessMode transactionMode, final boolean commit ) { - NetworkSession session = newSession( connectionProvider, WRITE ); + Session session = new InternalSession( newSession( connectionProvider, WRITE ) ); TransactionWork work = tx -> { - if ( commit ) - { - tx.success(); - } - else + if ( !commit ) { tx.failure(); } @@ -661,16 +642,16 @@ private void testTxCommitOrRollback( AccessMode transactionMode, final boolean c int result = executeTransaction( session, transactionMode, work ); - verify( connectionProvider ).acquireConnection( ABSENT_DB_NAME, transactionMode ); - verifyBeginTx( connection, times( 1 ) ); + verify( connectionProvider ).acquireConnection( any( String.class ), eq( transactionMode ) ); + verifyBeginTx( connection ); if ( commit ) { - verifyCommitTx( connection, times( 1 ) ); + verifyCommitTx( connection ); verifyRollbackTx( connection, never() ); } else { - verifyRollbackTx( connection, times( 1 ) ); + verifyRollbackTx( connection ); verifyCommitTx( connection, never() ); } assertEquals( 4242, result ); @@ -678,8 +659,6 @@ private void testTxCommitOrRollback( AccessMode transactionMode, final boolean c private void testTxRollbackWhenThrows( AccessMode transactionMode ) { - NetworkSession session = newSession( connectionProvider, WRITE ); - final RuntimeException error = new IllegalStateException( "Oh!" ); TransactionWork work = tx -> { @@ -689,9 +668,9 @@ private void testTxRollbackWhenThrows( AccessMode transactionMode ) Exception e = assertThrows( Exception.class, () -> executeTransaction( session, transactionMode, work ) ); assertEquals( error, e ); - verify( connectionProvider ).acquireConnection( ABSENT_DB_NAME, transactionMode ); - verifyBeginTx( connection, times( 1 ) ); - verifyRollbackTx( connection, times( 1 ) ); + verify( connectionProvider ).acquireConnection( any( String.class ), eq( transactionMode ) ); + verifyBeginTx( connection ); + verifyRollbackTx( connection ); } private void testTxIsRetriedUntilSuccessWhenFunctionThrows( AccessMode mode ) @@ -700,14 +679,14 @@ private void testTxIsRetriedUntilSuccessWhenFunctionThrows( AccessMode mode ) int retries = failures + 1; RetryLogic retryLogic = new FixedRetryLogic( retries ); - NetworkSession session = newSession( connectionProvider, retryLogic ); + Session session = new InternalSession( newSession( connectionProvider, retryLogic ) ); TxWork work = spy( new TxWork( 42, failures, new SessionExpiredException( "" ) ) ); int answer = executeTransaction( session, mode, work ); assertEquals( 42, answer ); verifyInvocationCount( work, failures + 1 ); - verifyCommitTx( connection, times( 1 ) ); + verifyCommitTx( connection ); verifyRollbackTx( connection, times( failures ) ); } @@ -718,7 +697,7 @@ private void testTxIsRetriedUntilSuccessWhenCommitThrows( AccessMode mode ) RetryLogic retryLogic = new FixedRetryLogic( retries ); setupFailingCommit( connection, failures ); - NetworkSession session = newSession( connectionProvider, retryLogic ); + Session session = new InternalSession( newSession( connectionProvider, retryLogic ) ); TxWork work = spy( new TxWork( 43 ) ); int answer = executeTransaction( session, mode, work ); @@ -734,7 +713,7 @@ private void testTxIsRetriedUntilFailureWhenFunctionThrows( AccessMode mode ) int retries = failures - 1; RetryLogic retryLogic = new FixedRetryLogic( retries ); - NetworkSession session = newSession( connectionProvider, retryLogic ); + Session session = new InternalSession( newSession( connectionProvider, retryLogic ) ); TxWork work = spy( new TxWork( 42, failures, new SessionExpiredException( "Oh!" ) ) ); @@ -754,7 +733,7 @@ private void testTxIsRetriedUntilFailureWhenCommitFails( AccessMode mode ) RetryLogic retryLogic = new FixedRetryLogic( retries ); setupFailingCommit( connection, failures ); - NetworkSession session = newSession( connectionProvider, retryLogic ); + Session session = new InternalSession( newSession( connectionProvider, retryLogic ) ); TxWork work = spy( new TxWork( 42 ) ); @@ -781,109 +760,11 @@ else if ( mode == WRITE ) } } - private static NetworkSession newSession( ConnectionProvider connectionProvider, AccessMode mode ) - { - return newSession( connectionProvider, mode, Bookmarks.empty() ); - } - - private static NetworkSession newSession( ConnectionProvider connectionProvider, RetryLogic retryLogic ) - { - return newSession( connectionProvider, WRITE, retryLogic, Bookmarks.empty() ); - } - - private static NetworkSession newSession( ConnectionProvider connectionProvider, AccessMode mode, - Bookmarks bookmarks ) - { - return newSession( connectionProvider, mode, new FixedRetryLogic( 0 ), bookmarks ); - } - - private static NetworkSession newSession( ConnectionProvider connectionProvider, AccessMode mode, - RetryLogic retryLogic, Bookmarks bookmarks ) - { - return new NetworkSession( connectionProvider, retryLogic, ABSENT_DB_NAME, mode, new DefaultBookmarksHolder( bookmarks ), DEV_NULL_LOGGING ); - } - private static void verifyInvocationCount( TransactionWork workSpy, int expectedInvocationCount ) { verify( workSpy, times( expectedInvocationCount ) ).execute( any( Transaction.class ) ); } - private static void verifyBeginTx( Connection connectionMock, VerificationMode mode ) - { - verify( connectionMock, mode ).write( eq( new RunMessage( "BEGIN" ) ), any(), any(), any() ); - } - - private static void verifyBeginTx( Connection connectionMock, Bookmarks bookmarks ) - { - if ( bookmarks.isEmpty() ) - { - verify( connectionMock ).write( eq( new RunMessage( "BEGIN" ) ), any(), any(), any() ); - } - else - { - Map params = bookmarks.asBeginTransactionParameters(); - verify( connectionMock ).writeAndFlush( eq( new RunMessage( "BEGIN", params ) ), any(), eq( PullAllMessage.PULL_ALL ), any() ); - } - } - - private static void verifyCommitTx( Connection connectionMock, VerificationMode mode ) - { - verifyRunAndFlush( connectionMock, "COMMIT", mode ); - } - - private static void verifyRollbackTx( Connection connectionMock, VerificationMode mode ) - { - verifyRunAndFlush( connectionMock, "ROLLBACK", mode ); - } - - private static void verifyRunAndFlush( Connection connectionMock, String statement, VerificationMode mode ) - { - verify( connectionMock, mode ).writeAndFlush( eq( new RunMessage( statement ) ), any(), eq( PullAllMessage.PULL_ALL ), any() ); - } - - private static void setupFailingCommit( Connection connection, int times ) - { - doAnswer( new Answer() - { - int invoked; - - @Override - public Void answer( InvocationOnMock invocation ) - { - ResponseHandler handler = invocation.getArgument( 3 ); - if ( invoked++ < times ) - { - handler.onFailure( new ServiceUnavailableException( "" ) ); - } - else - { - handler.onSuccess( emptyMap() ); - } - return null; - } - } ).when( connection ).writeAndFlush( eq( new RunMessage( "COMMIT" ) ), any(), eq( PullAllMessage.PULL_ALL ), any() ); - } - - private static void setupFailingBegin( Connection connection, Throwable error ) - { - doAnswer( invocation -> - { - ResponseHandler handler = invocation.getArgument( 3 ); - handler.onFailure( error ); - return null; - } ).when( connection ).writeAndFlush( argThat( runMessageWithStatementMatcher( "BEGIN" ) ), any(), eq( PullAllMessage.PULL_ALL ), any() ); - } - - private void setupSuccessfulPullAll( String query ) - { - doAnswer( invocation -> - { - ResponseHandler pullAllHandler = invocation.getArgument( 3 ); - pullAllHandler.onSuccess( emptyMap() ); - return null; - } ).when( connection ).writeAndFlush( eq( new RunMessage( query ) ), any(), eq( PullAllMessage.PULL_ALL ), any() ); - } - private static class TxWork implements TransactionWork { final int result; diff --git a/driver/src/test/java/org/neo4j/driver/internal/InternalStatementResultTest.java b/driver/src/test/java/org/neo4j/driver/internal/InternalStatementResultTest.java index ff721a6e57..086ef8a366 100644 --- a/driver/src/test/java/org/neo4j/driver/internal/InternalStatementResultTest.java +++ b/driver/src/test/java/org/neo4j/driver/internal/InternalStatementResultTest.java @@ -25,6 +25,7 @@ import java.util.List; import java.util.concurrent.CompletableFuture; +import org.neo4j.driver.internal.async.AsyncStatementResultCursor; import org.neo4j.driver.internal.handlers.PullAllResponseHandler; import org.neo4j.driver.internal.handlers.RunResponseHandler; import org.neo4j.driver.internal.handlers.SessionPullAllResponseHandler; diff --git a/driver/src/test/java/org/neo4j/driver/internal/InternalTransactionTest.java b/driver/src/test/java/org/neo4j/driver/internal/InternalTransactionTest.java new file mode 100644 index 0000000000..60ee1493e6 --- /dev/null +++ b/driver/src/test/java/org/neo4j/driver/internal/InternalTransactionTest.java @@ -0,0 +1,162 @@ +/* + * Copyright (c) 2002-2019 "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; + +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.MethodSource; + +import java.util.function.Function; +import java.util.stream.Stream; + +import org.neo4j.driver.AccessMode; +import org.neo4j.driver.Statement; +import org.neo4j.driver.StatementResult; +import org.neo4j.driver.Transaction; +import org.neo4j.driver.Value; +import org.neo4j.driver.internal.messaging.v4.BoltProtocolV4; +import org.neo4j.driver.internal.spi.Connection; +import org.neo4j.driver.internal.spi.ConnectionProvider; +import org.neo4j.driver.internal.value.IntegerValue; +import org.neo4j.driver.summary.ResultSummary; + +import static java.util.Collections.singletonList; +import static java.util.Collections.singletonMap; +import static java.util.concurrent.CompletableFuture.completedFuture; +import static org.junit.Assert.assertFalse; +import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; +import static org.neo4j.driver.Values.parameters; +import static org.neo4j.driver.util.TestUtil.connectionMock; +import static org.neo4j.driver.util.TestUtil.newSession; +import static org.neo4j.driver.util.TestUtil.setupFailingCommit; +import static org.neo4j.driver.util.TestUtil.setupFailingRollback; +import static org.neo4j.driver.util.TestUtil.setupFailingRun; +import static org.neo4j.driver.util.TestUtil.setupSuccessfulRunAndPull; +import static org.neo4j.driver.util.TestUtil.verifyCommitTx; +import static org.neo4j.driver.util.TestUtil.verifyRollbackTx; +import static org.neo4j.driver.util.TestUtil.verifyRunAndPull; + +class InternalTransactionTest +{ + private Connection connection; + private Transaction tx; + + @BeforeEach + void setUp() + { + connection = connectionMock( BoltProtocolV4.INSTANCE ); + ConnectionProvider connectionProvider = mock( ConnectionProvider.class ); + when( connectionProvider.acquireConnection( any( String.class ), any( AccessMode.class ) ) ) + .thenReturn( completedFuture( connection ) ); + InternalSession session = new InternalSession( newSession( connectionProvider ) ); + tx = session.beginTransaction(); + } + + private static Stream> allSessionRunMethods() + { + return Stream.of( + tx -> tx.run( "RETURN 1" ), + tx -> tx.run( "RETURN $x", parameters( "x", 1 ) ), + tx -> tx.run( "RETURN $x", singletonMap( "x", 1 ) ), + tx -> tx.run( "RETURN $x", + new InternalRecord( singletonList( "x" ), new Value[]{new IntegerValue( 1 )} ) ), + tx -> tx.run( new Statement( "RETURN $x", parameters( "x", 1 ) ) ) + ); + } + + @ParameterizedTest + @MethodSource( "allSessionRunMethods" ) + void shouldFlushOnRun( Function runReturnOne ) throws Throwable + { + setupSuccessfulRunAndPull( connection ); + + StatementResult result = runReturnOne.apply( tx ); + ResultSummary summary = result.summary(); + + verifyRunAndPull( connection, summary.statement().text() ); + } + + @Test + void shouldCommit() throws Throwable + { + tx.success(); + tx.close(); + + verifyCommitTx( connection ); + assertFalse( tx.isOpen() ); + } + + @Test + void shouldRollbackByDefault() throws Throwable + { + tx.close(); + + verifyRollbackTx( connection ); + assertFalse( tx.isOpen() ); + } + + @Test + void shouldRollback() throws Throwable + { + tx.failure(); + tx.close(); + + verifyRollbackTx( connection ); + assertFalse( tx.isOpen() ); + } + + @Test + void shouldRollbackWhenFailedRun() throws Throwable + { + setupFailingRun( connection, new RuntimeException( "Bang!" ) ); + assertThrows( RuntimeException.class, () -> tx.run( "RETURN 1" ).consume() ); + + tx.success(); + tx.close(); + + verify( connection ).release(); + assertFalse( tx.isOpen() ); + } + + @Test + void shouldReleaseConnectionWhenFailedToCommit() throws Throwable + { + setupFailingCommit( connection ); + tx.success(); + assertThrows( Exception.class, () -> tx.close() ); + + verify( connection ).release(); + assertFalse( tx.isOpen() ); + } + + @Test + void shouldReleaseConnectionWhenFailedToRollback() throws Throwable + { + setupFailingRollback( connection ); + assertThrows( Exception.class, () -> tx.close() ); + + verify( connection ).release(); + assertFalse( tx.isOpen() ); + } +} diff --git a/driver/src/test/java/org/neo4j/driver/internal/RoutingDriverBoltKitTest.java b/driver/src/test/java/org/neo4j/driver/internal/RoutingDriverBoltKitTest.java index 9e681a3d8c..5fa6af31d3 100644 --- a/driver/src/test/java/org/neo4j/driver/internal/RoutingDriverBoltKitTest.java +++ b/driver/src/test/java/org/neo4j/driver/internal/RoutingDriverBoltKitTest.java @@ -553,30 +553,6 @@ void shouldHandleLeaderSwitchWhenWritingInTransaction() assertThat( server.exitStatus(), equalTo( 0 ) ); } - @Test - @SuppressWarnings( "deprecation" ) - void shouldSendAndReceiveBookmark() throws Exception - { - StubServer router = StubServer.start( "acquire_endpoints.script", 9001 ); - StubServer writer = StubServer.start( "write_tx_with_bookmarks.script", 9007 ); - - try ( Driver driver = GraphDatabase.driver( "neo4j://127.0.0.1:9001", config ); - Session session = driver.session() ) - { - // intentionally test deprecated API - try ( Transaction tx = session.beginTransaction( "OldBookmark" ) ) - { - tx.run( "CREATE (n {name:'Bob'})" ); - tx.success(); - } - - assertEquals( "NewBookmark", session.lastBookmark() ); - } - - assertThat( router.exitStatus(), equalTo( 0 ) ); - assertThat( writer.exitStatus(), equalTo( 0 ) ); - } - @Test void shouldSendInitialBookmark() throws Exception { diff --git a/driver/src/test/java/org/neo4j/driver/internal/SessionFactoryImplTest.java b/driver/src/test/java/org/neo4j/driver/internal/SessionFactoryImplTest.java index 430470ff8e..80c69515a6 100644 --- a/driver/src/test/java/org/neo4j/driver/internal/SessionFactoryImplTest.java +++ b/driver/src/test/java/org/neo4j/driver/internal/SessionFactoryImplTest.java @@ -20,11 +20,12 @@ import org.junit.jupiter.api.Test; -import org.neo4j.driver.internal.util.FixedRetryLogic; -import org.neo4j.driver.internal.spi.ConnectionProvider; import org.neo4j.driver.AccessMode; import org.neo4j.driver.Config; -import org.neo4j.driver.Session; +import org.neo4j.driver.internal.async.NetworkSession; +import org.neo4j.driver.internal.async.LeakLoggingNetworkSession; +import org.neo4j.driver.internal.spi.ConnectionProvider; +import org.neo4j.driver.internal.util.FixedRetryLogic; import static org.hamcrest.Matchers.instanceOf; import static org.hamcrest.junit.MatcherAssert.assertThat; @@ -40,10 +41,10 @@ void createsNetworkSessions() Config config = Config.builder().withLogging( DEV_NULL_LOGGING ).build(); SessionFactory factory = newSessionFactory( config ); - Session readSession = factory.newInstance( template().withDefaultAccessMode( AccessMode.READ ).build() ); + NetworkSession readSession = factory.newInstance( template().withDefaultAccessMode( AccessMode.READ ).build() ); assertThat( readSession, instanceOf( NetworkSession.class ) ); - Session writeSession = factory.newInstance( template().withDefaultAccessMode( AccessMode.WRITE ).build() ); + NetworkSession writeSession = factory.newInstance( template().withDefaultAccessMode( AccessMode.WRITE ).build() ); assertThat( writeSession, instanceOf( NetworkSession.class ) ); } @@ -53,10 +54,10 @@ void createsLeakLoggingNetworkSessions() Config config = Config.builder().withLogging( DEV_NULL_LOGGING ).withLeakedSessionsLogging().build(); SessionFactory factory = newSessionFactory( config ); - Session readSession = factory.newInstance( template().withDefaultAccessMode( AccessMode.READ ).build() ); + NetworkSession readSession = factory.newInstance( template().withDefaultAccessMode( AccessMode.READ ).build() ); assertThat( readSession, instanceOf( LeakLoggingNetworkSession.class ) ); - Session writeSession = factory.newInstance( template().withDefaultAccessMode( AccessMode.WRITE ).build() ); + NetworkSession writeSession = factory.newInstance( template().withDefaultAccessMode( AccessMode.WRITE ).build() ); assertThat( writeSession, instanceOf( LeakLoggingNetworkSession.class ) ); } diff --git a/driver/src/test/java/org/neo4j/driver/internal/AsyncStatementResultCursorTest.java b/driver/src/test/java/org/neo4j/driver/internal/async/AsyncStatementResultCursorTest.java similarity index 99% rename from driver/src/test/java/org/neo4j/driver/internal/AsyncStatementResultCursorTest.java rename to driver/src/test/java/org/neo4j/driver/internal/async/AsyncStatementResultCursorTest.java index 5bbfa42fd2..1bf48c77f1 100644 --- a/driver/src/test/java/org/neo4j/driver/internal/AsyncStatementResultCursorTest.java +++ b/driver/src/test/java/org/neo4j/driver/internal/async/AsyncStatementResultCursorTest.java @@ -16,7 +16,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package org.neo4j.driver.internal; +package org.neo4j.driver.internal.async; import org.junit.jupiter.api.Test; @@ -27,6 +27,12 @@ import java.util.concurrent.atomic.AtomicInteger; import java.util.function.Function; +import org.neo4j.driver.Record; +import org.neo4j.driver.Statement; +import org.neo4j.driver.exceptions.NoSuchRecordException; +import org.neo4j.driver.exceptions.ServiceUnavailableException; +import org.neo4j.driver.internal.BoltServerAddress; +import org.neo4j.driver.internal.InternalRecord; import org.neo4j.driver.internal.handlers.PullAllResponseHandler; import org.neo4j.driver.internal.handlers.RunResponseHandler; import org.neo4j.driver.internal.messaging.v1.BoltProtocolV1; @@ -34,10 +40,6 @@ import org.neo4j.driver.internal.summary.InternalServerInfo; import org.neo4j.driver.internal.summary.InternalSummaryCounters; import org.neo4j.driver.internal.util.ServerVersion; -import org.neo4j.driver.Record; -import org.neo4j.driver.Statement; -import org.neo4j.driver.exceptions.NoSuchRecordException; -import org.neo4j.driver.exceptions.ServiceUnavailableException; import org.neo4j.driver.summary.ResultSummary; import org.neo4j.driver.summary.StatementType; @@ -55,10 +57,10 @@ import static org.mockito.Mockito.times; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; -import static org.neo4j.driver.internal.util.Futures.completedWithNull; -import static org.neo4j.driver.internal.util.Futures.failedFuture; import static org.neo4j.driver.Values.value; import static org.neo4j.driver.Values.values; +import static org.neo4j.driver.internal.util.Futures.completedWithNull; +import static org.neo4j.driver.internal.util.Futures.failedFuture; import static org.neo4j.driver.util.TestUtil.await; class AsyncStatementResultCursorTest diff --git a/driver/src/test/java/org/neo4j/driver/internal/messaging/v2/ExplicitTransactionTest.java b/driver/src/test/java/org/neo4j/driver/internal/async/ExplicitTransactionTest.java similarity index 78% rename from driver/src/test/java/org/neo4j/driver/internal/messaging/v2/ExplicitTransactionTest.java rename to driver/src/test/java/org/neo4j/driver/internal/async/ExplicitTransactionTest.java index d62a912039..d8906112be 100644 --- a/driver/src/test/java/org/neo4j/driver/internal/messaging/v2/ExplicitTransactionTest.java +++ b/driver/src/test/java/org/neo4j/driver/internal/async/ExplicitTransactionTest.java @@ -16,24 +16,25 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package org.neo4j.driver.internal.messaging.v2; +package org.neo4j.driver.internal.async; import org.junit.jupiter.api.Test; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.ValueSource; import org.mockito.InOrder; import java.util.function.Consumer; +import org.neo4j.driver.Statement; +import org.neo4j.driver.TransactionConfig; +import org.neo4j.driver.exceptions.ClientException; import org.neo4j.driver.internal.Bookmarks; import org.neo4j.driver.internal.DefaultBookmarksHolder; -import org.neo4j.driver.internal.ExplicitTransaction; import org.neo4j.driver.internal.messaging.request.PullAllMessage; import org.neo4j.driver.internal.messaging.request.RunMessage; +import org.neo4j.driver.internal.messaging.v4.BoltProtocolV4; import org.neo4j.driver.internal.spi.Connection; import org.neo4j.driver.internal.spi.ResponseHandler; -import org.neo4j.driver.Transaction; -import org.neo4j.driver.TransactionConfig; -import org.neo4j.driver.exceptions.ClientException; -import org.neo4j.driver.util.TestUtil; import static java.util.Collections.emptyMap; import static org.junit.jupiter.api.Assertions.assertEquals; @@ -45,16 +46,49 @@ import static org.mockito.ArgumentMatchers.eq; import static org.mockito.Mockito.doAnswer; import static org.mockito.Mockito.inOrder; -import static org.mockito.Mockito.mock; import static org.mockito.Mockito.never; import static org.mockito.Mockito.verify; -import static org.mockito.Mockito.when; -import static org.neo4j.driver.internal.messaging.request.MultiDatabaseUtil.ABSENT_DB_NAME; import static org.neo4j.driver.util.TestUtil.await; +import static org.neo4j.driver.util.TestUtil.connectionMock; import static org.neo4j.driver.util.TestUtil.runMessageWithStatementMatcher; +import static org.neo4j.driver.util.TestUtil.setupSuccessfulRun; +import static org.neo4j.driver.util.TestUtil.setupSuccessfulRunAndPull; +import static org.neo4j.driver.util.TestUtil.verifyRun; +import static org.neo4j.driver.util.TestUtil.verifyRunAndPull; class ExplicitTransactionTest { + @ParameterizedTest + @ValueSource( strings = {"true", "false"} ) + void shouldFlushOnRunAsync( boolean waitForResponse ) + { + // Given + Connection connection = connectionMock( BoltProtocolV4.INSTANCE ); + ExplicitTransaction tx = beginTx( connection ); + setupSuccessfulRunAndPull( connection ); + + // When + await( tx.runAsync( new Statement( "RETURN 1" ), waitForResponse ) ); + + // Then + verifyRunAndPull( connection, "RETURN 1" ); + } + + @Test + void shouldFlushOnRunRx() + { + // Given + Connection connection = connectionMock( BoltProtocolV4.INSTANCE ); + ExplicitTransaction tx = beginTx( connection ); + setupSuccessfulRun( connection ); + + // When + await( tx.runRx( new Statement( "RETURN 1" ) ) ); + + // Then + verifyRun( connection, "RETURN 1" ); + } + @Test void shouldRollbackOnImplicitFailure() { @@ -63,7 +97,7 @@ void shouldRollbackOnImplicitFailure() ExplicitTransaction tx = beginTx( connection ); // When - tx.close(); + await( tx.closeAsync() ); // Then InOrder order = inOrder( connection ); @@ -82,7 +116,7 @@ void shouldRollbackOnExplicitFailure() // When tx.failure(); tx.success(); // even if success is called after the failure call! - tx.close(); + await( tx.closeAsync() ); // Then InOrder order = inOrder( connection ); @@ -100,7 +134,7 @@ void shouldCommitOnSuccess() // When tx.success(); - tx.close(); + await( tx.closeAsync() ); // Then InOrder order = inOrder( connection ); @@ -136,7 +170,7 @@ void shouldFlushWhenBookmarkGiven() @Test void shouldBeOpenAfterConstruction() { - Transaction tx = beginTx( connectionMock() ); + ExplicitTransaction tx = beginTx( connectionMock() ); assertTrue( tx.isOpen() ); } @@ -144,7 +178,7 @@ void shouldBeOpenAfterConstruction() @Test void shouldBeOpenWhenMarkedForSuccess() { - Transaction tx = beginTx( connectionMock() ); + ExplicitTransaction tx = beginTx( connectionMock() ); tx.success(); @@ -154,7 +188,7 @@ void shouldBeOpenWhenMarkedForSuccess() @Test void shouldBeOpenWhenMarkedForFailure() { - Transaction tx = beginTx( connectionMock() ); + ExplicitTransaction tx = beginTx( connectionMock() ); tx.failure(); @@ -174,10 +208,10 @@ void shouldBeClosedWhenMarkedAsTerminated() @Test void shouldBeClosedAfterCommit() { - Transaction tx = beginTx( connectionMock() ); + ExplicitTransaction tx = beginTx( connectionMock() ); tx.success(); - tx.close(); + await( tx.closeAsync() ); assertFalse( tx.isOpen() ); } @@ -185,10 +219,10 @@ void shouldBeClosedAfterCommit() @Test void shouldBeClosedAfterRollback() { - Transaction tx = beginTx( connectionMock() ); + ExplicitTransaction tx = beginTx( connectionMock() ); tx.failure(); - tx.close(); + await( tx.closeAsync() ); assertFalse( tx.isOpen() ); } @@ -199,7 +233,7 @@ void shouldBeClosedWhenMarkedTerminatedAndClosed() ExplicitTransaction tx = beginTx( connectionMock() ); tx.markTerminated(); - tx.close(); + await( tx.closeAsync() ); assertFalse( tx.isOpen() ); } @@ -260,6 +294,17 @@ void shouldReleaseConnectionWhenTerminatedAndRolledBack() verify( connection ).release(); } + @Test + void shouldReleaseConnectionWhenClose() throws Throwable + { + Connection connection = connectionMock(); + ExplicitTransaction tx = new ExplicitTransaction( connection, new DefaultBookmarksHolder() ); + + await( tx.closeAsync() ); + + verify( connection ).release(); + } + private static ExplicitTransaction beginTx( Connection connection ) { return beginTx( connection, Bookmarks.empty() ); @@ -271,16 +316,9 @@ private static ExplicitTransaction beginTx( Connection connection, Bookmarks ini return await( tx.beginAsync( initialBookmarks, TransactionConfig.empty() ) ); } - static Connection connectionMock() - { - return TestUtil.connectionMock( BoltProtocolV2.INSTANCE ); - } - private static Connection connectionWithBegin( Consumer beginBehaviour ) { - Connection connection = mock( Connection.class ); - when( connection.protocol() ).thenReturn( BoltProtocolV2.INSTANCE ); - when( connection.databaseName() ).thenReturn( ABSENT_DB_NAME ); + Connection connection = connectionMock(); doAnswer( invocation -> { diff --git a/driver/src/test/java/org/neo4j/driver/internal/async/InternalAsyncSessionTest.java b/driver/src/test/java/org/neo4j/driver/internal/async/InternalAsyncSessionTest.java new file mode 100644 index 0000000000..6eab9dc858 --- /dev/null +++ b/driver/src/test/java/org/neo4j/driver/internal/async/InternalAsyncSessionTest.java @@ -0,0 +1,395 @@ +/* + * Copyright (c) 2002-2019 "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; + +import org.hamcrest.junit.MatcherAssert; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.MethodSource; + +import java.util.concurrent.CompletionStage; +import java.util.function.Function; +import java.util.function.Supplier; +import java.util.stream.Stream; + +import org.neo4j.driver.AccessMode; +import org.neo4j.driver.Statement; +import org.neo4j.driver.TransactionConfig; +import org.neo4j.driver.Value; +import org.neo4j.driver.async.AsyncSession; +import org.neo4j.driver.async.AsyncTransaction; +import org.neo4j.driver.async.AsyncTransactionWork; +import org.neo4j.driver.async.StatementResultCursor; +import org.neo4j.driver.exceptions.ServiceUnavailableException; +import org.neo4j.driver.exceptions.SessionExpiredException; +import org.neo4j.driver.internal.Bookmarks; +import org.neo4j.driver.internal.InternalRecord; +import org.neo4j.driver.internal.messaging.v4.BoltProtocolV4; +import org.neo4j.driver.internal.retry.RetryLogic; +import org.neo4j.driver.internal.spi.Connection; +import org.neo4j.driver.internal.spi.ConnectionProvider; +import org.neo4j.driver.internal.util.FixedRetryLogic; +import org.neo4j.driver.internal.value.IntegerValue; + +import static java.util.Collections.singletonList; +import static java.util.Collections.singletonMap; +import static java.util.concurrent.CompletableFuture.completedFuture; +import static org.hamcrest.CoreMatchers.equalTo; +import static org.hamcrest.CoreMatchers.instanceOf; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertThat; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.never; +import static org.mockito.Mockito.spy; +import static org.mockito.Mockito.times; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; +import static org.neo4j.driver.AccessMode.READ; +import static org.neo4j.driver.AccessMode.WRITE; +import static org.neo4j.driver.TransactionConfig.empty; +import static org.neo4j.driver.Values.parameters; +import static org.neo4j.driver.util.TestUtil.await; +import static org.neo4j.driver.util.TestUtil.connectionMock; +import static org.neo4j.driver.util.TestUtil.newSession; +import static org.neo4j.driver.util.TestUtil.setupFailingCommit; +import static org.neo4j.driver.util.TestUtil.setupSuccessfulRunAndPull; +import static org.neo4j.driver.util.TestUtil.verifyBeginTx; +import static org.neo4j.driver.util.TestUtil.verifyCommitTx; +import static org.neo4j.driver.util.TestUtil.verifyRollbackTx; +import static org.neo4j.driver.util.TestUtil.verifyRunAndPull; + +class InternalAsyncSessionTest +{ + private Connection connection; + private ConnectionProvider connectionProvider; + private AsyncSession asyncSession; + private NetworkSession session; + + @BeforeEach + void setUp() + { + connection = connectionMock( BoltProtocolV4.INSTANCE ); + connectionProvider = mock( ConnectionProvider.class ); + when( connectionProvider.acquireConnection( any( String.class ), any( AccessMode.class ) ) ) + .thenReturn( completedFuture( connection ) ); + session = newSession( connectionProvider ); + asyncSession = new InternalAsyncSession( session ); + } + + private static Stream>> allSessionRunMethods() + { + return Stream.of( + session -> session.runAsync( "RETURN 1" ), + session -> session.runAsync( "RETURN $x", parameters( "x", 1 ) ), + session -> session.runAsync( "RETURN $x", singletonMap( "x", 1 ) ), + session -> session.runAsync( "RETURN $x", + new InternalRecord( singletonList( "x" ), new Value[]{new IntegerValue( 1 )} ) ), + session -> session.runAsync( new Statement( "RETURN $x", parameters( "x", 1 ) ) ), + session -> session.runAsync( new Statement( "RETURN $x", parameters( "x", 1 ) ), empty() ), + session -> session.runAsync( "RETURN $x", singletonMap( "x", 1 ), empty() ), + session -> session.runAsync( "RETURN 1", empty() ) + ); + } + + private static Stream>> allBeginTxMethods() + { + return Stream.of( + session -> session.beginTransactionAsync(), + session -> session.beginTransactionAsync( TransactionConfig.empty() ) + ); + } + + private static Stream>> allRunTxMethods() + { + return Stream.of( + session -> session.readTransactionAsync( tx -> completedFuture( "a" ) ), + session -> session.writeTransactionAsync( tx -> completedFuture( "a" ) ), + session -> session.readTransactionAsync( tx -> completedFuture( "a" ), empty() ), + session -> session.writeTransactionAsync( tx -> completedFuture( "a" ), empty() ) + ); + } + + @ParameterizedTest + @MethodSource( "allSessionRunMethods" ) + void shouldFlushOnRun( Function> runReturnOne ) throws Throwable + { + setupSuccessfulRunAndPull( connection ); + + StatementResultCursor cursor = await( runReturnOne.apply( asyncSession ) ); + + verifyRunAndPull( connection, await( cursor.summaryAsync() ).statement().text() ); + } + + @ParameterizedTest + @MethodSource( "allBeginTxMethods" ) + void shouldDelegateBeginTx( Function> beginTx ) throws Throwable + { + AsyncTransaction tx = await( beginTx.apply( asyncSession ) ); + + verifyBeginTx( connection ); + assertNotNull( tx ); + } + + @ParameterizedTest + @MethodSource( "allRunTxMethods" ) + void txRunShouldBeginAndCommitTx( Function> runTx ) throws Throwable + { + String string = await( runTx.apply( asyncSession ) ); + + verifyBeginTx( connection ); + verifyCommitTx( connection ); + verify( connection ).release(); + assertThat( string, equalTo( "a" ) ); + } + + + @Test + void rollsBackReadTxWhenFunctionThrows() + { + testTxRollbackWhenThrows( READ ); + } + + @Test + void rollsBackWriteTxWhenFunctionThrows() + { + testTxRollbackWhenThrows( WRITE ); + } + + @Test + void readTxRetriedUntilSuccessWhenFunctionThrows() + { + testTxIsRetriedUntilSuccessWhenFunctionThrows( READ ); + } + + @Test + void writeTxRetriedUntilSuccessWhenFunctionThrows() + { + testTxIsRetriedUntilSuccessWhenFunctionThrows( WRITE ); + } + + @Test + void readTxRetriedUntilSuccessWhenTxCloseThrows() + { + testTxIsRetriedUntilSuccessWhenCommitThrows( READ ); + } + + @Test + void writeTxRetriedUntilSuccessWhenTxCloseThrows() + { + testTxIsRetriedUntilSuccessWhenCommitThrows( WRITE ); + } + + @Test + void readTxRetriedUntilFailureWhenFunctionThrows() + { + testTxIsRetriedUntilFailureWhenFunctionThrows( READ ); + } + + @Test + void writeTxRetriedUntilFailureWhenFunctionThrows() + { + testTxIsRetriedUntilFailureWhenFunctionThrows( WRITE ); + } + + @Test + void readTxRetriedUntilFailureWhenTxCloseThrows() + { + testTxIsRetriedUntilFailureWhenCommitFails( READ ); + } + + @Test + void writeTxRetriedUntilFailureWhenTxCloseThrows() + { + testTxIsRetriedUntilFailureWhenCommitFails( WRITE ); + } + + + @Test + void shouldCloseSession() throws Throwable + { + await ( asyncSession.closeAsync() ); + assertFalse( this.session.isOpen() ); + } + + @Test + void shouldReturnBookmark() throws Throwable + { + session = newSession( connectionProvider, Bookmarks.from( "Bookmark1" ) ); + asyncSession = new InternalAsyncSession( session ); + + assertThat( asyncSession.lastBookmark(), equalTo( session.lastBookmark() )); + } + + private void testTxRollbackWhenThrows( AccessMode transactionMode ) + { + final RuntimeException error = new IllegalStateException( "Oh!" ); + AsyncTransactionWork> work = tx -> + { + throw error; + }; + + Exception e = assertThrows( Exception.class, () -> executeTransaction( asyncSession, transactionMode, work ) ); + assertEquals( error, e ); + + verify( connectionProvider ).acquireConnection( any( String.class ), eq( transactionMode ) ); + verifyBeginTx( connection ); + verifyRollbackTx( connection ); + } + + private void testTxIsRetriedUntilSuccessWhenFunctionThrows( AccessMode mode ) + { + int failures = 12; + int retries = failures + 1; + + RetryLogic retryLogic = new FixedRetryLogic( retries ); + session = newSession( connectionProvider, retryLogic ); + asyncSession = new InternalAsyncSession( session ); + + TxWork work = spy( new TxWork( 42, failures, new SessionExpiredException( "" ) ) ); + int answer = executeTransaction( asyncSession, mode, work ); + + assertEquals( 42, answer ); + verifyInvocationCount( work, failures + 1 ); + verifyCommitTx( connection ); + verifyRollbackTx( connection, times( failures ) ); + } + + private void testTxIsRetriedUntilSuccessWhenCommitThrows( AccessMode mode ) + { + int failures = 13; + int retries = failures + 1; + + RetryLogic retryLogic = new FixedRetryLogic( retries ); + setupFailingCommit( connection, failures ); + session = newSession( connectionProvider, retryLogic ); + asyncSession = new InternalAsyncSession( session ); + + TxWork work = spy( new TxWork( 43 ) ); + int answer = executeTransaction( asyncSession, mode, work ); + + assertEquals( 43, answer ); + verifyInvocationCount( work, failures + 1 ); + verifyCommitTx( connection, times( retries ) ); + } + + private void testTxIsRetriedUntilFailureWhenFunctionThrows( AccessMode mode ) + { + int failures = 14; + int retries = failures - 1; + + RetryLogic retryLogic = new FixedRetryLogic( retries ); + session = newSession( connectionProvider, retryLogic ); + asyncSession = new InternalAsyncSession( session ); + + TxWork work = spy( new TxWork( 42, failures, new SessionExpiredException( "Oh!" ) ) ); + + Exception e = assertThrows( Exception.class, () -> executeTransaction( asyncSession, mode, work ) ); + + MatcherAssert.assertThat( e, instanceOf( SessionExpiredException.class ) ); + assertEquals( "Oh!", e.getMessage() ); + verifyInvocationCount( work, failures ); + verifyCommitTx( connection, never() ); + verifyRollbackTx( connection, times( failures ) ); + } + + private void testTxIsRetriedUntilFailureWhenCommitFails( AccessMode mode ) + { + int failures = 17; + int retries = failures - 1; + + RetryLogic retryLogic = new FixedRetryLogic( retries ); + setupFailingCommit( connection, failures ); + session = newSession( connectionProvider, retryLogic ); + asyncSession = new InternalAsyncSession( session ); + + TxWork work = spy( new TxWork( 42 ) ); + + Exception e = assertThrows( Exception.class, () -> executeTransaction( asyncSession, mode, work ) ); + + MatcherAssert.assertThat( e, instanceOf( ServiceUnavailableException.class ) ); + verifyInvocationCount( work, failures ); + verifyCommitTx( connection, times( failures ) ); + } + + private static T executeTransaction( AsyncSession session, AccessMode mode, AsyncTransactionWork> work ) + { + if ( mode == READ ) + { + return await( session.readTransactionAsync( work ) ); + } + else if ( mode == WRITE ) + { + return await( session.writeTransactionAsync( work ) ); + } + else + { + throw new IllegalArgumentException( "Unknown mode " + mode ); + } + } + + private static void verifyInvocationCount( AsyncTransactionWork workSpy, int expectedInvocationCount ) + { + verify( workSpy, times( expectedInvocationCount ) ).execute( any( AsyncTransaction.class ) ); + } + + private static class TxWork implements AsyncTransactionWork> + { + final int result; + final int timesToThrow; + final Supplier errorSupplier; + + int invoked; + + @SuppressWarnings( "unchecked" ) + TxWork( int result ) + { + this( result, 0, (Supplier) null ); + } + + TxWork( int result, int timesToThrow, final RuntimeException error ) + { + this.result = result; + this.timesToThrow = timesToThrow; + this.errorSupplier = () -> error; + } + + TxWork( int result, int timesToThrow, Supplier errorSupplier ) + { + this.result = result; + this.timesToThrow = timesToThrow; + this.errorSupplier = errorSupplier; + } + + @Override + public CompletionStage execute( AsyncTransaction tx ) + { + if ( timesToThrow > 0 && invoked++ < timesToThrow ) + { + throw errorSupplier.get(); + } + return completedFuture( result ); + } + } +} diff --git a/driver/src/test/java/org/neo4j/driver/internal/async/InternalAsyncTransactionTest.java b/driver/src/test/java/org/neo4j/driver/internal/async/InternalAsyncTransactionTest.java new file mode 100644 index 0000000000..fca3c84275 --- /dev/null +++ b/driver/src/test/java/org/neo4j/driver/internal/async/InternalAsyncTransactionTest.java @@ -0,0 +1,155 @@ +/* + * Copyright (c) 2002-2019 "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; + +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.MethodSource; + +import java.util.concurrent.CompletionStage; +import java.util.function.Function; +import java.util.stream.Stream; + +import org.neo4j.driver.AccessMode; +import org.neo4j.driver.Statement; +import org.neo4j.driver.Value; +import org.neo4j.driver.async.AsyncTransaction; +import org.neo4j.driver.async.StatementResultCursor; +import org.neo4j.driver.exceptions.ClientException; +import org.neo4j.driver.internal.InternalRecord; +import org.neo4j.driver.internal.messaging.v4.BoltProtocolV4; +import org.neo4j.driver.internal.spi.Connection; +import org.neo4j.driver.internal.spi.ConnectionProvider; +import org.neo4j.driver.internal.value.IntegerValue; +import org.neo4j.driver.summary.ResultSummary; + +import static java.util.Collections.singletonList; +import static java.util.Collections.singletonMap; +import static java.util.concurrent.CompletableFuture.completedFuture; +import static org.hamcrest.CoreMatchers.containsString; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertThat; +import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; +import static org.neo4j.driver.Values.parameters; +import static org.neo4j.driver.util.TestUtil.await; +import static org.neo4j.driver.util.TestUtil.connectionMock; +import static org.neo4j.driver.util.TestUtil.newSession; +import static org.neo4j.driver.util.TestUtil.setupFailingCommit; +import static org.neo4j.driver.util.TestUtil.setupFailingRollback; +import static org.neo4j.driver.util.TestUtil.setupSuccessfulRunAndPull; +import static org.neo4j.driver.util.TestUtil.verifyCommitTx; +import static org.neo4j.driver.util.TestUtil.verifyRollbackTx; +import static org.neo4j.driver.util.TestUtil.verifyRunAndPull; + +class InternalAsyncTransactionTest +{ + private Connection connection; + private InternalAsyncTransaction tx; + + @BeforeEach + void setUp() + { + connection = connectionMock( BoltProtocolV4.INSTANCE ); + ConnectionProvider connectionProvider = mock( ConnectionProvider.class ); + when( connectionProvider.acquireConnection( any( String.class ), any( AccessMode.class ) ) ) + .thenReturn( completedFuture( connection ) ); + InternalAsyncSession session = new InternalAsyncSession( newSession( connectionProvider ) ); + tx = (InternalAsyncTransaction) await( session.beginTransactionAsync() ); + } + + private static Stream>> allSessionRunMethods() + { + return Stream.of( + tx -> tx.runAsync( "RETURN 1" ), + tx -> tx.runAsync( "RETURN $x", parameters( "x", 1 ) ), + tx -> tx.runAsync( "RETURN $x", singletonMap( "x", 1 ) ), + tx -> tx.runAsync( "RETURN $x", + new InternalRecord( singletonList( "x" ), new Value[]{new IntegerValue( 1 )} ) ), + tx -> tx.runAsync( new Statement( "RETURN $x", parameters( "x", 1 ) ) ) + ); + } + + @ParameterizedTest + @MethodSource( "allSessionRunMethods" ) + void shouldFlushOnRun( Function> runReturnOne ) throws Throwable + { + setupSuccessfulRunAndPull( connection ); + + StatementResultCursor result = await( runReturnOne.apply( tx ) ); + ResultSummary summary = await( result.summaryAsync() ); + + verifyRunAndPull( connection, summary.statement().text() ); + } + + @Test + void shouldCommit() throws Throwable + { + await( tx.commitAsync() ); + + verifyCommitTx( connection ); + verify( connection ).release(); + assertFalse( tx.isOpen() ); + } + + @Test + void shouldRollback() throws Throwable + { + await( tx.rollbackAsync() ); + + verifyRollbackTx( connection ); + verify( connection ).release(); + assertFalse( tx.isOpen() ); + } + + @Test + void shouldRollbackWhenFailedRun() throws Throwable + { + tx.markTerminated(); + ClientException clientException = assertThrows( ClientException.class, () -> await( tx.commitAsync() ) ); + + assertThat( clientException.getMessage(), containsString( "It has been rolled back either because of an error or explicit termination" ) ); + verify( connection ).release(); + assertFalse( tx.isOpen() ); + } + + @Test + void shouldReleaseConnectionWhenFailedToCommit() throws Throwable + { + setupFailingCommit( connection ); + assertThrows( Exception.class, () -> await( tx.commitAsync() ) ); + + verify( connection ).release(); + assertFalse( tx.isOpen() ); + } + + @Test + void shouldReleaseConnectionWhenFailedToRollback() throws Throwable + { + setupFailingRollback( connection ); + assertThrows( Exception.class, () -> await( tx.rollbackAsync() ) ); + + verify( connection ).release(); + assertFalse( tx.isOpen() ); + } +} diff --git a/driver/src/test/java/org/neo4j/driver/internal/LeakLoggingNetworkSessionTest.java b/driver/src/test/java/org/neo4j/driver/internal/async/LeakLoggingNetworkSessionTest.java similarity index 94% rename from driver/src/test/java/org/neo4j/driver/internal/LeakLoggingNetworkSessionTest.java rename to driver/src/test/java/org/neo4j/driver/internal/async/LeakLoggingNetworkSessionTest.java index c2d5569e54..00da69a766 100644 --- a/driver/src/test/java/org/neo4j/driver/internal/LeakLoggingNetworkSessionTest.java +++ b/driver/src/test/java/org/neo4j/driver/internal/async/LeakLoggingNetworkSessionTest.java @@ -16,7 +16,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package org.neo4j.driver.internal; +package org.neo4j.driver.internal.async; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.TestInfo; @@ -24,13 +24,14 @@ import java.lang.reflect.Method; -import org.neo4j.driver.internal.util.FixedRetryLogic; -import org.neo4j.driver.internal.spi.Connection; -import org.neo4j.driver.internal.spi.ConnectionProvider; import org.neo4j.driver.AccessMode; import org.neo4j.driver.Logger; import org.neo4j.driver.Logging; -import org.neo4j.driver.Session; +import org.neo4j.driver.TransactionConfig; +import org.neo4j.driver.internal.DefaultBookmarksHolder; +import org.neo4j.driver.internal.spi.Connection; +import org.neo4j.driver.internal.spi.ConnectionProvider; +import org.neo4j.driver.internal.util.FixedRetryLogic; import org.neo4j.driver.util.TestUtil; import static java.util.concurrent.CompletableFuture.completedFuture; @@ -70,7 +71,7 @@ void logsMessageWithStacktraceDuringFinalizationIfLeaked( TestInfo testInfo ) th when( logging.getLog( anyString() ) ).thenReturn( log ); LeakLoggingNetworkSession session = newSession( logging, true ); // begin transaction to make session obtain a connection - session.beginTransaction(); + session.beginTransactionAsync( TransactionConfig.empty() ); finalize( session ); @@ -87,7 +88,7 @@ void logsMessageWithStacktraceDuringFinalizationIfLeaked( TestInfo testInfo ) th ); } - private static void finalize( Session session ) throws Exception + private static void finalize( NetworkSession session ) throws Exception { Method finalizeMethod = session.getClass().getDeclaredMethod( "finalize" ); finalizeMethod.setAccessible( true ); diff --git a/driver/src/test/java/org/neo4j/driver/internal/async/NetworkSessionTest.java b/driver/src/test/java/org/neo4j/driver/internal/async/NetworkSessionTest.java new file mode 100644 index 0000000000..57771ce4d2 --- /dev/null +++ b/driver/src/test/java/org/neo4j/driver/internal/async/NetworkSessionTest.java @@ -0,0 +1,459 @@ +/* + * Copyright (c) 2002-2019 "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; + +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.ValueSource; +import org.mockito.InOrder; + +import org.neo4j.driver.AccessMode; +import org.neo4j.driver.Statement; +import org.neo4j.driver.TransactionConfig; +import org.neo4j.driver.async.StatementResultCursor; +import org.neo4j.driver.exceptions.ClientException; +import org.neo4j.driver.internal.Bookmarks; +import org.neo4j.driver.internal.messaging.BoltProtocol; +import org.neo4j.driver.internal.messaging.request.PullMessage; +import org.neo4j.driver.internal.messaging.request.RunWithMetadataMessage; +import org.neo4j.driver.internal.messaging.v4.BoltProtocolV4; +import org.neo4j.driver.internal.spi.Connection; +import org.neo4j.driver.internal.spi.ConnectionProvider; + +import static java.util.concurrent.CompletableFuture.completedFuture; +import static org.hamcrest.CoreMatchers.containsString; +import static org.hamcrest.CoreMatchers.equalTo; +import static org.hamcrest.CoreMatchers.instanceOf; +import static org.hamcrest.junit.MatcherAssert.assertThat; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertNull; +import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.junit.jupiter.api.Assertions.assertTrue; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.Mockito.atLeastOnce; +import static org.mockito.Mockito.doReturn; +import static org.mockito.Mockito.inOrder; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.never; +import static org.mockito.Mockito.spy; +import static org.mockito.Mockito.times; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; +import static org.neo4j.driver.AccessMode.READ; +import static org.neo4j.driver.AccessMode.WRITE; +import static org.neo4j.driver.internal.messaging.request.MultiDatabaseUtil.ABSENT_DB_NAME; +import static org.neo4j.driver.internal.util.Futures.failedFuture; +import static org.neo4j.driver.util.TestUtil.await; +import static org.neo4j.driver.util.TestUtil.connectionMock; +import static org.neo4j.driver.util.TestUtil.newSession; +import static org.neo4j.driver.util.TestUtil.setupFailingBegin; +import static org.neo4j.driver.util.TestUtil.setupSuccessfulRun; +import static org.neo4j.driver.util.TestUtil.setupSuccessfulRunAndPull; +import static org.neo4j.driver.util.TestUtil.verifyBeginTx; +import static org.neo4j.driver.util.TestUtil.verifyRollbackTx; +import static org.neo4j.driver.util.TestUtil.verifyRun; +import static org.neo4j.driver.util.TestUtil.verifyRunAndPull; + +class NetworkSessionTest +{ + private Connection connection; + private ConnectionProvider connectionProvider; + private NetworkSession session; + + @BeforeEach + void setUp() + { + connection = connectionMock( BoltProtocolV4.INSTANCE ); + connectionProvider = mock( ConnectionProvider.class ); + when( connectionProvider.acquireConnection( any( String.class ), any( AccessMode.class ) ) ) + .thenReturn( completedFuture( connection ) ); + session = newSession( connectionProvider ); + } + + @ParameterizedTest + @ValueSource( strings = {"true", "false"} ) + void shouldFlushOnRunAsync( boolean waitForResponse ) + { + setupSuccessfulRunAndPull( connection ); + await( session.runAsync( new Statement( "RETURN 1" ), TransactionConfig.empty(), waitForResponse ) ); + + verifyRunAndPull( connection, "RETURN 1" ); + } + + @Test + void shouldFlushOnRunRx() + { + setupSuccessfulRun( connection ); + await( session.runRx( new Statement( "RETURN 1" ), TransactionConfig.empty() ) ); + + verifyRun( connection, "RETURN 1" ); + } + + @Test + void shouldNotAllowNewTxWhileOneIsRunning() + { + // Given + beginTransaction( session ); + + // Expect + assertThrows( ClientException.class, () -> beginTransaction( session ) ); + } + + @Test + void shouldBeAbleToOpenTxAfterPreviousIsClosed() + { + // Given + await( beginTransaction( session ).closeAsync() ); + + // When + ExplicitTransaction tx = beginTransaction( session ); + + // Then we should've gotten a transaction object back + assertNotNull( tx ); + verifyRollbackTx( connection ); + } + + @Test + void shouldNotBeAbleToUseSessionWhileOngoingTransaction() + { + // Given + beginTransaction( session ); + + // Expect + assertThrows( ClientException.class, () -> run( session, "RETURN 1" ) ); + } + + @Test + void shouldBeAbleToUseSessionAgainWhenTransactionIsClosed() + { + // Given + await ( beginTransaction( session ).closeAsync() ); + + // When + run( session, "RETURN 1" ); + + // Then + verifyRunAndPull( connection, "RETURN 1" ); + } + + @Test + void shouldNotCloseAlreadyClosedSession() + { + beginTransaction( session ); + + close( session ); + close( session ); + close( session ); + + verifyRollbackTx( connection ); + } + + @Test + void runThrowsWhenSessionIsClosed() + { + close( session ); + + Exception e = assertThrows( Exception.class, () -> run( session, "CREATE ()" ) ); + assertThat( e, instanceOf( ClientException.class ) ); + assertThat( e.getMessage(), containsString( "session is already closed" ) ); + } + + @Test + void acquiresNewConnectionForRun() + { + run( session, "RETURN 1" ); + + verify( connectionProvider ).acquireConnection( any( String.class ), any( AccessMode.class ) ); + } + + @Test + void releasesOpenConnectionUsedForRunWhenSessionIsClosed() + { + String query = "RETURN 1"; + setupSuccessfulRunAndPull( connection, query ); + + run( session, query ); + + close( session ); + + InOrder inOrder = inOrder( connection ); + inOrder.verify( connection ).writeAndFlush( any( RunWithMetadataMessage.class ), any(), any( PullMessage.class ), any() ); + inOrder.verify( connection, atLeastOnce() ).release(); + } + + @SuppressWarnings( "deprecation" ) + @Test + void resetDoesNothingWhenNoTransactionAndNoConnection() + { + await( session.resetAsync() ); + + verify( connectionProvider, never() ).acquireConnection( any( String.class ), any( AccessMode.class ) ); + } + + @Test + void closeWithoutConnection() + { + NetworkSession session = newSession( connectionProvider ); + + close( session ); + + verify( connectionProvider, never() ).acquireConnection( any( String.class ), any( AccessMode.class ) ); + } + + @Test + void acquiresNewConnectionForBeginTx() + { + ExplicitTransaction tx = beginTransaction( session ); + + assertNotNull( tx ); + verify( connectionProvider ).acquireConnection( any( String.class ), any( AccessMode.class ) ); + } + + @Test + void updatesBookmarkWhenTxIsClosed() + { + Bookmarks bookmarkAfterCommit = Bookmarks.from( "TheBookmark" ); + + BoltProtocol protocol = spy( BoltProtocolV4.INSTANCE ); + doReturn( completedFuture( bookmarkAfterCommit ) ).when( protocol ).commitTransaction( any( Connection.class ) ); + + when( connection.protocol() ).thenReturn( protocol ); + + ExplicitTransaction tx = beginTransaction( session ); + assertNull( session.lastBookmark() ); + + tx.success(); + await( tx.closeAsync() ); + assertEquals( "TheBookmark", session.lastBookmark() ); + } + + @Test + void releasesConnectionWhenTxIsClosed() + { + String query = "RETURN 42"; + setupSuccessfulRunAndPull( connection, query ); + + ExplicitTransaction tx = beginTransaction( session ); + await( tx.runAsync( new Statement( query ), false ) ); + + verify( connectionProvider ).acquireConnection( any( String.class ), any( AccessMode.class ) ); + verifyRunAndPull( connection, query ); + + await( tx.closeAsync() ); + verify( connection ).release(); + } + + @Test + void bookmarkIsPropagatedFromSession() + { + Bookmarks bookmarks = Bookmarks.from( "Bookmarks" ); + NetworkSession session = newSession( connectionProvider, bookmarks ); + + ExplicitTransaction tx = beginTransaction( session ); + assertNotNull( tx ); + verifyBeginTx( connection, bookmarks ); + } + + @Test + void bookmarkIsPropagatedBetweenTransactions() + { + Bookmarks bookmarks1 = Bookmarks.from( "Bookmark1" ); + Bookmarks bookmarks2 = Bookmarks.from( "Bookmark2" ); + + NetworkSession session = newSession( connectionProvider ); + + BoltProtocol protocol = spy( BoltProtocolV4.INSTANCE ); + doReturn( completedFuture( bookmarks1 ), completedFuture( bookmarks2 ) ).when( protocol ).commitTransaction( any( Connection.class ) ); + + when( connection.protocol() ).thenReturn( protocol ); + + ExplicitTransaction tx1 = beginTransaction( session ); + tx1.success(); + await( tx1.closeAsync() ); + assertEquals( bookmarks1, Bookmarks.from( session.lastBookmark() ) ); + + ExplicitTransaction tx2 = beginTransaction( session ); + verifyBeginTx( connection, bookmarks1 ); + tx2.success(); + await( tx2.closeAsync() ); + + assertEquals( bookmarks2, Bookmarks.from( session.lastBookmark() ) ); + } + + @Test + void accessModeUsedToAcquireConnections() + { + NetworkSession session1 = newSession( connectionProvider, READ ); + beginTransaction( session1 ); + verify( connectionProvider ).acquireConnection( ABSENT_DB_NAME, READ ); + + NetworkSession session2 = newSession( connectionProvider, WRITE ); + beginTransaction( session2 ); + verify( connectionProvider ).acquireConnection( ABSENT_DB_NAME, WRITE ); + } + + @Test + void testPassingNoBookmarkShouldRetainBookmark() + { + NetworkSession session = newSession( connectionProvider, Bookmarks.from( "X" ) ); + beginTransaction( session ); + assertThat( session.lastBookmark(), equalTo( "X" ) ); + } + + @Test + void connectionShouldBeResetAfterSessionReset() + { + run( session, "RETURN 1" ); + + verify( connection, never() ).reset(); + verify( connection, never() ).release(); + + await( session.resetAsync() ); + verify( connection ).reset(); + verify( connection, never() ).release(); + } + + @Test + void shouldHaveNullLastBookmarkInitially() + { + assertNull( session.lastBookmark() ); + } + + @Test + void shouldDoNothingWhenClosingWithoutAcquiredConnection() + { + RuntimeException error = new RuntimeException( "Hi" ); + when( connectionProvider.acquireConnection( any( String.class ), any( AccessMode.class ) ) ).thenReturn( failedFuture( error ) ); + + Exception e = assertThrows( Exception.class, () -> run( session, "RETURN 1" ) ); + assertEquals( error, e ); + + close( session ); + } + + @Test + void shouldRunAfterRunFailureToAcquireConnection() + { + RuntimeException error = new RuntimeException( "Hi" ); + when( connectionProvider.acquireConnection( any( String.class ), any( AccessMode.class ) ) ) + .thenReturn( failedFuture( error ) ).thenReturn( completedFuture( connection ) ); + + Exception e = assertThrows( Exception.class, () -> run( session,"RETURN 1" ) ); + assertEquals( error, e ); + + run( session, "RETURN 2" ); + + verify( connectionProvider, times( 2 ) ).acquireConnection( any( String.class ), any( AccessMode.class ) ); + verifyRunAndPull( connection, "RETURN 2" ); + } + + @Test + void shouldRunAfterBeginTxFailureOnBookmark() + { + RuntimeException error = new RuntimeException( "Hi" ); + Connection connection1 = connectionMock( BoltProtocolV4.INSTANCE ); + setupFailingBegin( connection1, error ); + Connection connection2 = connectionMock( BoltProtocolV4.INSTANCE ); + + when( connectionProvider.acquireConnection( any( String.class ), any( AccessMode.class ) ) ) + .thenReturn( completedFuture( connection1 ) ).thenReturn( completedFuture( connection2 ) ); + + Bookmarks bookmarks = Bookmarks.from( "neo4j:bookmark:v1:tx42" ); + NetworkSession session = newSession( connectionProvider, bookmarks ); + + Exception e = assertThrows( Exception.class, () -> beginTransaction( session ) ); + assertEquals( error, e ); + + run( session, "RETURN 2" ); + + verify( connectionProvider, times( 2 ) ).acquireConnection( any( String.class ), any( AccessMode.class ) ); + verifyBeginTx( connection1, bookmarks ); + verifyRunAndPull( connection2, "RETURN 2" ); + } + + @Test + void shouldBeginTxAfterBeginTxFailureOnBookmark() + { + RuntimeException error = new RuntimeException( "Hi" ); + Connection connection1 = connectionMock( BoltProtocolV4.INSTANCE ); + setupFailingBegin( connection1, error ); + Connection connection2 = connectionMock( BoltProtocolV4.INSTANCE ); + + when( connectionProvider.acquireConnection( any( String.class ), any( AccessMode.class ) ) ) + .thenReturn( completedFuture( connection1 ) ).thenReturn( completedFuture( connection2 ) ); + + Bookmarks bookmarks = Bookmarks.from( "neo4j:bookmark:v1:tx42" ); + NetworkSession session = newSession( connectionProvider, bookmarks ); + + Exception e = assertThrows( Exception.class, () -> beginTransaction( session ) ); + assertEquals( error, e ); + + beginTransaction( session ); + + verify( connectionProvider, times( 2 ) ).acquireConnection( any( String.class ), any( AccessMode.class ) ); + verifyBeginTx( connection1, bookmarks ); + verifyBeginTx( connection2, bookmarks ); + } + + @Test + void shouldBeginTxAfterRunFailureToAcquireConnection() + { + RuntimeException error = new RuntimeException( "Hi" ); + when( connectionProvider.acquireConnection( any( String.class ), any( AccessMode.class ) ) ) + .thenReturn( failedFuture( error ) ).thenReturn( completedFuture( connection ) ); + + Exception e = assertThrows( Exception.class, () -> run( session, "RETURN 1" ) ); + assertEquals( error, e ); + + beginTransaction( session ); + + verify( connectionProvider, times( 2 ) ).acquireConnection( any( String.class ), any( AccessMode.class ) ); + verifyBeginTx( connection ); + } + + @Test + void shouldMarkTransactionAsTerminatedAndThenResetConnectionOnReset() + { + ExplicitTransaction tx = beginTransaction( session ); + + assertTrue( tx.isOpen() ); + verify( connection, never() ).reset(); + + await( session.resetAsync() ); + + verify( connection ).reset(); + } + + private static StatementResultCursor run( NetworkSession session, String statement ) + { + return await( session.runAsync( new Statement( statement ), TransactionConfig.empty(), false ) ); + } + + private static ExplicitTransaction beginTransaction( NetworkSession session ) + { + return await( session.beginTransactionAsync( TransactionConfig.empty() ) ); + } + + private static void close( NetworkSession session ) + { + await( session.closeAsync() ); + } +} diff --git a/driver/src/test/java/org/neo4j/driver/internal/async/ResultCursorsHolderTest.java b/driver/src/test/java/org/neo4j/driver/internal/async/ResultCursorsHolderTest.java index e2fea8953d..24dfac964a 100644 --- a/driver/src/test/java/org/neo4j/driver/internal/async/ResultCursorsHolderTest.java +++ b/driver/src/test/java/org/neo4j/driver/internal/async/ResultCursorsHolderTest.java @@ -25,7 +25,6 @@ import java.util.concurrent.CompletionStage; import java.util.concurrent.TimeoutException; -import org.neo4j.driver.internal.AsyncStatementResultCursor; import org.neo4j.driver.internal.util.Futures; import static java.util.concurrent.CompletableFuture.completedFuture; diff --git a/driver/src/test/java/org/neo4j/driver/internal/async/BoltProtocolUtilTest.java b/driver/src/test/java/org/neo4j/driver/internal/async/connection/BoltProtocolUtilTest.java similarity index 80% rename from driver/src/test/java/org/neo4j/driver/internal/async/BoltProtocolUtilTest.java rename to driver/src/test/java/org/neo4j/driver/internal/async/connection/BoltProtocolUtilTest.java index 70b6bf1014..76bb5434c7 100644 --- a/driver/src/test/java/org/neo4j/driver/internal/async/BoltProtocolUtilTest.java +++ b/driver/src/test/java/org/neo4j/driver/internal/async/connection/BoltProtocolUtilTest.java @@ -16,7 +16,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package org.neo4j.driver.internal.async; +package org.neo4j.driver.internal.async.connection; import io.netty.buffer.ByteBuf; import io.netty.buffer.Unpooled; @@ -28,12 +28,12 @@ import org.neo4j.driver.internal.messaging.v4.BoltProtocolV4; import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.neo4j.driver.internal.async.BoltProtocolUtil.BOLT_MAGIC_PREAMBLE; -import static org.neo4j.driver.internal.async.BoltProtocolUtil.handshakeBuf; -import static org.neo4j.driver.internal.async.BoltProtocolUtil.handshakeString; -import static org.neo4j.driver.internal.async.BoltProtocolUtil.writeChunkHeader; -import static org.neo4j.driver.internal.async.BoltProtocolUtil.writeEmptyChunkHeader; -import static org.neo4j.driver.internal.async.BoltProtocolUtil.writeMessageBoundary; +import static org.neo4j.driver.internal.async.connection.BoltProtocolUtil.BOLT_MAGIC_PREAMBLE; +import static org.neo4j.driver.internal.async.connection.BoltProtocolUtil.handshakeBuf; +import static org.neo4j.driver.internal.async.connection.BoltProtocolUtil.handshakeString; +import static org.neo4j.driver.internal.async.connection.BoltProtocolUtil.writeChunkHeader; +import static org.neo4j.driver.internal.async.connection.BoltProtocolUtil.writeEmptyChunkHeader; +import static org.neo4j.driver.internal.async.connection.BoltProtocolUtil.writeMessageBoundary; import static org.neo4j.driver.util.TestUtil.assertByteBufContains; class BoltProtocolUtilTest diff --git a/driver/src/test/java/org/neo4j/driver/internal/async/ChannelAttributesTest.java b/driver/src/test/java/org/neo4j/driver/internal/async/connection/ChannelAttributesTest.java similarity index 76% rename from driver/src/test/java/org/neo4j/driver/internal/async/ChannelAttributesTest.java rename to driver/src/test/java/org/neo4j/driver/internal/async/connection/ChannelAttributesTest.java index c870332c8c..b7e6d2de8f 100644 --- a/driver/src/test/java/org/neo4j/driver/internal/async/ChannelAttributesTest.java +++ b/driver/src/test/java/org/neo4j/driver/internal/async/connection/ChannelAttributesTest.java @@ -16,7 +16,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package org.neo4j.driver.internal.async; +package org.neo4j.driver.internal.async.connection; import io.netty.channel.embedded.EmbeddedChannel; import org.junit.jupiter.api.Test; @@ -29,22 +29,22 @@ import static org.junit.jupiter.api.Assertions.assertNull; import static org.junit.jupiter.api.Assertions.assertThrows; import static org.mockito.Mockito.mock; -import static org.neo4j.driver.internal.async.ChannelAttributes.connectionId; -import static org.neo4j.driver.internal.async.ChannelAttributes.creationTimestamp; -import static org.neo4j.driver.internal.async.ChannelAttributes.lastUsedTimestamp; -import static org.neo4j.driver.internal.async.ChannelAttributes.messageDispatcher; -import static org.neo4j.driver.internal.async.ChannelAttributes.protocolVersion; -import static org.neo4j.driver.internal.async.ChannelAttributes.serverAddress; -import static org.neo4j.driver.internal.async.ChannelAttributes.serverVersion; -import static org.neo4j.driver.internal.async.ChannelAttributes.setConnectionId; -import static org.neo4j.driver.internal.async.ChannelAttributes.setCreationTimestamp; -import static org.neo4j.driver.internal.async.ChannelAttributes.setLastUsedTimestamp; -import static org.neo4j.driver.internal.async.ChannelAttributes.setMessageDispatcher; -import static org.neo4j.driver.internal.async.ChannelAttributes.setProtocolVersion; -import static org.neo4j.driver.internal.async.ChannelAttributes.setServerAddress; -import static org.neo4j.driver.internal.async.ChannelAttributes.setServerVersion; -import static org.neo4j.driver.internal.async.ChannelAttributes.setTerminationReason; -import static org.neo4j.driver.internal.async.ChannelAttributes.terminationReason; +import static org.neo4j.driver.internal.async.connection.ChannelAttributes.connectionId; +import static org.neo4j.driver.internal.async.connection.ChannelAttributes.creationTimestamp; +import static org.neo4j.driver.internal.async.connection.ChannelAttributes.lastUsedTimestamp; +import static org.neo4j.driver.internal.async.connection.ChannelAttributes.messageDispatcher; +import static org.neo4j.driver.internal.async.connection.ChannelAttributes.protocolVersion; +import static org.neo4j.driver.internal.async.connection.ChannelAttributes.serverAddress; +import static org.neo4j.driver.internal.async.connection.ChannelAttributes.serverVersion; +import static org.neo4j.driver.internal.async.connection.ChannelAttributes.setConnectionId; +import static org.neo4j.driver.internal.async.connection.ChannelAttributes.setCreationTimestamp; +import static org.neo4j.driver.internal.async.connection.ChannelAttributes.setLastUsedTimestamp; +import static org.neo4j.driver.internal.async.connection.ChannelAttributes.setMessageDispatcher; +import static org.neo4j.driver.internal.async.connection.ChannelAttributes.setProtocolVersion; +import static org.neo4j.driver.internal.async.connection.ChannelAttributes.setServerAddress; +import static org.neo4j.driver.internal.async.connection.ChannelAttributes.setServerVersion; +import static org.neo4j.driver.internal.async.connection.ChannelAttributes.setTerminationReason; +import static org.neo4j.driver.internal.async.connection.ChannelAttributes.terminationReason; import static org.neo4j.driver.internal.util.ServerVersion.version; class ChannelAttributesTest diff --git a/driver/src/test/java/org/neo4j/driver/internal/async/ChannelConnectedListenerTest.java b/driver/src/test/java/org/neo4j/driver/internal/async/connection/ChannelConnectedListenerTest.java similarity index 95% rename from driver/src/test/java/org/neo4j/driver/internal/async/ChannelConnectedListenerTest.java rename to driver/src/test/java/org/neo4j/driver/internal/async/connection/ChannelConnectedListenerTest.java index f3c9a0d889..f45f316ad2 100644 --- a/driver/src/test/java/org/neo4j/driver/internal/async/ChannelConnectedListenerTest.java +++ b/driver/src/test/java/org/neo4j/driver/internal/async/connection/ChannelConnectedListenerTest.java @@ -16,7 +16,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package org.neo4j.driver.internal.async; +package org.neo4j.driver.internal.async.connection; import io.netty.channel.ChannelPromise; import io.netty.channel.embedded.EmbeddedChannel; @@ -32,7 +32,7 @@ import static org.junit.jupiter.api.Assertions.assertThrows; import static org.junit.jupiter.api.Assertions.assertTrue; import static org.neo4j.driver.internal.BoltServerAddress.LOCAL_DEFAULT; -import static org.neo4j.driver.internal.async.BoltProtocolUtil.handshakeBuf; +import static org.neo4j.driver.internal.async.connection.BoltProtocolUtil.handshakeBuf; import static org.neo4j.driver.internal.logging.DevNullLogging.DEV_NULL_LOGGING; import static org.neo4j.driver.util.TestUtil.await; diff --git a/driver/src/test/java/org/neo4j/driver/internal/async/ChannelConnectorImplIT.java b/driver/src/test/java/org/neo4j/driver/internal/async/connection/ChannelConnectorImplIT.java similarity index 99% rename from driver/src/test/java/org/neo4j/driver/internal/async/ChannelConnectorImplIT.java rename to driver/src/test/java/org/neo4j/driver/internal/async/connection/ChannelConnectorImplIT.java index 504238a6c4..53aabadafc 100644 --- a/driver/src/test/java/org/neo4j/driver/internal/async/ChannelConnectorImplIT.java +++ b/driver/src/test/java/org/neo4j/driver/internal/async/connection/ChannelConnectorImplIT.java @@ -16,7 +16,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package org.neo4j.driver.internal.async; +package org.neo4j.driver.internal.async.connection; import io.netty.bootstrap.Bootstrap; import io.netty.channel.Channel; @@ -36,15 +36,15 @@ import java.util.concurrent.ExecutionException; import java.util.concurrent.TimeUnit; +import org.neo4j.driver.AuthToken; +import org.neo4j.driver.AuthTokens; +import org.neo4j.driver.exceptions.AuthenticationException; +import org.neo4j.driver.exceptions.ServiceUnavailableException; import org.neo4j.driver.internal.BoltServerAddress; import org.neo4j.driver.internal.ConnectionSettings; import org.neo4j.driver.internal.async.inbound.ConnectTimeoutHandler; import org.neo4j.driver.internal.security.SecurityPlan; import org.neo4j.driver.internal.util.FakeClock; -import org.neo4j.driver.AuthToken; -import org.neo4j.driver.AuthTokens; -import org.neo4j.driver.exceptions.AuthenticationException; -import org.neo4j.driver.exceptions.ServiceUnavailableException; import org.neo4j.driver.util.DatabaseExtension; import org.neo4j.driver.util.ParallelizableIT; diff --git a/driver/src/test/java/org/neo4j/driver/internal/async/ChannelErrorHandlerTest.java b/driver/src/test/java/org/neo4j/driver/internal/async/connection/ChannelErrorHandlerTest.java similarity index 95% rename from driver/src/test/java/org/neo4j/driver/internal/async/ChannelErrorHandlerTest.java rename to driver/src/test/java/org/neo4j/driver/internal/async/connection/ChannelErrorHandlerTest.java index 6e252f41df..ed2d1c2288 100644 --- a/driver/src/test/java/org/neo4j/driver/internal/async/ChannelErrorHandlerTest.java +++ b/driver/src/test/java/org/neo4j/driver/internal/async/connection/ChannelErrorHandlerTest.java @@ -16,7 +16,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package org.neo4j.driver.internal.async; +package org.neo4j.driver.internal.async.connection; import io.netty.channel.embedded.EmbeddedChannel; import io.netty.handler.codec.CodecException; @@ -36,8 +36,8 @@ import static org.hamcrest.junit.MatcherAssert.assertThat; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertFalse; -import static org.neo4j.driver.internal.async.ChannelAttributes.setMessageDispatcher; -import static org.neo4j.driver.internal.async.ChannelAttributes.setTerminationReason; +import static org.neo4j.driver.internal.async.connection.ChannelAttributes.setMessageDispatcher; +import static org.neo4j.driver.internal.async.connection.ChannelAttributes.setTerminationReason; import static org.neo4j.driver.internal.logging.DevNullLogging.DEV_NULL_LOGGING; class ChannelErrorHandlerTest diff --git a/driver/src/test/java/org/neo4j/driver/internal/async/ChannelPipelineBuilderImplTest.java b/driver/src/test/java/org/neo4j/driver/internal/async/connection/ChannelPipelineBuilderImplTest.java similarity index 97% rename from driver/src/test/java/org/neo4j/driver/internal/async/ChannelPipelineBuilderImplTest.java rename to driver/src/test/java/org/neo4j/driver/internal/async/connection/ChannelPipelineBuilderImplTest.java index b8c1262bbf..cddb0bcf2f 100644 --- a/driver/src/test/java/org/neo4j/driver/internal/async/ChannelPipelineBuilderImplTest.java +++ b/driver/src/test/java/org/neo4j/driver/internal/async/connection/ChannelPipelineBuilderImplTest.java @@ -16,7 +16,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package org.neo4j.driver.internal.async; +package org.neo4j.driver.internal.async.connection; import io.netty.channel.ChannelHandler; import io.netty.channel.embedded.EmbeddedChannel; diff --git a/driver/src/test/java/org/neo4j/driver/internal/async/DecoratedConnectionTest.java b/driver/src/test/java/org/neo4j/driver/internal/async/connection/DecoratedConnectionTest.java similarity index 99% rename from driver/src/test/java/org/neo4j/driver/internal/async/DecoratedConnectionTest.java rename to driver/src/test/java/org/neo4j/driver/internal/async/connection/DecoratedConnectionTest.java index 5e9f6d0bdf..db0ae23915 100644 --- a/driver/src/test/java/org/neo4j/driver/internal/async/DecoratedConnectionTest.java +++ b/driver/src/test/java/org/neo4j/driver/internal/async/connection/DecoratedConnectionTest.java @@ -16,20 +16,20 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package org.neo4j.driver.internal.async; +package org.neo4j.driver.internal.async.connection; import org.junit.jupiter.api.Test; import org.junit.jupiter.params.ParameterizedTest; import org.junit.jupiter.params.provider.EnumSource; import org.junit.jupiter.params.provider.ValueSource; +import org.neo4j.driver.AccessMode; import org.neo4j.driver.internal.BoltServerAddress; import org.neo4j.driver.internal.messaging.BoltProtocol; import org.neo4j.driver.internal.messaging.Message; import org.neo4j.driver.internal.spi.Connection; import org.neo4j.driver.internal.spi.ResponseHandler; import org.neo4j.driver.internal.util.ServerVersion; -import org.neo4j.driver.AccessMode; import org.neo4j.driver.net.ServerAddress; import static org.junit.jupiter.api.Assertions.assertEquals; diff --git a/driver/src/test/java/org/neo4j/driver/internal/async/DirectConnectionTest.java b/driver/src/test/java/org/neo4j/driver/internal/async/connection/DirectConnectionTest.java similarity index 98% rename from driver/src/test/java/org/neo4j/driver/internal/async/DirectConnectionTest.java rename to driver/src/test/java/org/neo4j/driver/internal/async/connection/DirectConnectionTest.java index 16df5f9c0d..08a65be48a 100644 --- a/driver/src/test/java/org/neo4j/driver/internal/async/DirectConnectionTest.java +++ b/driver/src/test/java/org/neo4j/driver/internal/async/connection/DirectConnectionTest.java @@ -16,7 +16,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package org.neo4j.driver.internal.async; +package org.neo4j.driver.internal.async.connection; import io.netty.channel.Channel; import io.netty.channel.DefaultEventLoop; @@ -56,8 +56,8 @@ import static org.mockito.Mockito.spy; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; -import static org.neo4j.driver.internal.async.ChannelAttributes.messageDispatcher; -import static org.neo4j.driver.internal.async.ChannelAttributes.terminationReason; +import static org.neo4j.driver.internal.async.connection.ChannelAttributes.messageDispatcher; +import static org.neo4j.driver.internal.async.connection.ChannelAttributes.terminationReason; import static org.neo4j.driver.internal.logging.DevNullLogging.DEV_NULL_LOGGING; import static org.neo4j.driver.internal.messaging.request.PullAllMessage.PULL_ALL; import static org.neo4j.driver.internal.messaging.request.ResetMessage.RESET; diff --git a/driver/src/test/java/org/neo4j/driver/internal/async/EventLoopGroupFactoryTest.java b/driver/src/test/java/org/neo4j/driver/internal/async/connection/EventLoopGroupFactoryTest.java similarity index 98% rename from driver/src/test/java/org/neo4j/driver/internal/async/EventLoopGroupFactoryTest.java rename to driver/src/test/java/org/neo4j/driver/internal/async/connection/EventLoopGroupFactoryTest.java index 044571e6a5..bd48766610 100644 --- a/driver/src/test/java/org/neo4j/driver/internal/async/EventLoopGroupFactoryTest.java +++ b/driver/src/test/java/org/neo4j/driver/internal/async/connection/EventLoopGroupFactoryTest.java @@ -16,7 +16,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package org.neo4j.driver.internal.async; +package org.neo4j.driver.internal.async.connection; import io.netty.channel.EventLoopGroup; import io.netty.channel.nio.NioEventLoopGroup; diff --git a/driver/src/test/java/org/neo4j/driver/internal/async/HandshakeCompletedListenerTest.java b/driver/src/test/java/org/neo4j/driver/internal/async/connection/HandshakeCompletedListenerTest.java similarity index 95% rename from driver/src/test/java/org/neo4j/driver/internal/async/HandshakeCompletedListenerTest.java rename to driver/src/test/java/org/neo4j/driver/internal/async/connection/HandshakeCompletedListenerTest.java index fce29ba32e..0db2e11191 100644 --- a/driver/src/test/java/org/neo4j/driver/internal/async/HandshakeCompletedListenerTest.java +++ b/driver/src/test/java/org/neo4j/driver/internal/async/connection/HandshakeCompletedListenerTest.java @@ -16,7 +16,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package org.neo4j.driver.internal.async; +package org.neo4j.driver.internal.async.connection; import io.netty.channel.ChannelPromise; import io.netty.channel.embedded.EmbeddedChannel; @@ -27,6 +27,7 @@ import java.util.HashMap; import java.util.Map; +import org.neo4j.driver.Value; import org.neo4j.driver.internal.async.inbound.InboundMessageDispatcher; import org.neo4j.driver.internal.handlers.HelloResponseHandler; import org.neo4j.driver.internal.handlers.InitResponseHandler; @@ -37,7 +38,6 @@ import org.neo4j.driver.internal.messaging.v2.BoltProtocolV2; import org.neo4j.driver.internal.messaging.v3.BoltProtocolV3; import org.neo4j.driver.internal.spi.ResponseHandler; -import org.neo4j.driver.Value; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertThrows; @@ -45,9 +45,9 @@ import static org.mockito.ArgumentMatchers.any; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.verify; -import static org.neo4j.driver.internal.async.ChannelAttributes.setMessageDispatcher; -import static org.neo4j.driver.internal.async.ChannelAttributes.setProtocolVersion; import static org.neo4j.driver.Values.value; +import static org.neo4j.driver.internal.async.connection.ChannelAttributes.setMessageDispatcher; +import static org.neo4j.driver.internal.async.connection.ChannelAttributes.setProtocolVersion; import static org.neo4j.driver.util.TestUtil.await; class HandshakeCompletedListenerTest diff --git a/driver/src/test/java/org/neo4j/driver/internal/async/HandshakeHandlerTest.java b/driver/src/test/java/org/neo4j/driver/internal/async/connection/HandshakeHandlerTest.java similarity index 97% rename from driver/src/test/java/org/neo4j/driver/internal/async/HandshakeHandlerTest.java rename to driver/src/test/java/org/neo4j/driver/internal/async/connection/HandshakeHandlerTest.java index 39df7ffd54..26ae04c27c 100644 --- a/driver/src/test/java/org/neo4j/driver/internal/async/HandshakeHandlerTest.java +++ b/driver/src/test/java/org/neo4j/driver/internal/async/connection/HandshakeHandlerTest.java @@ -16,7 +16,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package org.neo4j.driver.internal.async; +package org.neo4j.driver.internal.async.connection; import io.netty.channel.ChannelPipeline; import io.netty.channel.ChannelPromise; @@ -29,6 +29,10 @@ import java.io.IOException; import javax.net.ssl.SSLHandshakeException; +import org.neo4j.driver.Logging; +import org.neo4j.driver.exceptions.ClientException; +import org.neo4j.driver.exceptions.SecurityException; +import org.neo4j.driver.exceptions.ServiceUnavailableException; import org.neo4j.driver.internal.async.inbound.ChunkDecoder; import org.neo4j.driver.internal.async.inbound.InboundMessageDispatcher; import org.neo4j.driver.internal.async.inbound.InboundMessageHandler; @@ -40,10 +44,6 @@ import org.neo4j.driver.internal.messaging.v2.BoltProtocolV2; import org.neo4j.driver.internal.messaging.v2.MessageFormatV2; import org.neo4j.driver.internal.util.ErrorUtil; -import org.neo4j.driver.Logging; -import org.neo4j.driver.exceptions.ClientException; -import org.neo4j.driver.exceptions.SecurityException; -import org.neo4j.driver.exceptions.ServiceUnavailableException; import static io.netty.buffer.Unpooled.copyInt; import static org.hamcrest.Matchers.instanceOf; @@ -53,9 +53,9 @@ import static org.junit.jupiter.api.Assertions.assertNotNull; import static org.junit.jupiter.api.Assertions.assertNull; import static org.junit.jupiter.api.Assertions.assertThrows; -import static org.neo4j.driver.internal.async.BoltProtocolUtil.HTTP; -import static org.neo4j.driver.internal.async.BoltProtocolUtil.NO_PROTOCOL_VERSION; -import static org.neo4j.driver.internal.async.ChannelAttributes.setMessageDispatcher; +import static org.neo4j.driver.internal.async.connection.BoltProtocolUtil.HTTP; +import static org.neo4j.driver.internal.async.connection.BoltProtocolUtil.NO_PROTOCOL_VERSION; +import static org.neo4j.driver.internal.async.connection.ChannelAttributes.setMessageDispatcher; import static org.neo4j.driver.internal.logging.DevNullLogging.DEV_NULL_LOGGING; import static org.neo4j.driver.util.TestUtil.await; diff --git a/driver/src/test/java/org/neo4j/driver/internal/async/NettyChannelInitializerTest.java b/driver/src/test/java/org/neo4j/driver/internal/async/connection/NettyChannelInitializerTest.java similarity index 95% rename from driver/src/test/java/org/neo4j/driver/internal/async/NettyChannelInitializerTest.java rename to driver/src/test/java/org/neo4j/driver/internal/async/connection/NettyChannelInitializerTest.java index 70f95073a0..9b2923502a 100644 --- a/driver/src/test/java/org/neo4j/driver/internal/async/NettyChannelInitializerTest.java +++ b/driver/src/test/java/org/neo4j/driver/internal/async/connection/NettyChannelInitializerTest.java @@ -16,7 +16,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package org.neo4j.driver.internal.async; +package org.neo4j.driver.internal.async.connection; import io.netty.channel.embedded.EmbeddedChannel; import io.netty.handler.ssl.SslHandler; @@ -45,9 +45,9 @@ import static org.mockito.Mockito.mock; import static org.mockito.Mockito.when; import static org.neo4j.driver.internal.BoltServerAddress.LOCAL_DEFAULT; -import static org.neo4j.driver.internal.async.ChannelAttributes.creationTimestamp; -import static org.neo4j.driver.internal.async.ChannelAttributes.messageDispatcher; -import static org.neo4j.driver.internal.async.ChannelAttributes.serverAddress; +import static org.neo4j.driver.internal.async.connection.ChannelAttributes.creationTimestamp; +import static org.neo4j.driver.internal.async.connection.ChannelAttributes.messageDispatcher; +import static org.neo4j.driver.internal.async.connection.ChannelAttributes.serverAddress; import static org.neo4j.driver.internal.logging.DevNullLogging.DEV_NULL_LOGGING; class NettyChannelInitializerTest diff --git a/driver/src/test/java/org/neo4j/driver/internal/async/RoutingConnectionTest.java b/driver/src/test/java/org/neo4j/driver/internal/async/connection/RoutingConnectionTest.java similarity index 98% rename from driver/src/test/java/org/neo4j/driver/internal/async/RoutingConnectionTest.java rename to driver/src/test/java/org/neo4j/driver/internal/async/connection/RoutingConnectionTest.java index 6b7e4ea8d2..1f728c5673 100644 --- a/driver/src/test/java/org/neo4j/driver/internal/async/RoutingConnectionTest.java +++ b/driver/src/test/java/org/neo4j/driver/internal/async/connection/RoutingConnectionTest.java @@ -16,7 +16,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package org.neo4j.driver.internal.async; +package org.neo4j.driver.internal.async.connection; import org.junit.jupiter.api.Test; import org.mockito.ArgumentCaptor; @@ -31,9 +31,9 @@ import static org.mockito.Mockito.eq; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.verify; +import static org.neo4j.driver.AccessMode.READ; import static org.neo4j.driver.internal.messaging.request.DiscardAllMessage.DISCARD_ALL; import static org.neo4j.driver.internal.messaging.request.PullAllMessage.PULL_ALL; -import static org.neo4j.driver.AccessMode.READ; class RoutingConnectionTest { diff --git a/driver/src/test/java/org/neo4j/driver/internal/async/inbound/InboundMessageHandlerTest.java b/driver/src/test/java/org/neo4j/driver/internal/async/inbound/InboundMessageHandlerTest.java index fecc7ad89f..faa9609c4a 100644 --- a/driver/src/test/java/org/neo4j/driver/internal/async/inbound/InboundMessageHandlerTest.java +++ b/driver/src/test/java/org/neo4j/driver/internal/async/inbound/InboundMessageHandlerTest.java @@ -29,7 +29,7 @@ import java.util.HashMap; import java.util.Map; -import org.neo4j.driver.internal.async.ChannelAttributes; +import org.neo4j.driver.internal.async.connection.ChannelAttributes; import org.neo4j.driver.internal.util.messaging.KnowledgeableMessageFormat; import org.neo4j.driver.internal.messaging.MessageFormat; import org.neo4j.driver.internal.messaging.MessageFormat.Reader; diff --git a/driver/src/test/java/org/neo4j/driver/internal/async/outbound/OutboundMessageHandlerTest.java b/driver/src/test/java/org/neo4j/driver/internal/async/outbound/OutboundMessageHandlerTest.java index 5fa8190fcf..85541e1068 100644 --- a/driver/src/test/java/org/neo4j/driver/internal/async/outbound/OutboundMessageHandlerTest.java +++ b/driver/src/test/java/org/neo4j/driver/internal/async/outbound/OutboundMessageHandlerTest.java @@ -29,7 +29,7 @@ import java.util.HashMap; import java.util.Map; -import org.neo4j.driver.internal.async.ChannelAttributes; +import org.neo4j.driver.internal.async.connection.ChannelAttributes; import org.neo4j.driver.internal.async.inbound.ChannelErrorHandler; import org.neo4j.driver.internal.async.inbound.InboundMessageDispatcher; import org.neo4j.driver.internal.messaging.Message; diff --git a/driver/src/test/java/org/neo4j/driver/internal/async/pool/ConnectionPoolImplIT.java b/driver/src/test/java/org/neo4j/driver/internal/async/pool/ConnectionPoolImplIT.java index b200c67944..cdcdd433b2 100644 --- a/driver/src/test/java/org/neo4j/driver/internal/async/pool/ConnectionPoolImplIT.java +++ b/driver/src/test/java/org/neo4j/driver/internal/async/pool/ConnectionPoolImplIT.java @@ -33,9 +33,9 @@ import org.neo4j.driver.internal.BoltServerAddress; import org.neo4j.driver.internal.ConnectionSettings; -import org.neo4j.driver.internal.async.BootstrapFactory; -import org.neo4j.driver.internal.async.ChannelConnector; -import org.neo4j.driver.internal.async.ChannelConnectorImpl; +import org.neo4j.driver.internal.async.connection.BootstrapFactory; +import org.neo4j.driver.internal.async.connection.ChannelConnector; +import org.neo4j.driver.internal.async.connection.ChannelConnectorImpl; import org.neo4j.driver.internal.security.SecurityPlan; import org.neo4j.driver.internal.spi.Connection; import org.neo4j.driver.internal.util.FakeClock; diff --git a/driver/src/test/java/org/neo4j/driver/internal/async/pool/NettyChannelHealthCheckerTest.java b/driver/src/test/java/org/neo4j/driver/internal/async/pool/NettyChannelHealthCheckerTest.java index fca3592202..397abfc8cb 100644 --- a/driver/src/test/java/org/neo4j/driver/internal/async/pool/NettyChannelHealthCheckerTest.java +++ b/driver/src/test/java/org/neo4j/driver/internal/async/pool/NettyChannelHealthCheckerTest.java @@ -35,9 +35,9 @@ import static org.hamcrest.junit.MatcherAssert.assertThat; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertFalse; -import static org.neo4j.driver.internal.async.ChannelAttributes.setCreationTimestamp; -import static org.neo4j.driver.internal.async.ChannelAttributes.setLastUsedTimestamp; -import static org.neo4j.driver.internal.async.ChannelAttributes.setMessageDispatcher; +import static org.neo4j.driver.internal.async.connection.ChannelAttributes.setCreationTimestamp; +import static org.neo4j.driver.internal.async.connection.ChannelAttributes.setLastUsedTimestamp; +import static org.neo4j.driver.internal.async.connection.ChannelAttributes.setMessageDispatcher; import static org.neo4j.driver.internal.async.pool.PoolSettings.DEFAULT_CONNECTION_ACQUISITION_TIMEOUT; import static org.neo4j.driver.internal.async.pool.PoolSettings.DEFAULT_IDLE_TIME_BEFORE_CONNECTION_TEST; import static org.neo4j.driver.internal.async.pool.PoolSettings.DEFAULT_MAX_CONNECTION_POOL_SIZE; diff --git a/driver/src/test/java/org/neo4j/driver/internal/async/pool/NettyChannelPoolIT.java b/driver/src/test/java/org/neo4j/driver/internal/async/pool/NettyChannelPoolIT.java index 7eeddc2899..af97f883d0 100644 --- a/driver/src/test/java/org/neo4j/driver/internal/async/pool/NettyChannelPoolIT.java +++ b/driver/src/test/java/org/neo4j/driver/internal/async/pool/NettyChannelPoolIT.java @@ -34,8 +34,8 @@ import java.util.concurrent.TimeoutException; import org.neo4j.driver.internal.ConnectionSettings; -import org.neo4j.driver.internal.async.BootstrapFactory; -import org.neo4j.driver.internal.async.ChannelConnectorImpl; +import org.neo4j.driver.internal.async.connection.BootstrapFactory; +import org.neo4j.driver.internal.async.connection.ChannelConnectorImpl; import org.neo4j.driver.internal.security.InternalAuthToken; import org.neo4j.driver.internal.security.SecurityPlan; import org.neo4j.driver.internal.util.FakeClock; diff --git a/driver/src/test/java/org/neo4j/driver/internal/async/pool/NettyChannelTrackerTest.java b/driver/src/test/java/org/neo4j/driver/internal/async/pool/NettyChannelTrackerTest.java index a2e65f8c33..e326836a0c 100644 --- a/driver/src/test/java/org/neo4j/driver/internal/async/pool/NettyChannelTrackerTest.java +++ b/driver/src/test/java/org/neo4j/driver/internal/async/pool/NettyChannelTrackerTest.java @@ -37,9 +37,9 @@ import static org.mockito.Mockito.mock; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; -import static org.neo4j.driver.internal.async.ChannelAttributes.setMessageDispatcher; -import static org.neo4j.driver.internal.async.ChannelAttributes.setProtocolVersion; -import static org.neo4j.driver.internal.async.ChannelAttributes.setServerAddress; +import static org.neo4j.driver.internal.async.connection.ChannelAttributes.setMessageDispatcher; +import static org.neo4j.driver.internal.async.connection.ChannelAttributes.setProtocolVersion; +import static org.neo4j.driver.internal.async.connection.ChannelAttributes.setServerAddress; import static org.neo4j.driver.internal.logging.DevNullLogging.DEV_NULL_LOGGING; import static org.neo4j.driver.internal.metrics.InternalAbstractMetrics.DEV_NULL_METRICS; diff --git a/driver/src/test/java/org/neo4j/driver/internal/cluster/loadbalancing/LoadBalancerTest.java b/driver/src/test/java/org/neo4j/driver/internal/cluster/loadbalancing/LoadBalancerTest.java index 7a78f6e91d..38ca2bbc6c 100644 --- a/driver/src/test/java/org/neo4j/driver/internal/cluster/loadbalancing/LoadBalancerTest.java +++ b/driver/src/test/java/org/neo4j/driver/internal/cluster/loadbalancing/LoadBalancerTest.java @@ -30,7 +30,7 @@ import java.util.Set; import org.neo4j.driver.internal.BoltServerAddress; -import org.neo4j.driver.internal.async.DecoratedConnection; +import org.neo4j.driver.internal.async.connection.DecoratedConnection; import org.neo4j.driver.internal.cluster.AddressSet; import org.neo4j.driver.internal.cluster.ClusterComposition; import org.neo4j.driver.internal.cluster.ClusterRoutingTable; diff --git a/driver/src/test/java/org/neo4j/driver/internal/reactive/cursor/AsyncResultCursorOnlyFactoryTest.java b/driver/src/test/java/org/neo4j/driver/internal/cursor/AsyncResultCursorOnlyFactoryTest.java similarity index 98% rename from driver/src/test/java/org/neo4j/driver/internal/reactive/cursor/AsyncResultCursorOnlyFactoryTest.java rename to driver/src/test/java/org/neo4j/driver/internal/cursor/AsyncResultCursorOnlyFactoryTest.java index fbdad3395f..7f6df1aaa4 100644 --- a/driver/src/test/java/org/neo4j/driver/internal/reactive/cursor/AsyncResultCursorOnlyFactoryTest.java +++ b/driver/src/test/java/org/neo4j/driver/internal/cursor/AsyncResultCursorOnlyFactoryTest.java @@ -16,7 +16,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package org.neo4j.driver.internal.reactive.cursor; +package org.neo4j.driver.internal.cursor; import org.junit.jupiter.api.Test; import org.junit.jupiter.params.ParameterizedTest; @@ -27,7 +27,7 @@ import java.util.concurrent.CompletionStage; import java.util.stream.Stream; -import org.neo4j.driver.internal.AsyncStatementResultCursor; +import org.neo4j.driver.internal.async.AsyncStatementResultCursor; import org.neo4j.driver.internal.handlers.AbstractPullAllResponseHandler; import org.neo4j.driver.internal.handlers.RunResponseHandler; import org.neo4j.driver.internal.messaging.Message; diff --git a/driver/src/test/java/org/neo4j/driver/internal/reactive/cursor/InternalStatementResultCursorFactoryTest.java b/driver/src/test/java/org/neo4j/driver/internal/cursor/InternalStatementResultCursorFactoryTest.java similarity index 98% rename from driver/src/test/java/org/neo4j/driver/internal/reactive/cursor/InternalStatementResultCursorFactoryTest.java rename to driver/src/test/java/org/neo4j/driver/internal/cursor/InternalStatementResultCursorFactoryTest.java index 97630de10b..0bf373d570 100644 --- a/driver/src/test/java/org/neo4j/driver/internal/reactive/cursor/InternalStatementResultCursorFactoryTest.java +++ b/driver/src/test/java/org/neo4j/driver/internal/cursor/InternalStatementResultCursorFactoryTest.java @@ -16,7 +16,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package org.neo4j.driver.internal.reactive.cursor; +package org.neo4j.driver.internal.cursor; import org.junit.jupiter.api.Test; import org.junit.jupiter.params.ParameterizedTest; @@ -27,7 +27,7 @@ import java.util.concurrent.CompletionStage; import java.util.stream.Stream; -import org.neo4j.driver.internal.AsyncStatementResultCursor; +import org.neo4j.driver.internal.async.AsyncStatementResultCursor; import org.neo4j.driver.internal.handlers.PullAllResponseHandler; import org.neo4j.driver.internal.handlers.RunResponseHandler; import org.neo4j.driver.internal.handlers.pulln.BasicPullResponseHandler; diff --git a/driver/src/test/java/org/neo4j/driver/internal/reactive/cursor/RxStatementResultCursorTest.java b/driver/src/test/java/org/neo4j/driver/internal/cursor/RxStatementResultCursorTest.java similarity index 99% rename from driver/src/test/java/org/neo4j/driver/internal/reactive/cursor/RxStatementResultCursorTest.java rename to driver/src/test/java/org/neo4j/driver/internal/cursor/RxStatementResultCursorTest.java index 9372d9f978..a302781a51 100644 --- a/driver/src/test/java/org/neo4j/driver/internal/reactive/cursor/RxStatementResultCursorTest.java +++ b/driver/src/test/java/org/neo4j/driver/internal/cursor/RxStatementResultCursorTest.java @@ -16,7 +16,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package org.neo4j.driver.internal.reactive.cursor; +package org.neo4j.driver.internal.cursor; import org.junit.jupiter.api.Test; diff --git a/driver/src/test/java/org/neo4j/driver/internal/handlers/ChannelReleasingResetResponseHandlerTest.java b/driver/src/test/java/org/neo4j/driver/internal/handlers/ChannelReleasingResetResponseHandlerTest.java index 9529529871..601df886cb 100644 --- a/driver/src/test/java/org/neo4j/driver/internal/handlers/ChannelReleasingResetResponseHandlerTest.java +++ b/driver/src/test/java/org/neo4j/driver/internal/handlers/ChannelReleasingResetResponseHandlerTest.java @@ -40,7 +40,7 @@ import static org.mockito.Mockito.mock; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; -import static org.neo4j.driver.internal.async.ChannelAttributes.lastUsedTimestamp; +import static org.neo4j.driver.internal.async.connection.ChannelAttributes.lastUsedTimestamp; class ChannelReleasingResetResponseHandlerTest { diff --git a/driver/src/test/java/org/neo4j/driver/internal/handlers/HelloResponseHandlerTest.java b/driver/src/test/java/org/neo4j/driver/internal/handlers/HelloResponseHandlerTest.java index 09f3eae845..74b13915d7 100644 --- a/driver/src/test/java/org/neo4j/driver/internal/handlers/HelloResponseHandlerTest.java +++ b/driver/src/test/java/org/neo4j/driver/internal/handlers/HelloResponseHandlerTest.java @@ -43,9 +43,9 @@ import static org.junit.jupiter.api.Assertions.assertFalse; import static org.junit.jupiter.api.Assertions.assertThrows; import static org.junit.jupiter.api.Assertions.assertTrue; -import static org.neo4j.driver.internal.async.ChannelAttributes.connectionId; -import static org.neo4j.driver.internal.async.ChannelAttributes.serverVersion; -import static org.neo4j.driver.internal.async.ChannelAttributes.setMessageDispatcher; +import static org.neo4j.driver.internal.async.connection.ChannelAttributes.connectionId; +import static org.neo4j.driver.internal.async.connection.ChannelAttributes.serverVersion; +import static org.neo4j.driver.internal.async.connection.ChannelAttributes.setMessageDispatcher; import static org.neo4j.driver.internal.async.outbound.OutboundMessageHandler.NAME; import static org.neo4j.driver.internal.logging.DevNullLogging.DEV_NULL_LOGGING; import static org.neo4j.driver.Values.value; diff --git a/driver/src/test/java/org/neo4j/driver/internal/handlers/InitResponseHandlerTest.java b/driver/src/test/java/org/neo4j/driver/internal/handlers/InitResponseHandlerTest.java index 7581348757..1a5665ed87 100644 --- a/driver/src/test/java/org/neo4j/driver/internal/handlers/InitResponseHandlerTest.java +++ b/driver/src/test/java/org/neo4j/driver/internal/handlers/InitResponseHandlerTest.java @@ -46,8 +46,8 @@ import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertThrows; import static org.junit.jupiter.api.Assertions.assertTrue; -import static org.neo4j.driver.internal.async.ChannelAttributes.serverVersion; -import static org.neo4j.driver.internal.async.ChannelAttributes.setMessageDispatcher; +import static org.neo4j.driver.internal.async.connection.ChannelAttributes.serverVersion; +import static org.neo4j.driver.internal.async.connection.ChannelAttributes.setMessageDispatcher; import static org.neo4j.driver.internal.async.outbound.OutboundMessageHandler.NAME; import static org.neo4j.driver.internal.logging.DevNullLogging.DEV_NULL_LOGGING; import static org.neo4j.driver.Values.value; diff --git a/driver/src/test/java/org/neo4j/driver/internal/handlers/TransactionPullAllResponseHandlerTest.java b/driver/src/test/java/org/neo4j/driver/internal/handlers/TransactionPullAllResponseHandlerTest.java index fc5b7ea992..898c12d0bc 100644 --- a/driver/src/test/java/org/neo4j/driver/internal/handlers/TransactionPullAllResponseHandlerTest.java +++ b/driver/src/test/java/org/neo4j/driver/internal/handlers/TransactionPullAllResponseHandlerTest.java @@ -23,15 +23,15 @@ import java.io.IOException; import java.util.concurrent.CompletableFuture; -import org.neo4j.driver.internal.BoltServerAddress; -import org.neo4j.driver.internal.ExplicitTransaction; -import org.neo4j.driver.internal.spi.Connection; -import org.neo4j.driver.internal.util.ServerVersion; import org.neo4j.driver.Statement; import org.neo4j.driver.exceptions.ClientException; import org.neo4j.driver.exceptions.ServiceUnavailableException; import org.neo4j.driver.exceptions.SessionExpiredException; import org.neo4j.driver.exceptions.TransientException; +import org.neo4j.driver.internal.BoltServerAddress; +import org.neo4j.driver.internal.async.ExplicitTransaction; +import org.neo4j.driver.internal.spi.Connection; +import org.neo4j.driver.internal.util.ServerVersion; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.verify; diff --git a/driver/src/test/java/org/neo4j/driver/internal/handlers/pulln/TransactionPullResponseHandlerTest.java b/driver/src/test/java/org/neo4j/driver/internal/handlers/pulln/TransactionPullResponseHandlerTest.java index d6407a598f..a0b5c0e324 100644 --- a/driver/src/test/java/org/neo4j/driver/internal/handlers/pulln/TransactionPullResponseHandlerTest.java +++ b/driver/src/test/java/org/neo4j/driver/internal/handlers/pulln/TransactionPullResponseHandlerTest.java @@ -21,12 +21,12 @@ import java.util.Collections; import java.util.function.BiConsumer; -import org.neo4j.driver.internal.ExplicitTransaction; +import org.neo4j.driver.Record; +import org.neo4j.driver.Statement; +import org.neo4j.driver.internal.async.ExplicitTransaction; import org.neo4j.driver.internal.handlers.RunResponseHandler; import org.neo4j.driver.internal.messaging.v4.BoltProtocolV4; import org.neo4j.driver.internal.spi.Connection; -import org.neo4j.driver.Record; -import org.neo4j.driver.Statement; import org.neo4j.driver.summary.ResultSummary; import static org.hamcrest.CoreMatchers.equalTo; diff --git a/driver/src/test/java/org/neo4j/driver/internal/logging/ChannelActivityLoggerTest.java b/driver/src/test/java/org/neo4j/driver/internal/logging/ChannelActivityLoggerTest.java index c561d4e40b..47587d4adc 100644 --- a/driver/src/test/java/org/neo4j/driver/internal/logging/ChannelActivityLoggerTest.java +++ b/driver/src/test/java/org/neo4j/driver/internal/logging/ChannelActivityLoggerTest.java @@ -22,7 +22,7 @@ import org.junit.jupiter.api.Test; import org.neo4j.driver.internal.BoltServerAddress; -import org.neo4j.driver.internal.async.ChannelAttributes; +import org.neo4j.driver.internal.async.connection.ChannelAttributes; import org.neo4j.driver.Logging; import static org.junit.jupiter.api.Assertions.assertEquals; diff --git a/driver/src/test/java/org/neo4j/driver/internal/messaging/BoltProtocolTest.java b/driver/src/test/java/org/neo4j/driver/internal/messaging/BoltProtocolTest.java index 6606746337..cde78822a6 100644 --- a/driver/src/test/java/org/neo4j/driver/internal/messaging/BoltProtocolTest.java +++ b/driver/src/test/java/org/neo4j/driver/internal/messaging/BoltProtocolTest.java @@ -30,7 +30,7 @@ import static org.hamcrest.Matchers.instanceOf; import static org.junit.jupiter.api.Assertions.assertAll; import static org.junit.jupiter.api.Assertions.assertThrows; -import static org.neo4j.driver.internal.async.ChannelAttributes.setProtocolVersion; +import static org.neo4j.driver.internal.async.connection.ChannelAttributes.setProtocolVersion; class BoltProtocolTest { diff --git a/driver/src/test/java/org/neo4j/driver/internal/messaging/MessageFormatTest.java b/driver/src/test/java/org/neo4j/driver/internal/messaging/MessageFormatTest.java index 67e64c3005..e12bb2001e 100644 --- a/driver/src/test/java/org/neo4j/driver/internal/messaging/MessageFormatTest.java +++ b/driver/src/test/java/org/neo4j/driver/internal/messaging/MessageFormatTest.java @@ -29,8 +29,8 @@ import java.util.List; import java.util.Map; -import org.neo4j.driver.internal.async.BoltProtocolUtil; -import org.neo4j.driver.internal.async.ChannelPipelineBuilderImpl; +import org.neo4j.driver.internal.async.connection.BoltProtocolUtil; +import org.neo4j.driver.internal.async.connection.ChannelPipelineBuilderImpl; import org.neo4j.driver.internal.async.inbound.InboundMessageDispatcher; import org.neo4j.driver.internal.async.outbound.ChunkAwareByteBufOutput; import org.neo4j.driver.internal.messaging.request.InitMessage; @@ -54,8 +54,8 @@ import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertThrows; import static org.junit.jupiter.api.Assertions.assertTrue; -import static org.neo4j.driver.internal.async.ChannelAttributes.messageDispatcher; -import static org.neo4j.driver.internal.async.ChannelAttributes.setMessageDispatcher; +import static org.neo4j.driver.internal.async.connection.ChannelAttributes.messageDispatcher; +import static org.neo4j.driver.internal.async.connection.ChannelAttributes.setMessageDispatcher; import static org.neo4j.driver.internal.logging.DevNullLogging.DEV_NULL_LOGGING; import static org.neo4j.driver.internal.util.ValueFactory.emptyNodeValue; import static org.neo4j.driver.internal.util.ValueFactory.emptyPathValue; diff --git a/driver/src/test/java/org/neo4j/driver/internal/messaging/v1/BoltProtocolV1Test.java b/driver/src/test/java/org/neo4j/driver/internal/messaging/v1/BoltProtocolV1Test.java index 50de0af24d..0ebab73ccc 100644 --- a/driver/src/test/java/org/neo4j/driver/internal/messaging/v1/BoltProtocolV1Test.java +++ b/driver/src/test/java/org/neo4j/driver/internal/messaging/v1/BoltProtocolV1Test.java @@ -31,11 +31,17 @@ import java.util.concurrent.CompletableFuture; import java.util.concurrent.CompletionStage; +import org.neo4j.driver.Logging; +import org.neo4j.driver.Statement; +import org.neo4j.driver.TransactionConfig; +import org.neo4j.driver.Value; +import org.neo4j.driver.exceptions.ClientException; import org.neo4j.driver.internal.Bookmarks; import org.neo4j.driver.internal.BookmarksHolder; -import org.neo4j.driver.internal.ExplicitTransaction; -import org.neo4j.driver.internal.async.ChannelAttributes; +import org.neo4j.driver.internal.async.ExplicitTransaction; +import org.neo4j.driver.internal.async.connection.ChannelAttributes; import org.neo4j.driver.internal.async.inbound.InboundMessageDispatcher; +import org.neo4j.driver.internal.cursor.InternalStatementResultCursor; import org.neo4j.driver.internal.handlers.BeginTxResponseHandler; import org.neo4j.driver.internal.handlers.CommitTxResponseHandler; import org.neo4j.driver.internal.handlers.NoOpResponseHandler; @@ -51,12 +57,6 @@ import org.neo4j.driver.internal.spi.Connection; import org.neo4j.driver.internal.spi.ResponseHandler; import org.neo4j.driver.internal.util.Futures; -import org.neo4j.driver.internal.reactive.cursor.InternalStatementResultCursor; -import org.neo4j.driver.Logging; -import org.neo4j.driver.Statement; -import org.neo4j.driver.TransactionConfig; -import org.neo4j.driver.Value; -import org.neo4j.driver.exceptions.ClientException; import static java.util.Collections.emptyMap; import static java.util.Collections.singletonMap; @@ -76,9 +76,9 @@ import static org.mockito.Mockito.mock; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; +import static org.neo4j.driver.Values.value; import static org.neo4j.driver.internal.messaging.request.MultiDatabaseUtil.ABSENT_DB_NAME; import static org.neo4j.driver.internal.util.Futures.blockingGet; -import static org.neo4j.driver.Values.value; import static org.neo4j.driver.util.TestUtil.await; import static org.neo4j.driver.util.TestUtil.connectionMock; diff --git a/driver/src/test/java/org/neo4j/driver/internal/messaging/v3/BoltProtocolV3Test.java b/driver/src/test/java/org/neo4j/driver/internal/messaging/v3/BoltProtocolV3Test.java index b7f452a6e4..68b21d4f22 100644 --- a/driver/src/test/java/org/neo4j/driver/internal/messaging/v3/BoltProtocolV3Test.java +++ b/driver/src/test/java/org/neo4j/driver/internal/messaging/v3/BoltProtocolV3Test.java @@ -32,13 +32,19 @@ import java.util.concurrent.CompletableFuture; import java.util.concurrent.CompletionStage; +import org.neo4j.driver.AccessMode; +import org.neo4j.driver.Logging; +import org.neo4j.driver.Statement; +import org.neo4j.driver.TransactionConfig; +import org.neo4j.driver.Value; import org.neo4j.driver.exceptions.ClientException; import org.neo4j.driver.internal.Bookmarks; import org.neo4j.driver.internal.BookmarksHolder; import org.neo4j.driver.internal.DefaultBookmarksHolder; -import org.neo4j.driver.internal.ExplicitTransaction; -import org.neo4j.driver.internal.async.ChannelAttributes; +import org.neo4j.driver.internal.async.ExplicitTransaction; +import org.neo4j.driver.internal.async.connection.ChannelAttributes; import org.neo4j.driver.internal.async.inbound.InboundMessageDispatcher; +import org.neo4j.driver.internal.cursor.InternalStatementResultCursor; import org.neo4j.driver.internal.handlers.BeginTxResponseHandler; import org.neo4j.driver.internal.handlers.CommitTxResponseHandler; import org.neo4j.driver.internal.handlers.NoOpResponseHandler; @@ -57,12 +63,6 @@ import org.neo4j.driver.internal.messaging.request.RunWithMetadataMessage; import org.neo4j.driver.internal.spi.Connection; import org.neo4j.driver.internal.spi.ResponseHandler; -import org.neo4j.driver.internal.reactive.cursor.InternalStatementResultCursor; -import org.neo4j.driver.AccessMode; -import org.neo4j.driver.Logging; -import org.neo4j.driver.Statement; -import org.neo4j.driver.TransactionConfig; -import org.neo4j.driver.Value; import static java.time.Duration.ofSeconds; import static java.util.Collections.emptyMap; @@ -83,10 +83,10 @@ import static org.mockito.Mockito.mock; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; -import static org.neo4j.driver.internal.messaging.request.MultiDatabaseUtil.ABSENT_DB_NAME; -import static org.neo4j.driver.internal.util.ServerVersion.v3_5_0; import static org.neo4j.driver.AccessMode.WRITE; import static org.neo4j.driver.Values.value; +import static org.neo4j.driver.internal.messaging.request.MultiDatabaseUtil.ABSENT_DB_NAME; +import static org.neo4j.driver.internal.util.ServerVersion.v3_5_0; import static org.neo4j.driver.util.TestUtil.await; import static org.neo4j.driver.util.TestUtil.connectionMock; diff --git a/driver/src/test/java/org/neo4j/driver/internal/messaging/v4/BoltProtocolV4Test.java b/driver/src/test/java/org/neo4j/driver/internal/messaging/v4/BoltProtocolV4Test.java index 3a791ee58a..1b95c0f5ac 100644 --- a/driver/src/test/java/org/neo4j/driver/internal/messaging/v4/BoltProtocolV4Test.java +++ b/driver/src/test/java/org/neo4j/driver/internal/messaging/v4/BoltProtocolV4Test.java @@ -28,7 +28,9 @@ import org.neo4j.driver.internal.Bookmarks; import org.neo4j.driver.internal.BookmarksHolder; import org.neo4j.driver.internal.DefaultBookmarksHolder; -import org.neo4j.driver.internal.ExplicitTransaction; +import org.neo4j.driver.internal.async.ExplicitTransaction; +import org.neo4j.driver.internal.cursor.InternalStatementResultCursor; +import org.neo4j.driver.internal.cursor.StatementResultCursorFactory; import org.neo4j.driver.internal.handlers.BeginTxResponseHandler; import org.neo4j.driver.internal.handlers.NoOpResponseHandler; import org.neo4j.driver.internal.handlers.PullAllResponseHandler; @@ -39,8 +41,6 @@ import org.neo4j.driver.internal.messaging.request.PullMessage; import org.neo4j.driver.internal.messaging.request.RunWithMetadataMessage; import org.neo4j.driver.internal.messaging.v3.BoltProtocolV3Test; -import org.neo4j.driver.internal.reactive.cursor.InternalStatementResultCursor; -import org.neo4j.driver.internal.reactive.cursor.StatementResultCursorFactory; import org.neo4j.driver.internal.spi.Connection; import org.neo4j.driver.internal.spi.ResponseHandler; diff --git a/driver/src/test/java/org/neo4j/driver/internal/reactive/InternalRxResultTest.java b/driver/src/test/java/org/neo4j/driver/internal/reactive/InternalRxResultTest.java index a4a3b0def1..73bae7427c 100644 --- a/driver/src/test/java/org/neo4j/driver/internal/reactive/InternalRxResultTest.java +++ b/driver/src/test/java/org/neo4j/driver/internal/reactive/InternalRxResultTest.java @@ -34,7 +34,7 @@ import org.neo4j.driver.internal.util.Futures; import org.neo4j.driver.reactive.RxResult; import org.neo4j.driver.internal.reactive.util.ListBasedPullHandler; -import org.neo4j.driver.internal.reactive.cursor.RxStatementResultCursor; +import org.neo4j.driver.internal.cursor.RxStatementResultCursor; import org.neo4j.driver.Record; import org.neo4j.driver.summary.ResultSummary; @@ -215,7 +215,7 @@ private InternalRxResult newRxResult( Throwable error ) { return new InternalRxResult( () -> { // now we successfully run - return failedFuture( new CompletionException(error) ); + return failedFuture( new CompletionException( error ) ); } ); } } diff --git a/driver/src/test/java/org/neo4j/driver/internal/reactive/InternalRxSessionTest.java b/driver/src/test/java/org/neo4j/driver/internal/reactive/InternalRxSessionTest.java index 7b8d97281c..e4f5cde24d 100644 --- a/driver/src/test/java/org/neo4j/driver/internal/reactive/InternalRxSessionTest.java +++ b/driver/src/test/java/org/neo4j/driver/internal/reactive/InternalRxSessionTest.java @@ -22,29 +22,31 @@ import org.junit.jupiter.params.ParameterizedTest; import org.junit.jupiter.params.provider.MethodSource; import org.reactivestreams.Publisher; +import reactor.core.publisher.Flux; import reactor.core.publisher.Mono; import reactor.test.StepVerifier; import java.util.concurrent.CompletableFuture; import java.util.concurrent.CompletionException; import java.util.concurrent.CompletionStage; +import java.util.concurrent.atomic.AtomicInteger; +import java.util.function.Function; import java.util.stream.Stream; -import org.neo4j.driver.internal.ExplicitTransaction; +import org.neo4j.driver.AccessMode; +import org.neo4j.driver.Statement; +import org.neo4j.driver.TransactionConfig; +import org.neo4j.driver.Value; import org.neo4j.driver.internal.InternalRecord; -import org.neo4j.driver.internal.NetworkSession; -import org.neo4j.driver.internal.reactive.InternalRxResult; -import org.neo4j.driver.internal.reactive.InternalRxSession; +import org.neo4j.driver.internal.async.ExplicitTransaction; +import org.neo4j.driver.internal.async.NetworkSession; +import org.neo4j.driver.internal.cursor.RxStatementResultCursor; +import org.neo4j.driver.internal.util.FixedRetryLogic; import org.neo4j.driver.internal.util.Futures; import org.neo4j.driver.internal.value.IntegerValue; import org.neo4j.driver.reactive.RxResult; import org.neo4j.driver.reactive.RxSession; import org.neo4j.driver.reactive.RxTransaction; -import org.neo4j.driver.internal.reactive.cursor.RxStatementResultCursor; -import org.neo4j.driver.Statement; -import org.neo4j.driver.TransactionConfig; -import org.neo4j.driver.Value; -import java.util.function.Function; import static java.util.Collections.singletonList; import static java.util.Collections.singletonMap; @@ -54,12 +56,13 @@ import static org.junit.jupiter.api.Assertions.assertThrows; import static org.mockito.ArgumentMatchers.any; import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.times; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.verifyNoMoreInteractions; import static org.mockito.Mockito.when; -import static org.neo4j.driver.internal.util.Futures.completedWithNull; import static org.neo4j.driver.TransactionConfig.empty; import static org.neo4j.driver.Values.parameters; +import static org.neo4j.driver.internal.util.Futures.completedWithNull; class InternalRxSessionTest { @@ -86,6 +89,16 @@ private static Stream>> allBeginTxMe ); } + private static Stream>> allRunTxMethods() + { + return Stream.of( + rxSession -> rxSession.readTransaction( tx -> Flux.just( "a" ) ), + rxSession -> rxSession.writeTransaction( tx -> Flux.just( "a" ) ), + rxSession -> rxSession.readTransaction( tx -> Flux.just( "a" ), empty() ), + rxSession -> rxSession.writeTransaction( tx -> Flux.just( "a" ), empty() ) + ); + } + @ParameterizedTest @MethodSource( "allSessionRunMethods" ) void shouldDelegateRun( Function runReturnOne ) throws Throwable @@ -118,7 +131,7 @@ void shouldReleaseConnectionIfFailedToRun( Function runRetur // Run failed with error when( session.runRx( any( Statement.class ), any( TransactionConfig.class ) ) ).thenReturn( Futures.failedFuture( error ) ); - when( session.releaseConnection() ).thenReturn( Futures.completedWithNull() ); + when( session.releaseConnectionAsync() ).thenReturn( Futures.completedWithNull() ); InternalRxSession rxSession = new InternalRxSession( session ); @@ -131,7 +144,7 @@ void shouldReleaseConnectionIfFailedToRun( Function runRetur verify( session ).runRx( any( Statement.class ), any( TransactionConfig.class ) ); RuntimeException t = assertThrows( CompletionException.class, () -> Futures.getNow( cursorFuture ) ); assertThat( t.getCause(), equalTo( error ) ); - verify( session ).releaseConnection(); + verify( session ).releaseConnectionAsync(); } @ParameterizedTest @@ -163,7 +176,7 @@ void shouldReleaseConnectionIfFailedToBeginTx( Function Futures.getNow( txFuture ) ); assertThat( t.getCause(), equalTo( error ) ); - verify( session ).releaseConnection(); + verify( session ).releaseConnectionAsync(); + } + + @ParameterizedTest + @MethodSource( "allRunTxMethods" ) + void shouldDelegateRunTx( Function> runTx ) throws Throwable + { + // Given + NetworkSession session = mock( NetworkSession.class ); + ExplicitTransaction tx = mock( ExplicitTransaction.class ); + when( tx.commitAsync() ).thenReturn( completedWithNull() ); + when( tx.rollbackAsync() ).thenReturn( completedWithNull() ); + + when( session.beginTransactionAsync( any( AccessMode.class ), any( TransactionConfig.class ) ) ).thenReturn( completedFuture( tx ) ); + when( session.retryLogic() ).thenReturn( new FixedRetryLogic( 1 ) ); + InternalRxSession rxSession = new InternalRxSession( session ); + + // When + Publisher strings = runTx.apply( rxSession ); + StepVerifier.create( Flux.from( strings ) ).expectNext( "a" ).verifyComplete(); + + // Then + verify( session ).beginTransactionAsync( any( AccessMode.class ), any( TransactionConfig.class ) ); + verify( tx ).commitAsync(); + } + + @Test + void shouldRetryOnError() throws Throwable + { + // Given + int retryCount = 2; + NetworkSession session = mock( NetworkSession.class ); + ExplicitTransaction tx = mock( ExplicitTransaction.class ); + when( tx.commitAsync() ).thenReturn( completedWithNull() ); + when( tx.rollbackAsync() ).thenReturn( completedWithNull() ); + + when( session.beginTransactionAsync( any( AccessMode.class ), any( TransactionConfig.class ) ) ).thenReturn( completedFuture( tx ) ); + when( session.retryLogic() ).thenReturn( new FixedRetryLogic( retryCount ) ); + InternalRxSession rxSession = new InternalRxSession( session ); + + // When + Publisher strings = rxSession.readTransaction( t -> + Flux.just( "a" ).then( Mono.error( new RuntimeException( "Errored" ) ) ) ); + StepVerifier.create( Flux.from( strings ) ) + // we lost the "a"s too as the user only see the last failure + .expectError( RuntimeException.class ) + .verify(); + + // Then + verify( session, times( retryCount + 1 ) ).beginTransactionAsync( any( AccessMode.class ), any( TransactionConfig.class ) ); + verify( tx, times( retryCount + 1 ) ).rollbackAsync(); + } + + @Test + void shouldObtainResultIfRetrySucceed() throws Throwable + { + // Given + int retryCount = 2; + NetworkSession session = mock( NetworkSession.class ); + ExplicitTransaction tx = mock( ExplicitTransaction.class ); + when( tx.commitAsync() ).thenReturn( completedWithNull() ); + when( tx.rollbackAsync() ).thenReturn( completedWithNull() ); + + when( session.beginTransactionAsync( any( AccessMode.class ), any( TransactionConfig.class ) ) ).thenReturn( completedFuture( tx ) ); + when( session.retryLogic() ).thenReturn( new FixedRetryLogic( retryCount ) ); + InternalRxSession rxSession = new InternalRxSession( session ); + + // When + AtomicInteger count = new AtomicInteger(); + Publisher strings = rxSession.readTransaction( t -> { + // we fail for the first few retries, and then success on the last run. + if ( count.getAndIncrement() == retryCount ) + { + return Flux.just( "a" ); + } + else + { + return Flux.just( "a" ).then( Mono.error( new RuntimeException( "Errored" ) ) ); + } + } ); + StepVerifier.create( Flux.from( strings ) ).expectNext( "a" ).verifyComplete(); + + // Then + verify( session, times( retryCount + 1 ) ).beginTransactionAsync( any( AccessMode.class ), any( TransactionConfig.class ) ); + verify( tx, times( retryCount ) ).rollbackAsync(); + verify( tx ).commitAsync(); } @Test diff --git a/driver/src/test/java/org/neo4j/driver/internal/reactive/InternalRxTransactionTest.java b/driver/src/test/java/org/neo4j/driver/internal/reactive/InternalRxTransactionTest.java index 848ca16aa6..cd4d5a736f 100644 --- a/driver/src/test/java/org/neo4j/driver/internal/reactive/InternalRxTransactionTest.java +++ b/driver/src/test/java/org/neo4j/driver/internal/reactive/InternalRxTransactionTest.java @@ -26,20 +26,18 @@ import java.util.concurrent.CompletionException; import java.util.concurrent.CompletionStage; +import java.util.function.Function; import java.util.stream.Stream; -import org.neo4j.driver.internal.ExplicitTransaction; +import org.neo4j.driver.Statement; +import org.neo4j.driver.Value; import org.neo4j.driver.internal.InternalRecord; -import org.neo4j.driver.internal.reactive.InternalRxResult; -import org.neo4j.driver.internal.reactive.InternalRxTransaction; +import org.neo4j.driver.internal.async.ExplicitTransaction; +import org.neo4j.driver.internal.cursor.RxStatementResultCursor; import org.neo4j.driver.internal.util.Futures; import org.neo4j.driver.internal.value.IntegerValue; import org.neo4j.driver.reactive.RxResult; import org.neo4j.driver.reactive.RxTransaction; -import org.neo4j.driver.internal.reactive.cursor.RxStatementResultCursor; -import org.neo4j.driver.Statement; -import org.neo4j.driver.Value; -import java.util.function.Function; import static java.util.Collections.singletonList; import static java.util.Collections.singletonMap; diff --git a/driver/src/test/java/org/neo4j/driver/internal/retry/ExponentialBackoffRetryLogicTest.java b/driver/src/test/java/org/neo4j/driver/internal/retry/ExponentialBackoffRetryLogicTest.java index 51727cfe8a..236d951c96 100644 --- a/driver/src/test/java/org/neo4j/driver/internal/retry/ExponentialBackoffRetryLogicTest.java +++ b/driver/src/test/java/org/neo4j/driver/internal/retry/ExponentialBackoffRetryLogicTest.java @@ -19,26 +19,44 @@ package org.neo4j.driver.internal.retry; import org.junit.jupiter.api.Test; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.MethodSource; import org.mockito.ArgumentCaptor; +import org.reactivestreams.Publisher; +import reactor.core.publisher.Flux; +import reactor.core.publisher.Mono; +import reactor.core.publisher.MonoSink; +import reactor.test.StepVerifier; import java.util.ArrayList; +import java.util.Arrays; +import java.util.Iterator; import java.util.List; import java.util.concurrent.CompletionStage; +import java.util.concurrent.atomic.AtomicBoolean; +import java.util.concurrent.atomic.AtomicInteger; +import java.util.function.Consumer; import java.util.function.Supplier; +import java.util.stream.Stream; -import org.neo4j.driver.internal.util.Clock; -import org.neo4j.driver.internal.util.ImmediateSchedulingEventExecutor; import org.neo4j.driver.Logger; import org.neo4j.driver.Logging; import org.neo4j.driver.exceptions.ServiceUnavailableException; import org.neo4j.driver.exceptions.SessionExpiredException; import org.neo4j.driver.exceptions.TransientException; +import org.neo4j.driver.internal.util.Clock; +import org.neo4j.driver.internal.util.ImmediateSchedulingEventExecutor; import static java.lang.Long.MAX_VALUE; import static java.util.concurrent.CompletableFuture.completedFuture; +import static org.hamcrest.Matchers.allOf; import static org.hamcrest.Matchers.closeTo; +import static org.hamcrest.Matchers.contains; import static org.hamcrest.Matchers.containsString; +import static org.hamcrest.Matchers.equalTo; +import static org.hamcrest.Matchers.greaterThanOrEqualTo; import static org.hamcrest.Matchers.instanceOf; +import static org.hamcrest.Matchers.lessThanOrEqualTo; import static org.hamcrest.junit.MatcherAssert.assertThat; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertNull; @@ -133,6 +151,24 @@ void nextDelayCalculatedAccordingToMultiplierAsync() assertEquals( delaysWithoutJitter( initialDelay, multiplier, retries ), eventExecutor.scheduleDelays() ); } + @Test + void nextDelayCalculatedAccordingToMultiplierRx() + { + String result = "The Result"; + int retries = 14; + int initialDelay = 1; + int multiplier = 2; + int noJitter = 0; + + ExponentialBackoffRetryLogic retryLogic = newRetryLogic( MAX_VALUE, initialDelay, multiplier, noJitter, + Clock.SYSTEM ); + + Mono single = Flux.from( retryRx( retryLogic, retries, result ) ).single(); + + assertEquals( result, await( single ) ); + assertEquals( delaysWithoutJitter( initialDelay, multiplier, retries ), eventExecutor.scheduleDelays() ); + } + @Test void nextDelayCalculatedAccordingToJitter() throws Exception { @@ -173,6 +209,27 @@ void nextDelayCalculatedAccordingToJitterAsync() assertDelaysApproximatelyEqual( delaysWithoutJitter, scheduleDelays, jitterFactor ); } + @Test + void nextDelayCalculatedAccordingToJitterRx() + { + String result = "The Result"; + int retries = 24; + double jitterFactor = 0.2; + int initialDelay = 1; + int multiplier = 2; + + ExponentialBackoffRetryLogic retryLogic = newRetryLogic( MAX_VALUE, initialDelay, multiplier, jitterFactor, + mock( Clock.class ) ); + + Mono single = Flux.from( retryRx( retryLogic, retries, result ) ).single(); + assertEquals( result, await( single ) ); + + List scheduleDelays = eventExecutor.scheduleDelays(); + List delaysWithoutJitter = delaysWithoutJitter( initialDelay, multiplier, retries ); + + assertDelaysApproximatelyEqual( delaysWithoutJitter, scheduleDelays, jitterFactor ); + } + @Test void doesNotRetryWhenMaxRetryTimeExceeded() throws Exception { @@ -230,6 +287,35 @@ void doesNotRetryWhenMaxRetryTimeExceededAsync() verify( workMock, times( 3 ) ).get(); } + @Test + void doesNotRetryWhenMaxRetryTimeExceededRx() + { + long retryStart = Clock.SYSTEM.millis(); + int initialDelay = 100; + int multiplier = 2; + long maxRetryTimeMs = 45; + Clock clock = mock( Clock.class ); + when( clock.millis() ).thenReturn( retryStart ) + .thenReturn( retryStart + maxRetryTimeMs - 5 ) + .thenReturn( retryStart + maxRetryTimeMs + 7 ); + + ExponentialBackoffRetryLogic retryLogic = newRetryLogic( maxRetryTimeMs, initialDelay, multiplier, 0, clock ); + + SessionExpiredException error = sessionExpired(); + AtomicInteger executionCount = new AtomicInteger(); + Publisher publisher = retryLogic.retryRx( Mono.error( error ).doOnTerminate( executionCount::getAndIncrement ) ); + + Exception e = assertThrows( Exception.class, () -> await( publisher ) ); + assertEquals( error, e ); + + List scheduleDelays = eventExecutor.scheduleDelays(); + assertEquals( 2, scheduleDelays.size() ); + assertEquals( initialDelay, scheduleDelays.get( 0 ).intValue() ); + assertEquals( initialDelay * multiplier, scheduleDelays.get( 1 ).intValue() ); + + assertThat( executionCount.get(), equalTo( 3 ) ); + } + @Test void sleepsOnServiceUnavailableException() throws Exception { @@ -255,7 +341,7 @@ void schedulesRetryOnServiceUnavailableExceptionAsync() ExponentialBackoffRetryLogic retryLogic = newRetryLogic( 1, 42, 1, 0, clock ); Supplier> workMock = newWorkMock(); - SessionExpiredException error = sessionExpired(); + ServiceUnavailableException error = serviceUnavailable(); when( workMock.get() ).thenReturn( failedFuture( error ) ).thenReturn( completedFuture( result ) ); assertEquals( result, await( retryLogic.retryAsync( workMock ) ) ); @@ -443,6 +529,40 @@ void doesNotRetryOnTransactionLockClientStoppedErrorAsync() assertEquals( 0, eventExecutor.scheduleDelays().size() ); } + @ParameterizedTest + @MethodSource( "canBeRetriedErrors" ) + void schedulesRetryOnErrorRx( Exception error ) + { + String result = "The Result"; + Clock clock = mock( Clock.class ); + + ExponentialBackoffRetryLogic retryLogic = newRetryLogic( 1, 4242, 1, 0, clock ); + + Publisher publisher = createMono( result, error ); + Mono single = Flux.from( retryLogic.retryRx( publisher ) ).single(); + + assertEquals( result, await( single ) ); + + List scheduleDelays = eventExecutor.scheduleDelays(); + assertEquals( 1, scheduleDelays.size() ); + assertEquals( 4242, scheduleDelays.get( 0 ).intValue() ); + } + + @ParameterizedTest + @MethodSource( "cannotBeRetriedErrors" ) + void scheduleNoRetryOnErrorRx( Exception error ) + { + Clock clock = mock( Clock.class ); + ExponentialBackoffRetryLogic retryLogic = newRetryLogic( 1, 10, 1, 1, clock ); + + Mono single = Flux.from( retryLogic.retryRx( Mono.error( error ) ) ).single(); + + Exception e = assertThrows( Exception.class, () -> await( single ) ); + assertEquals( error, e ); + + assertEquals( 0, eventExecutor.scheduleDelays().size() ); + } + @Test void throwsWhenSleepInterrupted() throws Exception { @@ -539,6 +659,37 @@ void collectsSuppressedErrorsAsync() assertEquals( initialDelay * multiplier * multiplier, scheduleDelays.get( 2 ).intValue() ); } + @Test + void collectsSuppressedErrorsRx() throws Exception + { + long maxRetryTime = 20; + int initialDelay = 15; + int multiplier = 2; + Clock clock = mock( Clock.class ); + when( clock.millis() ).thenReturn( 0L ).thenReturn( 10L ).thenReturn( 15L ).thenReturn( 25L ); + ExponentialBackoffRetryLogic logic = newRetryLogic( maxRetryTime, initialDelay, multiplier, 0, clock ); + + SessionExpiredException error1 = sessionExpired(); + SessionExpiredException error2 = sessionExpired(); + ServiceUnavailableException error3 = serviceUnavailable(); + TransientException error4 = transientException(); + Mono mono = createMono( "A result", error1, error2, error3, error4 ); + + StepVerifier.create( logic.retryRx( mono ) ).expectErrorSatisfies( e -> { + assertEquals( error4, e ); + Throwable[] suppressed = e.getSuppressed(); + assertEquals( 3, suppressed.length ); + assertEquals( error1, suppressed[0] ); + assertEquals( error2, suppressed[1] ); + assertEquals( error3, suppressed[2] ); + } ).verify(); + + List scheduleDelays = eventExecutor.scheduleDelays(); + assertEquals( 3, scheduleDelays.size() ); + assertEquals( initialDelay, scheduleDelays.get( 0 ).intValue() ); + assertEquals( initialDelay * multiplier, scheduleDelays.get( 1 ).intValue() ); + assertEquals( initialDelay * multiplier * multiplier, scheduleDelays.get( 2 ).intValue() ); } + @Test void doesNotCollectSuppressedErrorsWhenSameErrorIsThrown() throws Exception { @@ -591,6 +742,26 @@ void doesNotCollectSuppressedErrorsWhenSameErrorIsThrownAsync() assertEquals( initialDelay * multiplier, scheduleDelays.get( 1 ).intValue() ); } + @Test + void doesNotCollectSuppressedErrorsWhenSameErrorIsThrownRx() + { + long maxRetryTime = 20; + int initialDelay = 15; + int multiplier = 2; + Clock clock = mock( Clock.class ); + when( clock.millis() ).thenReturn( 0L ).thenReturn( 10L ).thenReturn( 25L ); + + ExponentialBackoffRetryLogic retryLogic = newRetryLogic( maxRetryTime, initialDelay, multiplier, 0, clock ); + + SessionExpiredException error = sessionExpired(); + StepVerifier.create( retryLogic.retryRx( Mono.error( error ) ) ).expectErrorSatisfies( e -> assertEquals( error, e ) ).verify(); + + List scheduleDelays = eventExecutor.scheduleDelays(); + assertEquals( 2, scheduleDelays.size() ); + assertEquals( initialDelay, scheduleDelays.get( 0 ).intValue() ); + assertEquals( initialDelay * multiplier, scheduleDelays.get( 1 ).intValue() ); + } + @Test void eachRetryIsLogged() { @@ -631,6 +802,27 @@ void eachRetryIsLoggedAsync() ); } + @Test + void eachRetryIsLoggedRx() + { + String result = "The Result"; + int retries = 9; + Clock clock = mock( Clock.class ); + Logging logging = mock( Logging.class ); + Logger logger = mock( Logger.class ); + when( logging.getLog( anyString() ) ).thenReturn( logger ); + + ExponentialBackoffRetryLogic logic = new ExponentialBackoffRetryLogic( RetrySettings.DEFAULT, eventExecutor, + clock, logging ); + + assertEquals( result, await( Flux.from( retryRx( logic, retries, result ) ).single() ) ); + + verify( logger, times( retries ) ).warn( + startsWith( "Reactive transaction failed and is scheduled to retry" ), + any( ServiceUnavailableException.class ) + ); + } + @Test void nothingIsLoggedOnFatalFailure() { @@ -665,6 +857,22 @@ void nothingIsLoggedOnFatalFailureAsync() verifyZeroInteractions( logger ); } + @Test + void nothingIsLoggedOnFatalFailureRx() + { + Logging logging = mock( Logging.class ); + Logger logger = mock( Logger.class ); + when( logging.getLog( anyString() ) ).thenReturn( logger ); + ExponentialBackoffRetryLogic logic = new ExponentialBackoffRetryLogic( RetrySettings.DEFAULT, eventExecutor, + mock( Clock.class ), logging ); + + Publisher retryRx = logic.retryRx( Mono.error( new RuntimeException( "Fatal rx" ) ) ); + RuntimeException error = assertThrows( RuntimeException.class, () -> await( retryRx ) ); + + assertEquals( "Fatal rx", error.getMessage() ); + verifyZeroInteractions( logger ); + } + @Test void correctNumberOfRetiesAreLoggedOnFailure() { @@ -741,6 +949,82 @@ public CompletionStage get() ); } + @Test + void correctNumberOfRetiesAreLoggedOnFailureRx() + { + Clock clock = mock( Clock.class ); + Logging logging = mock( Logging.class ); + Logger logger = mock( Logger.class ); + when( logging.getLog( anyString() ) ).thenReturn( logger ); + RetrySettings settings = RetrySettings.DEFAULT; + RetryLogic logic = new ExponentialBackoffRetryLogic( settings, eventExecutor, clock, logging ); + + AtomicBoolean invoked = new AtomicBoolean( false ); + SessionExpiredException error = assertThrows( SessionExpiredException.class, () -> + await( logic.retryRx( Mono.create( e -> { + if ( invoked.get() ) + { + // move clock forward to stop retries + when( clock.millis() ).thenReturn( settings.maxRetryTimeMs() + 42 ); + } + else + { + invoked.set( true ); + } + e.error( new SessionExpiredException( "Session no longer valid" ) ); + } ) ) ) ); + assertEquals( "Session no longer valid", error.getMessage() ); + verify( logger ).warn( + startsWith( "Reactive transaction failed and is scheduled to retry" ), + any( SessionExpiredException.class ) + ); + } + + @Test + void shouldRetryWithBackOffRx() + { + Exception exception = new TransientException( "Unknown", "Retry this error." ); + Clock clock = mock( Clock.class ); + when( clock.millis() ).thenReturn( 0L, 100L, 200L, 400L, 800L ); + ExponentialBackoffRetryLogic retryLogic = new ExponentialBackoffRetryLogic( 500, 100, 2, 0, + eventExecutor, clock, DEV_NULL_LOGGING ); + + Flux source = Flux.concat( Flux.range( 0, 2 ), Flux.error( exception ) ); + Flux retriedSource = Flux.from( retryLogic.retryRx( source ) ); + StepVerifier.create( retriedSource ) + .expectNext( 0, 1 ) // first run + .expectNext( 0, 1, 0, 1, 0, 1, 0, 1 ) //4 retry attempts + .verifyErrorSatisfies( e -> assertThat( e, equalTo( exception ) ) ); + + List delays = eventExecutor.scheduleDelays(); + assertThat( delays.size(), equalTo( 4 ) ); + assertThat( delays, contains( 100L, 200L, 400L, 800L ) ); + } + + @Test + void shouldRetryWithRandomBackOffRx() + { + Exception exception = new TransientException( "Unknown", "Retry this error." ); + Clock clock = mock( Clock.class ); + when( clock.millis() ).thenReturn( 0L, 100L, 200L, 400L, 800L ); + ExponentialBackoffRetryLogic retryLogic = new ExponentialBackoffRetryLogic( 500, 100, 2, 0.1, + eventExecutor, clock, DEV_NULL_LOGGING ); + + Flux source = Flux.concat( Flux.range( 0, 2 ), Flux.error( exception ) ); + Flux retriedSource = Flux.from( retryLogic.retryRx( source ) ); + StepVerifier.create( retriedSource ) + .expectNext( 0, 1 ) // first run + .expectNext( 0, 1, 0, 1, 0, 1, 0, 1 ) // 4 retry attempts + .verifyErrorSatisfies( e -> assertThat( e, equalTo( exception ) ) ); + + List delays = eventExecutor.scheduleDelays(); + assertThat( delays.size(), equalTo( 4 ) ); + assertThat( delays.get( 0 ), allOf( greaterThanOrEqualTo( 90L ), lessThanOrEqualTo( 110L ) ) ); + assertThat( delays.get( 1 ), allOf( greaterThanOrEqualTo( 180L ), lessThanOrEqualTo( 220L ) ) ); + assertThat( delays.get( 2 ), allOf( greaterThanOrEqualTo( 260L ), lessThanOrEqualTo( 440L ) ) ); + assertThat( delays.get( 3 ), allOf( greaterThanOrEqualTo( 720L ), lessThanOrEqualTo( 880L ) ) ); + } + private static void retry( ExponentialBackoffRetryLogic retryLogic, final int times ) { retryLogic.retry( new Supplier() @@ -779,6 +1063,22 @@ public CompletionStage get() } ); } + private Publisher retryRx( ExponentialBackoffRetryLogic retryLogic, int times, Object result ) + { + AtomicInteger invoked = new AtomicInteger(); + return retryLogic.retryRx( Mono.create( e -> { + if ( invoked.get() < times ) + { + invoked.getAndIncrement(); + e.error( serviceUnavailable() ); + } + else + { + e.success( result ); + } + } ) ); + } + private static List delaysWithoutJitter( long initialDelay, double multiplier, int count ) { List values = new ArrayList<>(); @@ -840,4 +1140,38 @@ private static void assertDelaysApproximatelyEqual( List expectedDelays, L assertThat( actualValue, closeTo( expectedValue, expectedValue * delta ) ); } } + + private Mono createMono( T result, Exception... errors ) + { + AtomicInteger executionCount = new AtomicInteger(); + Iterator iterator = Arrays.asList( errors ).iterator(); + return Mono.create( (Consumer>) e -> { + if ( iterator.hasNext() ) + { + e.error( iterator.next() ); + } + else + { + e.success( result ); + } + } ).doOnTerminate( executionCount::getAndIncrement ); + } + + private static Stream canBeRetriedErrors() + { + return Stream.of( + transientException(), + sessionExpired(), + serviceUnavailable() + ); + } + + private static Stream cannotBeRetriedErrors() + { + return Stream.of( + new IllegalStateException(), + new TransientException( "Neo.TransientError.Transaction.Terminated", "" ), + new TransientException( "Neo.TransientError.Transaction.LockClientStopped", "" ) + ); + } } diff --git a/driver/src/test/java/org/neo4j/driver/internal/util/DriverFactoryWithOneEventLoopThread.java b/driver/src/test/java/org/neo4j/driver/internal/util/DriverFactoryWithOneEventLoopThread.java index caa7f76cd2..a592f814b3 100644 --- a/driver/src/test/java/org/neo4j/driver/internal/util/DriverFactoryWithOneEventLoopThread.java +++ b/driver/src/test/java/org/neo4j/driver/internal/util/DriverFactoryWithOneEventLoopThread.java @@ -23,7 +23,7 @@ import java.net.URI; import org.neo4j.driver.internal.DriverFactory; -import org.neo4j.driver.internal.async.BootstrapFactory; +import org.neo4j.driver.internal.async.connection.BootstrapFactory; import org.neo4j.driver.internal.cluster.RoutingSettings; import org.neo4j.driver.internal.retry.RetrySettings; import org.neo4j.driver.AuthToken; diff --git a/driver/src/test/java/org/neo4j/driver/internal/util/FuturesTest.java b/driver/src/test/java/org/neo4j/driver/internal/util/FuturesTest.java index 6ba3f38bb9..6e0b907ea8 100644 --- a/driver/src/test/java/org/neo4j/driver/internal/util/FuturesTest.java +++ b/driver/src/test/java/org/neo4j/driver/internal/util/FuturesTest.java @@ -34,7 +34,7 @@ import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; -import org.neo4j.driver.internal.async.EventLoopGroupFactory; +import org.neo4j.driver.internal.async.connection.EventLoopGroupFactory; import static org.hamcrest.Matchers.is; import static org.hamcrest.junit.MatcherAssert.assertThat; diff --git a/driver/src/test/java/org/neo4j/driver/internal/util/ImmediateSchedulingEventExecutor.java b/driver/src/test/java/org/neo4j/driver/internal/util/ImmediateSchedulingEventExecutor.java index c5edc335df..0df9a26eae 100644 --- a/driver/src/test/java/org/neo4j/driver/internal/util/ImmediateSchedulingEventExecutor.java +++ b/driver/src/test/java/org/neo4j/driver/internal/util/ImmediateSchedulingEventExecutor.java @@ -180,7 +180,9 @@ public ScheduledFuture schedule( Runnable command, long delay, TimeUnit unit @Override public ScheduledFuture schedule( Callable callable, long delay, TimeUnit unit ) { - throw new UnsupportedOperationException(); + scheduleDelays.add( unit.toMillis( delay ) ); + delegate.submit( callable ); + return mock( ScheduledFuture.class ); } @Override diff --git a/driver/src/test/java/org/neo4j/driver/internal/util/MessageRecordingDriverFactory.java b/driver/src/test/java/org/neo4j/driver/internal/util/MessageRecordingDriverFactory.java index 0a66bf78ca..ab5239e14a 100644 --- a/driver/src/test/java/org/neo4j/driver/internal/util/MessageRecordingDriverFactory.java +++ b/driver/src/test/java/org/neo4j/driver/internal/util/MessageRecordingDriverFactory.java @@ -30,10 +30,10 @@ import org.neo4j.driver.internal.ConnectionSettings; import org.neo4j.driver.internal.DriverFactory; -import org.neo4j.driver.internal.async.ChannelConnector; -import org.neo4j.driver.internal.async.ChannelConnectorImpl; -import org.neo4j.driver.internal.async.ChannelPipelineBuilder; -import org.neo4j.driver.internal.async.ChannelPipelineBuilderImpl; +import org.neo4j.driver.internal.async.connection.ChannelConnector; +import org.neo4j.driver.internal.async.connection.ChannelConnectorImpl; +import org.neo4j.driver.internal.async.connection.ChannelPipelineBuilder; +import org.neo4j.driver.internal.async.connection.ChannelPipelineBuilderImpl; import org.neo4j.driver.internal.async.outbound.OutboundMessageHandler; import org.neo4j.driver.internal.messaging.Message; import org.neo4j.driver.internal.messaging.MessageFormat; diff --git a/driver/src/test/java/org/neo4j/driver/internal/util/io/ChannelPipelineBuilderWithFailingMessageFormat.java b/driver/src/test/java/org/neo4j/driver/internal/util/io/ChannelPipelineBuilderWithFailingMessageFormat.java index b6014d3a6e..3f88983f60 100644 --- a/driver/src/test/java/org/neo4j/driver/internal/util/io/ChannelPipelineBuilderWithFailingMessageFormat.java +++ b/driver/src/test/java/org/neo4j/driver/internal/util/io/ChannelPipelineBuilderWithFailingMessageFormat.java @@ -20,8 +20,8 @@ import io.netty.channel.ChannelPipeline; -import org.neo4j.driver.internal.async.ChannelPipelineBuilder; -import org.neo4j.driver.internal.async.ChannelPipelineBuilderImpl; +import org.neo4j.driver.internal.async.connection.ChannelPipelineBuilder; +import org.neo4j.driver.internal.async.connection.ChannelPipelineBuilderImpl; import org.neo4j.driver.internal.messaging.MessageFormat; import org.neo4j.driver.internal.util.FailingMessageFormat; import org.neo4j.driver.Logging; diff --git a/driver/src/test/java/org/neo4j/driver/internal/util/io/ChannelTrackingConnector.java b/driver/src/test/java/org/neo4j/driver/internal/util/io/ChannelTrackingConnector.java index 7072d325ee..1282db02a9 100644 --- a/driver/src/test/java/org/neo4j/driver/internal/util/io/ChannelTrackingConnector.java +++ b/driver/src/test/java/org/neo4j/driver/internal/util/io/ChannelTrackingConnector.java @@ -25,7 +25,7 @@ import java.util.List; import org.neo4j.driver.internal.BoltServerAddress; -import org.neo4j.driver.internal.async.ChannelConnector; +import org.neo4j.driver.internal.async.connection.ChannelConnector; public class ChannelTrackingConnector implements ChannelConnector { diff --git a/driver/src/test/java/org/neo4j/driver/internal/util/io/ChannelTrackingDriverFactory.java b/driver/src/test/java/org/neo4j/driver/internal/util/io/ChannelTrackingDriverFactory.java index 13386d2037..252483c4ba 100644 --- a/driver/src/test/java/org/neo4j/driver/internal/util/io/ChannelTrackingDriverFactory.java +++ b/driver/src/test/java/org/neo4j/driver/internal/util/io/ChannelTrackingDriverFactory.java @@ -27,8 +27,8 @@ import org.neo4j.driver.internal.BoltServerAddress; import org.neo4j.driver.internal.ConnectionSettings; -import org.neo4j.driver.internal.async.BootstrapFactory; -import org.neo4j.driver.internal.async.ChannelConnector; +import org.neo4j.driver.internal.async.connection.BootstrapFactory; +import org.neo4j.driver.internal.async.connection.ChannelConnector; import org.neo4j.driver.internal.metrics.MetricsProvider; import org.neo4j.driver.internal.security.SecurityPlan; import org.neo4j.driver.internal.spi.ConnectionPool; diff --git a/driver/src/test/java/org/neo4j/driver/internal/util/io/ChannelTrackingDriverFactoryWithFailingMessageFormat.java b/driver/src/test/java/org/neo4j/driver/internal/util/io/ChannelTrackingDriverFactoryWithFailingMessageFormat.java index 60f3e0c2db..703f3e2081 100644 --- a/driver/src/test/java/org/neo4j/driver/internal/util/io/ChannelTrackingDriverFactoryWithFailingMessageFormat.java +++ b/driver/src/test/java/org/neo4j/driver/internal/util/io/ChannelTrackingDriverFactoryWithFailingMessageFormat.java @@ -19,8 +19,8 @@ package org.neo4j.driver.internal.util.io; import org.neo4j.driver.internal.ConnectionSettings; -import org.neo4j.driver.internal.async.ChannelConnector; -import org.neo4j.driver.internal.async.ChannelConnectorImpl; +import org.neo4j.driver.internal.async.connection.ChannelConnector; +import org.neo4j.driver.internal.async.connection.ChannelConnectorImpl; import org.neo4j.driver.internal.security.SecurityPlan; import org.neo4j.driver.internal.util.Clock; import org.neo4j.driver.internal.util.FailingMessageFormat; diff --git a/driver/src/test/java/org/neo4j/driver/util/DriverExtension.java b/driver/src/test/java/org/neo4j/driver/util/DriverExtension.java new file mode 100644 index 0000000000..2ada7da286 --- /dev/null +++ b/driver/src/test/java/org/neo4j/driver/util/DriverExtension.java @@ -0,0 +1,69 @@ +/* + * Copyright (c) 2002-2019 "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.util; + +import org.junit.jupiter.api.extension.AfterEachCallback; +import org.junit.jupiter.api.extension.BeforeEachCallback; +import org.junit.jupiter.api.extension.ExtensionContext; + +import org.neo4j.driver.Session; +import org.neo4j.driver.async.AsyncSession; + +import static org.neo4j.driver.util.TestUtil.await; + +/** + * A little utility for integration testing, this provides tests with sessions they can work with. + * If you want more direct control, have a look at {@link DatabaseExtension} instead. + */ +public class DriverExtension extends DatabaseExtension implements BeforeEachCallback, AfterEachCallback +{ + private AsyncSession asyncSession; + private Session session; + + public AsyncSession asyncSession() + { + return asyncSession; + } + + public Session session() + { + return session; + } + + @Override + public void beforeEach( ExtensionContext context ) throws Exception + { + super.beforeEach( context ); + asyncSession = driver().asyncSession(); + session = driver().session(); + } + + @Override + public void afterEach( ExtensionContext context ) + { + if ( asyncSession != null ) + { + await( asyncSession.closeAsync() ); + } + if ( session != null ) + { + session.close(); + } + } +} diff --git a/driver/src/test/java/org/neo4j/driver/util/SessionExtension.java b/driver/src/test/java/org/neo4j/driver/util/SessionExtension.java index eda0e49009..e06a10da13 100644 --- a/driver/src/test/java/org/neo4j/driver/util/SessionExtension.java +++ b/driver/src/test/java/org/neo4j/driver/util/SessionExtension.java @@ -23,7 +23,6 @@ import org.junit.jupiter.api.extension.ExtensionContext; import java.util.Map; -import java.util.concurrent.CompletionStage; import org.neo4j.driver.Record; import org.neo4j.driver.Session; @@ -33,26 +32,21 @@ import org.neo4j.driver.TransactionConfig; import org.neo4j.driver.TransactionWork; import org.neo4j.driver.Value; -import org.neo4j.driver.async.AsyncSession; -import org.neo4j.driver.async.AsyncTransaction; -import org.neo4j.driver.async.AsyncTransactionWork; -import org.neo4j.driver.async.StatementResultCursor; -import org.neo4j.driver.internal.NetworkSession; import org.neo4j.driver.types.TypeSystem; /** * A little utility for integration testing, this provides tests with a session they can work with. * If you want more direct control, have a look at {@link DatabaseExtension} instead. */ -public class SessionExtension extends DatabaseExtension implements Session, AsyncSession, BeforeEachCallback, AfterEachCallback +public class SessionExtension extends DatabaseExtension implements Session, BeforeEachCallback, AfterEachCallback { - private NetworkSession realSession; + private Session realSession; @Override public void beforeEach( ExtensionContext context ) throws Exception { super.beforeEach( context ); - realSession = (NetworkSession) driver().session(); // TODO This shall only return a blocking driver + realSession = driver().session(); } @Override @@ -76,18 +70,6 @@ public void close() throw new UnsupportedOperationException( "Disallowed on this test session" ); } - @Override - public CompletionStage closeAsync() - { - throw new UnsupportedOperationException( "Disallowed on this test session" ); - } - - @Override - public CompletionStage runAsync( String statement, Map params ) - { - return realSession.runAsync( statement, params ); - } - @Override public Transaction beginTransaction() { @@ -100,25 +82,6 @@ public Transaction beginTransaction( TransactionConfig config ) return realSession.beginTransaction( config ); } - @Deprecated - @Override - public Transaction beginTransaction( String bookmark ) - { - return realSession.beginTransaction( bookmark ); - } - - @Override - public CompletionStage beginTransactionAsync() - { - return realSession.beginTransactionAsync(); - } - - @Override - public CompletionStage beginTransactionAsync( TransactionConfig config ) - { - return realSession.beginTransactionAsync( config ); - } - @Override public T readTransaction( TransactionWork work ) { @@ -131,18 +94,6 @@ public T readTransaction( TransactionWork work, TransactionConfig config return realSession.readTransaction( work, config ); } - @Override - public CompletionStage readTransactionAsync( AsyncTransactionWork> work ) - { - return realSession.readTransactionAsync( work ); - } - - @Override - public CompletionStage readTransactionAsync( AsyncTransactionWork> work, TransactionConfig config ) - { - return realSession.readTransactionAsync( work, config ); - } - @Override public T writeTransaction( TransactionWork work ) { @@ -155,18 +106,6 @@ public T writeTransaction( TransactionWork work, TransactionConfig config return realSession.writeTransaction( work, config ); } - @Override - public CompletionStage writeTransactionAsync( AsyncTransactionWork> work ) - { - return realSession.writeTransactionAsync( work ); - } - - @Override - public CompletionStage writeTransactionAsync( AsyncTransactionWork> work, TransactionConfig config ) - { - return realSession.writeTransactionAsync( work, config ); - } - @Override public String lastBookmark() { @@ -192,48 +131,24 @@ public StatementResult run( String statementText, Value parameters ) return realSession.run( statementText, parameters ); } - @Override - public CompletionStage runAsync( String statementText, Value parameters ) - { - return realSession.runAsync( statementText, parameters ); - } - @Override public StatementResult run( String statementText, Record parameters ) { return realSession.run( statementText, parameters ); } - @Override - public CompletionStage runAsync( String statementTemplate, Record statementParameters ) - { - return realSession.runAsync( statementTemplate, statementParameters ); - } - @Override public StatementResult run( String statementTemplate ) { return realSession.run( statementTemplate ); } - @Override - public CompletionStage runAsync( String statementTemplate ) - { - return realSession.runAsync( statementTemplate ); - } - @Override public StatementResult run( Statement statement ) { return realSession.run( statement.text(), statement.parameters() ); } - @Override - public CompletionStage runAsync( Statement statement ) - { - return realSession.runAsync( statement ); - } - @Override public StatementResult run( String statement, TransactionConfig config ) { @@ -252,24 +167,6 @@ public StatementResult run( Statement statement, TransactionConfig config ) return realSession.run( statement, config ); } - @Override - public CompletionStage runAsync( String statement, TransactionConfig config ) - { - return realSession.runAsync( statement, config ); - } - - @Override - public CompletionStage runAsync( String statement, Map parameters, TransactionConfig config ) - { - return realSession.runAsync( statement, parameters, config ); - } - - @Override - public CompletionStage runAsync( Statement statement, TransactionConfig config ) - { - return realSession.runAsync( statement, config ); - } - @Override public TypeSystem typeSystem() { diff --git a/driver/src/test/java/org/neo4j/driver/util/TestUtil.java b/driver/src/test/java/org/neo4j/driver/util/TestUtil.java index e6b8a34fe0..cfdd01bf0a 100644 --- a/driver/src/test/java/org/neo4j/driver/util/TestUtil.java +++ b/driver/src/test/java/org/neo4j/driver/util/TestUtil.java @@ -21,6 +21,9 @@ import io.netty.buffer.ByteBuf; import io.netty.util.internal.PlatformDependent; import org.mockito.ArgumentMatcher; +import org.mockito.invocation.InvocationOnMock; +import org.mockito.stubbing.Answer; +import org.mockito.verification.VerificationMode; import org.reactivestreams.Publisher; import reactor.core.publisher.Flux; import reactor.core.publisher.Mono; @@ -40,22 +43,36 @@ import java.util.concurrent.TimeoutException; import java.util.function.BooleanSupplier; +import org.neo4j.driver.AccessMode; +import org.neo4j.driver.Driver; +import org.neo4j.driver.Session; +import org.neo4j.driver.StatementResult; +import org.neo4j.driver.exceptions.ServiceUnavailableException; import org.neo4j.driver.internal.BoltServerAddress; -import org.neo4j.driver.internal.async.EventLoopGroupFactory; +import org.neo4j.driver.internal.Bookmarks; +import org.neo4j.driver.internal.DefaultBookmarksHolder; +import org.neo4j.driver.internal.async.NetworkSession; +import org.neo4j.driver.internal.async.connection.EventLoopGroupFactory; +import org.neo4j.driver.internal.handlers.BeginTxResponseHandler; +import org.neo4j.driver.internal.handlers.NoOpResponseHandler; import org.neo4j.driver.internal.messaging.BoltProtocol; import org.neo4j.driver.internal.messaging.Message; import org.neo4j.driver.internal.messaging.request.BeginMessage; import org.neo4j.driver.internal.messaging.request.CommitMessage; +import org.neo4j.driver.internal.messaging.request.PullMessage; import org.neo4j.driver.internal.messaging.request.RollbackMessage; import org.neo4j.driver.internal.messaging.request.RunMessage; +import org.neo4j.driver.internal.messaging.request.RunWithMetadataMessage; +import org.neo4j.driver.internal.messaging.v1.BoltProtocolV1; +import org.neo4j.driver.internal.messaging.v2.BoltProtocolV2; +import org.neo4j.driver.internal.messaging.v3.BoltProtocolV3; import org.neo4j.driver.internal.messaging.v4.BoltProtocolV4; +import org.neo4j.driver.internal.retry.RetryLogic; import org.neo4j.driver.internal.spi.Connection; +import org.neo4j.driver.internal.spi.ConnectionProvider; import org.neo4j.driver.internal.spi.ResponseHandler; +import org.neo4j.driver.internal.util.FixedRetryLogic; import org.neo4j.driver.internal.util.ServerVersion; -import org.neo4j.driver.AccessMode; -import org.neo4j.driver.Driver; -import org.neo4j.driver.Session; -import org.neo4j.driver.StatementResult; import static java.util.Collections.emptyMap; import static java.util.concurrent.TimeUnit.MILLISECONDS; @@ -66,11 +83,17 @@ import static org.junit.jupiter.api.Assertions.fail; import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.argThat; +import static org.mockito.ArgumentMatchers.eq; import static org.mockito.Mockito.doAnswer; import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.times; +import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; -import static org.neo4j.driver.internal.messaging.request.MultiDatabaseUtil.ABSENT_DB_NAME; import static org.neo4j.driver.AccessMode.WRITE; +import static org.neo4j.driver.internal.Bookmarks.empty; +import static org.neo4j.driver.internal.logging.DevNullLogging.DEV_NULL_LOGGING; +import static org.neo4j.driver.internal.messaging.request.MultiDatabaseUtil.ABSENT_DB_NAME; +import static org.neo4j.driver.internal.util.Futures.completedWithNull; public final class TestUtil { @@ -212,6 +235,202 @@ public static String cleanDb( Driver driver ) } } + public static NetworkSession newSession( ConnectionProvider connectionProvider, Bookmarks x ) + { + return newSession( connectionProvider, WRITE, x ); + } + + private static NetworkSession newSession( ConnectionProvider connectionProvider, AccessMode mode, Bookmarks x ) + { + return newSession( connectionProvider, mode, new FixedRetryLogic( 0 ), x ); + } + + public static NetworkSession newSession( ConnectionProvider connectionProvider, AccessMode mode ) + { + return newSession( connectionProvider, mode, empty() ); + } + + public static NetworkSession newSession( ConnectionProvider connectionProvider, RetryLogic logic ) + { + return newSession( connectionProvider, WRITE, logic, empty() ); + } + + public static NetworkSession newSession( ConnectionProvider connectionProvider ) + { + return newSession( connectionProvider, WRITE, empty() ); + } + + public static NetworkSession newSession( ConnectionProvider connectionProvider, AccessMode mode, + RetryLogic retryLogic, Bookmarks bookmarks ) + { + return new NetworkSession( connectionProvider, retryLogic, ABSENT_DB_NAME, mode, new DefaultBookmarksHolder( bookmarks ), DEV_NULL_LOGGING ); + } + + public static void verifyRun( Connection connection, String query ) + { + verify( connection ).writeAndFlush( argThat( runWithMetaMessageWithStatementMatcher( query ) ), any() ); + } + + public static void verifyRunAndPull( Connection connection, String query ) + { + verify( connection ).writeAndFlush( argThat( runWithMetaMessageWithStatementMatcher( query ) ), any(), any( PullMessage.class ), any() ); + } + + public static void verifyCommitTx( Connection connection, VerificationMode mode ) + { + verify( connection, mode ).writeAndFlush( any( CommitMessage.class ), any() ); + } + + public static void verifyCommitTx( Connection connection ) + { + verifyCommitTx( connection, times( 1 ) ); + } + + public static void verifyRollbackTx( Connection connection, VerificationMode mode ) + { + verify( connection, mode ).writeAndFlush( any( RollbackMessage.class ), any() ); + } + + public static void verifyRollbackTx( Connection connection ) + { + verifyRollbackTx( connection, times( 1 ) ); + } + + public static void verifyBeginTx( Connection connectionMock ) + { + verifyBeginTx( connectionMock, Bookmarks.empty() ); + } + + public static void verifyBeginTx( Connection connectionMock, Bookmarks bookmarks ) + { + if ( bookmarks.isEmpty() ) + { + verify( connectionMock ).write( any( BeginMessage.class ), eq( NoOpResponseHandler.INSTANCE ) ); + } + else + { + verify( connectionMock ).writeAndFlush( any( BeginMessage.class ), any( BeginTxResponseHandler.class ) ); + } + } + + public static void setupFailingRun( Connection connection, Throwable error ) + { + doAnswer( invocation -> + { + ResponseHandler runHandler = invocation.getArgument( 1 ); + runHandler.onFailure( error ); + ResponseHandler pullHandler = invocation.getArgument( 3 ); + pullHandler.onFailure( error ); + return null; + } ).when( connection ).writeAndFlush( any( RunWithMetadataMessage.class ), any(), any( PullMessage.class ), any() ); + } + + public static void setupFailingBegin( Connection connection, Throwable error ) + { + // with bookmarks + doAnswer( invocation -> + { + ResponseHandler handler = invocation.getArgument( 1 ); + handler.onFailure( error ); + return null; + } ).when( connection ).writeAndFlush( any( BeginMessage.class ), any( BeginTxResponseHandler.class ) ); + } + + public static void setupFailingCommit( Connection connection ) + { + setupFailingCommit( connection, 1 ); + } + + public static void setupFailingCommit( Connection connection, int times ) + { + doAnswer( new Answer() + { + int invoked; + + @Override + public Void answer( InvocationOnMock invocation ) + { + ResponseHandler handler = invocation.getArgument( 1 ); + if ( invoked++ < times ) + { + handler.onFailure( new ServiceUnavailableException( "" ) ); + } + else + { + handler.onSuccess( emptyMap() ); + } + return null; + } + } ).when( connection ).writeAndFlush( any( CommitMessage.class ), any() ); + } + + public static void setupFailingRollback( Connection connection ) + { + setupFailingRollback( connection, 1 ); + } + + public static void setupFailingRollback( Connection connection, int times ) + { + doAnswer( new Answer() + { + int invoked; + + @Override + public Void answer( InvocationOnMock invocation ) + { + ResponseHandler handler = invocation.getArgument( 1 ); + if ( invoked++ < times ) + { + handler.onFailure( new ServiceUnavailableException( "" ) ); + } + else + { + handler.onSuccess( emptyMap() ); + } + return null; + } + } ).when( connection ).writeAndFlush( any( RollbackMessage.class ), any() ); + } + + public static void setupSuccessfulRunAndPull( Connection connection ) + { + doAnswer( invocation -> + { + ResponseHandler runHandler = invocation.getArgument( 1 ); + runHandler.onSuccess( emptyMap() ); + ResponseHandler pullHandler = invocation.getArgument( 3 ); + pullHandler.onSuccess( emptyMap() ); + return null; + } ).when( connection ).writeAndFlush( any( RunWithMetadataMessage.class ), any(), any( PullMessage.class ), any() ); + } + + public static void setupSuccessfulRun( Connection connection ) + { + doAnswer( invocation -> + { + ResponseHandler runHandler = invocation.getArgument( 1 ); + runHandler.onSuccess( emptyMap() ); + return null; + } ).when( connection ).writeAndFlush( any( RunWithMetadataMessage.class ), any() ); + } + + public static void setupSuccessfulRunAndPull( Connection connection, String query ) + { + doAnswer( invocation -> + { + ResponseHandler runHandler = invocation.getArgument( 1 ); + runHandler.onSuccess( emptyMap() ); + ResponseHandler pullHandler = invocation.getArgument( 3 ); + pullHandler.onSuccess( emptyMap() ); + return null; + } ).when( connection ).writeAndFlush( argThat( runWithMetaMessageWithStatementMatcher( query ) ), any(), any( PullMessage.class ), any() ); + } + + public static Connection connectionMock() + { + return connectionMock( BoltProtocolV2.INSTANCE ); + } + public static Connection connectionMock( BoltProtocol protocol ) { return connectionMock( WRITE, protocol ); @@ -235,12 +454,25 @@ public static Connection connectionMock( String databaseName, AccessMode mode, B when( connection.protocol() ).thenReturn( protocol ); when( connection.mode() ).thenReturn( mode ); when( connection.databaseName() ).thenReturn( databaseName ); - setupSuccessfulPullAll( connection, "COMMIT" ); - setupSuccessfulPullAll( connection, "ROLLBACK" ); - setupSuccessfulPullAll( connection, "BEGIN" ); - setupSuccessResponse( connection, CommitMessage.class ); - setupSuccessResponse( connection, RollbackMessage.class ); - setupSuccessResponse( connection, BeginMessage.class ); + int version = protocol.version(); + if ( version == BoltProtocolV1.VERSION || version == BoltProtocolV2.VERSION ) + { + setupSuccessfulPullAll( connection, "COMMIT" ); + setupSuccessfulPullAll( connection, "ROLLBACK" ); + setupSuccessfulPullAll( connection, "BEGIN" ); + } + else if ( version == BoltProtocolV3.VERSION || version == BoltProtocolV4.VERSION ) + { + setupSuccessResponse( connection, CommitMessage.class ); + setupSuccessResponse( connection, RollbackMessage.class ); + setupSuccessResponse( connection, BeginMessage.class ); + when( connection.release() ).thenReturn( completedWithNull() ); + when( connection.reset() ).thenReturn( completedWithNull() ); + } + else + { + throw new IllegalArgumentException( "Unsupported bolt protocol version: " + version ); + } return connection; } @@ -332,6 +564,12 @@ public static ArgumentMatcher runMessageWithStatementMatcher( String st return message -> message instanceof RunMessage && Objects.equals( statement, ((RunMessage) message).statement() ); } + public static ArgumentMatcher runWithMetaMessageWithStatementMatcher( String statement ) + { + return message -> message instanceof RunWithMetadataMessage && Objects.equals( statement, ((RunWithMetadataMessage) message).statement() ); + } + + private static void setupSuccessfulPullAll( Connection connection, String statement ) { doAnswer( invocation -> diff --git a/examples/pom.xml b/examples/pom.xml index 864b2e9696..fb54589307 100644 --- a/examples/pom.xml +++ b/examples/pom.xml @@ -41,6 +41,10 @@ org.hamcrest hamcrest-junit + + org.mockito + mockito-core + org.junit.jupiter junit-jupiter