diff --git a/driver/pom.xml b/driver/pom.xml index c5be24348b..2c2d92c744 100644 --- a/driver/pom.xml +++ b/driver/pom.xml @@ -104,8 +104,8 @@ maven-compiler-plugin 2.3.2 - 1.7 - 1.7 + ${java.version} + ${java.version} 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 4bd40e7bd2..639dcd75ba 100644 --- a/driver/src/main/java/org/neo4j/driver/internal/DirectConnectionProvider.java +++ b/driver/src/main/java/org/neo4j/driver/internal/DirectConnectionProvider.java @@ -18,8 +18,10 @@ */ package org.neo4j.driver.internal; +import java.util.concurrent.CompletionStage; + import org.neo4j.driver.internal.async.AsyncConnection; -import org.neo4j.driver.internal.async.InternalFuture; +import org.neo4j.driver.internal.async.Futures; import org.neo4j.driver.internal.async.pool.AsyncConnectionPool; import org.neo4j.driver.internal.net.BoltServerAddress; import org.neo4j.driver.internal.spi.ConnectionPool; @@ -53,7 +55,7 @@ public PooledConnection acquireConnection( AccessMode mode ) } @Override - public InternalFuture acquireAsyncConnection( AccessMode mode ) + public CompletionStage acquireAsyncConnection( AccessMode mode ) { return asyncPool.acquire( address ); } @@ -62,7 +64,7 @@ public InternalFuture acquireAsyncConnection( AccessMode mode ) public void close() throws Exception { pool.close(); - asyncPool.closeAsync().syncUninterruptibly(); + Futures.getBlocking( asyncPool.closeAsync() ); } public BoltServerAddress getAddress() 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 527874e44f..e1eb995d26 100644 --- a/driver/src/main/java/org/neo4j/driver/internal/DriverFactory.java +++ b/driver/src/main/java/org/neo4j/driver/internal/DriverFactory.java @@ -28,6 +28,7 @@ import org.neo4j.driver.internal.async.AsyncConnectorImpl; import org.neo4j.driver.internal.async.BootstrapFactory; +import org.neo4j.driver.internal.async.Futures; import org.neo4j.driver.internal.async.pool.ActiveChannelTracker; import org.neo4j.driver.internal.async.pool.AsyncConnectionPool; import org.neo4j.driver.internal.async.pool.AsyncConnectionPoolImpl; @@ -85,7 +86,7 @@ public final Driver newInstance( URI uri, AuthToken authToken, RoutingSettings r try { return createDriver( uri, address, connectionPool, config, newRoutingSettings, securityPlan, retryLogic, - asyncConnectionPool, eventLoopGroup ); + asyncConnectionPool ); } catch ( Throwable driverError ) { @@ -93,7 +94,7 @@ public final Driver newInstance( URI uri, AuthToken authToken, RoutingSettings r try { connectionPool.close(); - asyncConnectionPool.closeAsync().syncUninterruptibly(); + Futures.getBlocking( asyncConnectionPool.closeAsync() ); } catch ( Throwable closeError ) { @@ -120,19 +121,17 @@ private AsyncConnectionPool createAsyncConnectionPool( AuthToken authToken, Secu } private Driver createDriver( URI uri, BoltServerAddress address, ConnectionPool connectionPool, - Config config, RoutingSettings routingSettings, SecurityPlan securityPlan, - RetryLogic retryLogic, AsyncConnectionPool asyncConnectionPool, EventExecutorGroup eventExecutorGroup ) + Config config, RoutingSettings routingSettings, SecurityPlan securityPlan, RetryLogic retryLogic, + AsyncConnectionPool asyncConnectionPool ) { String scheme = uri.getScheme().toLowerCase(); switch ( scheme ) { case BOLT_URI_SCHEME: assertNoRoutingContext( uri, routingSettings ); - return createDirectDriver( address, connectionPool, config, securityPlan, retryLogic, asyncConnectionPool, - eventExecutorGroup ); + return createDirectDriver( address, connectionPool, config, securityPlan, retryLogic, asyncConnectionPool ); case BOLT_ROUTING_URI_SCHEME: - return createRoutingDriver( address, connectionPool, config, routingSettings, securityPlan, retryLogic, - eventExecutorGroup ); + return createRoutingDriver( address, connectionPool, config, routingSettings, securityPlan, retryLogic ); default: throw new ClientException( format( "Unsupported URI scheme: %s", scheme ) ); } @@ -144,13 +143,12 @@ private Driver createDriver( URI uri, BoltServerAddress address, ConnectionPool * This method is protected only for testing */ protected Driver createDirectDriver( BoltServerAddress address, ConnectionPool connectionPool, Config config, - SecurityPlan securityPlan, RetryLogic retryLogic, AsyncConnectionPool asyncConnectionPool, - EventExecutorGroup eventExecutorGroup ) + SecurityPlan securityPlan, RetryLogic retryLogic, AsyncConnectionPool asyncConnectionPool ) { ConnectionProvider connectionProvider = new DirectConnectionProvider( address, connectionPool, asyncConnectionPool ); SessionFactory sessionFactory = - createSessionFactory( connectionProvider, retryLogic, eventExecutorGroup, config ); + createSessionFactory( connectionProvider, retryLogic, config ); return createDriver( config, securityPlan, sessionFactory ); } @@ -160,16 +158,14 @@ protected Driver createDirectDriver( BoltServerAddress address, ConnectionPool c * This method is protected only for testing */ protected Driver createRoutingDriver( BoltServerAddress address, ConnectionPool connectionPool, - Config config, RoutingSettings routingSettings, SecurityPlan securityPlan, RetryLogic retryLogic, - EventExecutorGroup eventExecutorGroup ) + Config config, RoutingSettings routingSettings, SecurityPlan securityPlan, RetryLogic retryLogic ) { if ( !securityPlan.isRoutingCompatible() ) { throw new IllegalArgumentException( "The chosen security plan is not compatible with a routing driver" ); } ConnectionProvider connectionProvider = createLoadBalancer( address, connectionPool, config, routingSettings ); - SessionFactory sessionFactory = - createSessionFactory( connectionProvider, retryLogic, eventExecutorGroup, config ); + SessionFactory sessionFactory = createSessionFactory( connectionProvider, retryLogic, config ); return createDriver( config, securityPlan, sessionFactory ); } @@ -251,9 +247,9 @@ protected Connector createConnector( final ConnectionSettings connectionSettings * This method is protected only for testing */ protected SessionFactory createSessionFactory( ConnectionProvider connectionProvider, RetryLogic retryLogic, - EventExecutorGroup eventExecutorGroup, Config config ) + Config config ) { - return new SessionFactoryImpl( connectionProvider, retryLogic, eventExecutorGroup, config ); + return new SessionFactoryImpl( connectionProvider, retryLogic, config ); } /** diff --git a/driver/src/main/java/org/neo4j/driver/internal/ExplicitTransaction.java b/driver/src/main/java/org/neo4j/driver/internal/ExplicitTransaction.java index 7551c88373..0ba43cf66d 100644 --- a/driver/src/main/java/org/neo4j/driver/internal/ExplicitTransaction.java +++ b/driver/src/main/java/org/neo4j/driver/internal/ExplicitTransaction.java @@ -20,11 +20,12 @@ import java.util.Collections; import java.util.Map; +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.CompletionStage; +import java.util.function.BiConsumer; import org.neo4j.driver.ResultResourcesHandler; import org.neo4j.driver.internal.async.AsyncConnection; -import org.neo4j.driver.internal.async.InternalFuture; -import org.neo4j.driver.internal.async.InternalPromise; import org.neo4j.driver.internal.async.QueryRunner; import org.neo4j.driver.internal.handlers.BeginTxResponseHandler; import org.neo4j.driver.internal.handlers.BookmarkResponseHandler; @@ -33,9 +34,7 @@ import org.neo4j.driver.internal.handlers.RollbackTxResponseHandler; import org.neo4j.driver.internal.spi.Connection; import org.neo4j.driver.internal.types.InternalTypeSystem; -import org.neo4j.driver.internal.util.BiConsumer; import org.neo4j.driver.v1.Record; -import org.neo4j.driver.v1.Response; import org.neo4j.driver.v1.Statement; import org.neo4j.driver.v1.StatementResult; import org.neo4j.driver.v1.StatementResultCursor; @@ -45,8 +44,9 @@ import org.neo4j.driver.v1.exceptions.ClientException; import org.neo4j.driver.v1.exceptions.Neo4jException; import org.neo4j.driver.v1.types.TypeSystem; -import org.neo4j.driver.v1.util.Function; +import static java.util.concurrent.CompletableFuture.completedFuture; +import static org.neo4j.driver.internal.async.Futures.failedFuture; import static org.neo4j.driver.internal.util.ErrorUtil.isRecoverable; import static org.neo4j.driver.v1.Values.ofValue; import static org.neo4j.driver.v1.Values.value; @@ -118,25 +118,23 @@ public void begin( Bookmark initialBookmark ) } } - public InternalFuture beginAsync( Bookmark initialBookmark ) + public CompletionStage beginAsync( Bookmark initialBookmark ) { - InternalPromise beginTxPromise = asyncConnection.newPromise(); - Map parameters = initialBookmark.asBeginTransactionParameters(); asyncConnection.run( BEGIN_QUERY, parameters, NoOpResponseHandler.INSTANCE ); if ( initialBookmark.isEmpty() ) { asyncConnection.pullAll( NoOpResponseHandler.INSTANCE ); - beginTxPromise.setSuccess( this ); + return completedFuture( this ); } else { - asyncConnection.pullAll( new BeginTxResponseHandler<>( beginTxPromise, this ) ); + CompletableFuture beginFuture = new CompletableFuture<>(); + asyncConnection.pullAll( new BeginTxResponseHandler<>( beginFuture, this ) ); asyncConnection.flush(); + return beginFuture; } - - return beginTxPromise; } @Override @@ -215,21 +213,15 @@ private void rollbackTx() } @Override - public Response commitAsync() - { - return internalCommitAsync(); - } - - InternalFuture internalCommitAsync() + public CompletionStage commitAsync() { if ( state == State.COMMITTED ) { - return asyncConnection.newPromise().setSuccess( null ); + return completedFuture( null ); } else if ( state == State.ROLLED_BACK ) { - return asyncConnection.newPromise().setFailure( - new ClientException( "Can't commit, transaction has already been rolled back" ) ); + return failedFuture( new ClientException( "Can't commit, transaction has already been rolled back" ) ); } else { @@ -238,21 +230,15 @@ else if ( state == State.ROLLED_BACK ) } @Override - public Response rollbackAsync() - { - return internalRollbackAsync(); - } - - InternalFuture internalRollbackAsync() + public CompletionStage rollbackAsync() { if ( state == State.COMMITTED ) { - return asyncConnection.newPromise() - .setFailure( new ClientException( "Can't rollback, transaction has already been committed" ) ); + return failedFuture( new ClientException( "Can't rollback, transaction has already been committed" ) ); } else if ( state == State.ROLLED_BACK ) { - return asyncConnection.newPromise().setSuccess( null ); + return completedFuture( null ); } else { @@ -262,51 +248,39 @@ else if ( state == State.ROLLED_BACK ) private BiConsumer releaseConnectionAndNotifySession() { - return new BiConsumer() + return ( ignore, error ) -> { - @Override - public void accept( Void result, Throwable error ) - { - asyncConnection.release(); - session.asyncTransactionClosed( ExplicitTransaction.this ); - } + asyncConnection.release(); + session.asyncTransactionClosed( ExplicitTransaction.this ); }; } - private InternalFuture doCommitAsync() + private CompletionStage doCommitAsync() { - InternalPromise commitTxPromise = asyncConnection.newPromise(); + CompletableFuture commitFuture = new CompletableFuture<>(); - asyncConnection.run( COMMIT_QUERY, Collections.emptyMap(), NoOpResponseHandler.INSTANCE ); - asyncConnection.pullAll( new CommitTxResponseHandler( commitTxPromise, this ) ); + asyncConnection.run( COMMIT_QUERY, Collections.emptyMap(), NoOpResponseHandler.INSTANCE ); + asyncConnection.pullAll( new CommitTxResponseHandler( commitFuture, this ) ); asyncConnection.flush(); - return commitTxPromise.thenApply( new Function() + return commitFuture.thenApply( ignore -> { - @Override - public Void apply( Void ignore ) - { - ExplicitTransaction.this.state = State.COMMITTED; - return null; - } + ExplicitTransaction.this.state = State.COMMITTED; + return null; } ); } - private InternalFuture doRollbackAsync() + private CompletionStage doRollbackAsync() { - InternalPromise rollbackTxPromise = asyncConnection.newPromise(); - asyncConnection.run( ROLLBACK_QUERY, Collections.emptyMap(), NoOpResponseHandler.INSTANCE ); - asyncConnection.pullAll( new RollbackTxResponseHandler( rollbackTxPromise ) ); + CompletableFuture rollbackFuture = new CompletableFuture<>(); + asyncConnection.run( ROLLBACK_QUERY, Collections.emptyMap(), NoOpResponseHandler.INSTANCE ); + asyncConnection.pullAll( new RollbackTxResponseHandler( rollbackFuture ) ); asyncConnection.flush(); - return rollbackTxPromise.thenApply( new Function() + return rollbackFuture.thenApply( ignore -> { - @Override - public Void apply( Void ignore ) - { - ExplicitTransaction.this.state = State.ROLLED_BACK; - return null; - } + ExplicitTransaction.this.state = State.ROLLED_BACK; + return null; } ); } @@ -317,7 +291,7 @@ public StatementResult run( String statementText, Value statementParameters ) } @Override - public Response runAsync( String statementText, Value parameters ) + public CompletionStage runAsync( String statementText, Value parameters ) { return runAsync( new Statement( statementText, parameters ) ); } @@ -329,7 +303,7 @@ public StatementResult run( String statementText ) } @Override - public Response runAsync( String statementTemplate ) + public CompletionStage runAsync( String statementTemplate ) { return runAsync( statementTemplate, Values.EmptyMap ); } @@ -342,7 +316,8 @@ public StatementResult run( String statementText, Map statementPa } @Override - public Response runAsync( String statementTemplate, Map statementParameters ) + public CompletionStage runAsync( String statementTemplate, + Map statementParameters ) { Value params = statementParameters == null ? Values.EmptyMap : value( statementParameters ); return runAsync( statementTemplate, params ); @@ -356,7 +331,7 @@ public StatementResult run( String statementTemplate, Record statementParameters } @Override - public Response runAsync( String statementTemplate, Record statementParameters ) + public CompletionStage runAsync( String statementTemplate, Record statementParameters ) { Value params = statementParameters == null ? Values.EmptyMap : value( statementParameters.asMap() ); return runAsync( statementTemplate, params ); @@ -388,7 +363,7 @@ public StatementResult run( Statement statement ) } @Override - public Response runAsync( Statement statement ) + public CompletionStage runAsync( Statement statement ) { ensureNotFailed(); return QueryRunner.runAsync( asyncConnection, statement, this ); diff --git a/driver/src/main/java/org/neo4j/driver/internal/LeakLoggingNetworkSession.java b/driver/src/main/java/org/neo4j/driver/internal/LeakLoggingNetworkSession.java index 63420be0e8..eeecf4f0fe 100644 --- a/driver/src/main/java/org/neo4j/driver/internal/LeakLoggingNetworkSession.java +++ b/driver/src/main/java/org/neo4j/driver/internal/LeakLoggingNetworkSession.java @@ -18,8 +18,6 @@ */ package org.neo4j.driver.internal; -import io.netty.util.concurrent.EventExecutorGroup; - import org.neo4j.driver.internal.retry.RetryLogic; import org.neo4j.driver.internal.spi.ConnectionProvider; import org.neo4j.driver.v1.AccessMode; @@ -32,9 +30,9 @@ class LeakLoggingNetworkSession extends NetworkSession private final String stackTrace; LeakLoggingNetworkSession( ConnectionProvider connectionProvider, AccessMode mode, RetryLogic retryLogic, - EventExecutorGroup eventExecutorGroup, Logging logging ) + Logging logging ) { - super( connectionProvider, mode, retryLogic, eventExecutorGroup, logging ); + super( connectionProvider, mode, retryLogic, logging ); this.stackTrace = captureStackTrace(); } diff --git a/driver/src/main/java/org/neo4j/driver/internal/NetworkSession.java b/driver/src/main/java/org/neo4j/driver/internal/NetworkSession.java index 4a39bab241..7a59ea4f08 100644 --- a/driver/src/main/java/org/neo4j/driver/internal/NetworkSession.java +++ b/driver/src/main/java/org/neo4j/driver/internal/NetworkSession.java @@ -18,17 +18,14 @@ */ package org.neo4j.driver.internal; -import io.netty.util.concurrent.EventExecutorGroup; -import io.netty.util.concurrent.Future; -import io.netty.util.concurrent.FutureListener; - import java.util.Map; +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.CompletionStage; import java.util.concurrent.atomic.AtomicBoolean; import org.neo4j.driver.ResultResourcesHandler; import org.neo4j.driver.internal.async.AsyncConnection; -import org.neo4j.driver.internal.async.InternalFuture; -import org.neo4j.driver.internal.async.InternalPromise; +import org.neo4j.driver.internal.async.Futures; import org.neo4j.driver.internal.async.QueryRunner; import org.neo4j.driver.internal.logging.DelegatingLogger; import org.neo4j.driver.internal.retry.RetryLogic; @@ -41,8 +38,6 @@ import org.neo4j.driver.v1.Logger; import org.neo4j.driver.v1.Logging; import org.neo4j.driver.v1.Record; -import org.neo4j.driver.v1.Response; -import org.neo4j.driver.v1.ResponseListener; import org.neo4j.driver.v1.Session; import org.neo4j.driver.v1.Statement; import org.neo4j.driver.v1.StatementResult; @@ -53,8 +48,8 @@ import org.neo4j.driver.v1.Values; import org.neo4j.driver.v1.exceptions.ClientException; import org.neo4j.driver.v1.types.TypeSystem; -import org.neo4j.driver.v1.util.Function; +import static java.util.concurrent.CompletableFuture.completedFuture; import static org.neo4j.driver.v1.Values.value; public class NetworkSession implements Session, SessionResourcesHandler, ResultResourcesHandler @@ -64,25 +59,23 @@ public class NetworkSession implements Session, SessionResourcesHandler, ResultR private final ConnectionProvider connectionProvider; private final AccessMode mode; private final RetryLogic retryLogic; - private final EventExecutorGroup eventExecutorGroup; protected final Logger logger; private volatile Bookmark bookmark = Bookmark.empty(); private PooledConnection currentConnection; private ExplicitTransaction currentTransaction; - private volatile InternalFuture currentAsyncTransactionFuture; + private volatile CompletionStage asyncTransactionStage; - private InternalFuture asyncConnectionFuture; + private CompletionStage asyncConnectionStage; private final AtomicBoolean isOpen = new AtomicBoolean( true ); public NetworkSession( ConnectionProvider connectionProvider, AccessMode mode, RetryLogic retryLogic, - EventExecutorGroup eventExecutorGroup, Logging logging ) + Logging logging ) { this.connectionProvider = connectionProvider; this.mode = mode; this.retryLogic = retryLogic; - this.eventExecutorGroup = eventExecutorGroup; this.logger = new DelegatingLogger( logging.getLog( LOG_NAME ), String.valueOf( hashCode() ) ); } @@ -93,7 +86,7 @@ public StatementResult run( String statementText ) } @Override - public Response runAsync( String statementText ) + public CompletionStage runAsync( String statementText ) { return runAsync( statementText, Values.EmptyMap ); } @@ -106,7 +99,8 @@ public StatementResult run( String statementText, Map statementPa } @Override - public Response runAsync( String statementText, Map statementParameters ) + public CompletionStage runAsync( String statementText, + Map statementParameters ) { Value params = statementParameters == null ? Values.EmptyMap : value( statementParameters ); return runAsync( statementText, params ); @@ -120,7 +114,7 @@ public StatementResult run( String statementTemplate, Record statementParameters } @Override - public Response runAsync( String statementTemplate, Record statementParameters ) + public CompletionStage runAsync( String statementTemplate, Record statementParameters ) { Value params = statementParameters == null ? Values.EmptyMap : value( statementParameters.asMap() ); return runAsync( statementTemplate, params ); @@ -133,7 +127,7 @@ public StatementResult run( String statementText, Value statementParameters ) } @Override - public Response runAsync( String statementText, Value parameters ) + public CompletionStage runAsync( String statementText, Value parameters ) { return runAsync( new Statement( statementText, parameters ) ); } @@ -151,21 +145,13 @@ public StatementResult run( Statement statement ) } @Override - public Response runAsync( final Statement statement ) + public CompletionStage runAsync( final Statement statement ) { ensureSessionIsOpen(); ensureNoOpenTransactionBeforeRunningSession(); - InternalFuture connectionFuture = acquireAsyncConnection( mode ); - - return connectionFuture.thenCompose( new Function>() - { - @Override - public InternalFuture apply( AsyncConnection connection ) - { - return QueryRunner.runAsync( connection, statement ); - } - } ); + return acquireAsyncConnection( mode ).thenCompose( connection -> + QueryRunner.runAsync( connection, statement ) ); } public static StatementResult run( Connection connection, Statement statement, @@ -232,7 +218,7 @@ public void close() try { - closeAsync().get(); + closeAsync().toCompletableFuture().get(); } catch ( Exception e ) { @@ -241,33 +227,19 @@ public void close() } @Override - public Response closeAsync() + public CompletionStage closeAsync() { - if ( asyncConnectionFuture != null ) + if ( asyncConnectionStage != null ) { - return asyncConnectionFuture.thenCompose( new Function>() - { - @Override - public InternalFuture apply( AsyncConnection connection ) - { - return connection.forceRelease(); - } - } ); + return asyncConnectionStage.thenCompose( AsyncConnection::forceRelease ); } - else if ( currentAsyncTransactionFuture != null ) + else if ( asyncTransactionStage != null ) { - return currentAsyncTransactionFuture.thenCompose( new Function>() - { - @Override - public InternalFuture apply( ExplicitTransaction tx ) - { - return tx.internalRollbackAsync(); - } - } ); + return asyncTransactionStage.thenCompose( ExplicitTransaction::rollbackAsync ); } else { - return new InternalPromise( eventExecutorGroup ).setSuccess( null ); + return completedFuture( null ); } } @@ -286,10 +258,10 @@ public synchronized Transaction beginTransaction( String bookmark ) } @Override - public Response beginTransactionAsync() + public CompletionStage beginTransactionAsync() { //noinspection unchecked - return (Response) beginTransactionAsync( mode ); + return (CompletionStage) beginTransactionAsync( mode ); } @Override @@ -299,7 +271,7 @@ public T readTransaction( TransactionWork work ) } @Override - public Response readTransactionAsync( TransactionWork> work ) + public CompletionStage readTransactionAsync( TransactionWork> work ) { return transactionAsync( AccessMode.READ, work ); } @@ -311,7 +283,7 @@ public T writeTransaction( TransactionWork work ) } @Override - public Response writeTransactionAsync( TransactionWork> work ) + public CompletionStage writeTransactionAsync( TransactionWork> work ) { return transactionAsync( AccessMode.WRITE, work ); } @@ -368,7 +340,7 @@ public synchronized void onTransactionClosed( ExplicitTransaction tx ) public void asyncTransactionClosed( ExplicitTransaction tx ) { setBookmark( tx.bookmark() ); - currentAsyncTransactionFuture = null; + asyncTransactionStage = null; } @Override @@ -415,63 +387,47 @@ public T get() } ); } - private InternalFuture transactionAsync( final AccessMode mode, final TransactionWork> work ) + private CompletionStage transactionAsync( AccessMode mode, TransactionWork> work ) { - return retryLogic.retryAsync( new Supplier>() + return retryLogic.retryAsync( () -> { - @Override - public InternalFuture get() - { - final InternalFuture txFuture = beginTransactionAsync( mode ); - final InternalPromise resultPromise = new InternalPromise<>( txFuture.eventExecutor() ); + CompletableFuture resultFuture = new CompletableFuture<>(); + CompletionStage txFuture = beginTransactionAsync( mode ); - txFuture.addListener( new FutureListener() + txFuture.whenComplete( ( tx, error ) -> + { + if ( error != null ) { - @Override - public void operationComplete( Future future ) throws Exception - { - if ( future.isCancelled() ) - { - resultPromise.cancel( true ); - } - else if ( future.isSuccess() ) - { - executeWork( resultPromise, future.getNow(), work ); - } - else - { - resultPromise.setFailure( future.cause() ); - } - } - } ); + resultFuture.completeExceptionally( error ); + } + else + { + executeWork( resultFuture, tx, work ); + } + } ); - return resultPromise; - } + return resultFuture; } ); } - private void executeWork( final InternalPromise resultPromise, final ExplicitTransaction tx, - TransactionWork> work ) + private void executeWork( CompletableFuture resultFuture, ExplicitTransaction tx, + TransactionWork> work ) { - Response workResponse = safeExecuteWork( tx, work ); - workResponse.addListener( new ResponseListener() + CompletionStage workFuture = safeExecuteWork( tx, work ); + workFuture.whenComplete( ( result, error ) -> { - @Override - public void operationCompleted( T result, Throwable error ) + if ( error != null ) { - if ( error != null ) - { - rollbackTxAfterFailedTransactionWork( tx, resultPromise, error ); - } - else - { - commitTxAfterSucceededTransactionWork( tx, resultPromise, result ); - } + rollbackTxAfterFailedTransactionWork( tx, resultFuture, error ); + } + else + { + commitTxAfterSucceededTransactionWork( tx, resultFuture, result ); } } ); } - private Response safeExecuteWork( ExplicitTransaction tx, TransactionWork> work ) + private CompletionStage safeExecuteWork( ExplicitTransaction tx, TransactionWork> work ) { // given work might fail in both async and sync way // async failure will result in a failed future being returned @@ -483,58 +439,50 @@ private Response safeExecuteWork( ExplicitTransaction tx, TransactionWork catch ( Throwable workError ) { // work threw an exception, wrap it in a future and proceed - return new InternalPromise( eventExecutorGroup ).setFailure( workError ); + return Futures.failedFuture( workError ); } } - private void rollbackTxAfterFailedTransactionWork( ExplicitTransaction tx, - final InternalPromise resultPromise, final Throwable error ) + private void rollbackTxAfterFailedTransactionWork( ExplicitTransaction tx, CompletableFuture resultFuture, + Throwable error ) { if ( tx.isOpen() ) { - tx.rollbackAsync().addListener( new ResponseListener() + tx.rollbackAsync().whenComplete( ( ignore, rollbackError ) -> { - @Override - public void operationCompleted( Void ignore, Throwable rollbackError ) + if ( rollbackError != null ) { - if ( rollbackError != null ) - { - error.addSuppressed( rollbackError ); - } - resultPromise.setFailure( error ); + error.addSuppressed( rollbackError ); } + resultFuture.completeExceptionally( error ); } ); } else { - resultPromise.setFailure( error ); + resultFuture.completeExceptionally( error ); } } - private void commitTxAfterSucceededTransactionWork( ExplicitTransaction tx, - final InternalPromise resultPromise, final T result ) + private void commitTxAfterSucceededTransactionWork( ExplicitTransaction tx, CompletableFuture resultFuture, + T result ) { if ( tx.isOpen() ) { - tx.commitAsync().addListener( new ResponseListener() + tx.commitAsync().whenComplete( ( ignore, commitError ) -> { - @Override - public void operationCompleted( Void ignore, Throwable commitError ) + if ( commitError != null ) { - if ( commitError != null ) - { - resultPromise.setFailure( commitError ); - } - else - { - resultPromise.setSuccess( result ); - } + resultFuture.completeExceptionally( commitError ); + } + else + { + resultFuture.complete( result ); } } ); } else { - resultPromise.setSuccess( result ); + resultFuture.complete( result ); } } @@ -553,25 +501,18 @@ private synchronized Transaction beginTransaction( AccessMode mode ) return currentTransaction; } - private synchronized InternalFuture beginTransactionAsync( AccessMode mode ) + private synchronized CompletionStage beginTransactionAsync( AccessMode mode ) { ensureSessionIsOpen(); ensureNoOpenTransactionBeforeOpeningTransaction(); - InternalFuture connectionFuture = acquireAsyncConnection( mode ); - - currentAsyncTransactionFuture = connectionFuture.thenCompose( - new Function>() - { - @Override - public InternalFuture apply( AsyncConnection connection ) - { - ExplicitTransaction tx = new ExplicitTransaction( connection, NetworkSession.this ); - return tx.beginAsync( bookmark ); - } - } ); + asyncTransactionStage = acquireAsyncConnection( mode ).thenCompose( connection -> + { + ExplicitTransaction tx = new ExplicitTransaction( connection, NetworkSession.this ); + return tx.beginAsync( bookmark ); + } ); - return currentAsyncTransactionFuture; + return asyncTransactionStage; } private void ensureNoUnrecoverableError() @@ -587,7 +528,7 @@ private void ensureNoUnrecoverableError() //should be called from a synchronized block private void ensureNoOpenTransactionBeforeRunningSession() { - if ( currentTransaction != null || currentAsyncTransactionFuture != null ) + if ( currentTransaction != null || asyncTransactionStage != null ) { throw new ClientException( "Statements cannot be run directly on a session with an open transaction;" + " either run from within the transaction or use a different session." ); @@ -597,7 +538,7 @@ private void ensureNoOpenTransactionBeforeRunningSession() //should be called from a synchronized block private void ensureNoOpenTransactionBeforeOpeningTransaction() { - if ( currentTransaction != null || currentAsyncTransactionFuture != null ) + if ( currentTransaction != null || asyncTransactionStage != null ) { throw new ClientException( "You cannot begin a transaction on a session with an open transaction;" + " either run from within the transaction or use a different session." ); @@ -624,36 +565,31 @@ private PooledConnection acquireConnection( AccessMode mode ) return connection; } - private InternalFuture acquireAsyncConnection( final AccessMode mode ) + private CompletionStage acquireAsyncConnection( final AccessMode mode ) { - if ( asyncConnectionFuture == null ) + if ( asyncConnectionStage == null ) { - asyncConnectionFuture = connectionProvider.acquireAsyncConnection( mode ); + asyncConnectionStage = connectionProvider.acquireAsyncConnection( mode ); } else { // memorize in local so same instance is transformed and used in callbacks - final InternalFuture currentAsyncConnectionFuture = asyncConnectionFuture; + CompletionStage currentAsyncConnectionStage = asyncConnectionStage; - asyncConnectionFuture = currentAsyncConnectionFuture.thenCompose( - new Function>() - { - @Override - public InternalFuture apply( AsyncConnection connection ) - { - if ( connection.tryMarkInUse() ) - { - return currentAsyncConnectionFuture; - } - else - { - return connectionProvider.acquireAsyncConnection( mode ); - } - } - } ); + asyncConnectionStage = currentAsyncConnectionStage.thenCompose( connection -> + { + if ( connection.tryMarkInUse() ) + { + return currentAsyncConnectionStage; + } + else + { + return connectionProvider.acquireAsyncConnection( mode ); + } + } ); } - return asyncConnectionFuture; + return asyncConnectionStage; } boolean currentConnectionIsOpen() 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 cc9c7050ac..a9070c7911 100644 --- a/driver/src/main/java/org/neo4j/driver/internal/SessionFactoryImpl.java +++ b/driver/src/main/java/org/neo4j/driver/internal/SessionFactoryImpl.java @@ -18,8 +18,6 @@ */ package org.neo4j.driver.internal; -import io.netty.util.concurrent.EventExecutorGroup; - import org.neo4j.driver.internal.retry.RetryLogic; import org.neo4j.driver.internal.spi.ConnectionProvider; import org.neo4j.driver.v1.AccessMode; @@ -31,15 +29,12 @@ public class SessionFactoryImpl implements SessionFactory { private final ConnectionProvider connectionProvider; private final RetryLogic retryLogic; - private final EventExecutorGroup eventExecutorGroup; private final Logging logging; private final boolean leakedSessionsLoggingEnabled; - SessionFactoryImpl( ConnectionProvider connectionProvider, RetryLogic retryLogic, - EventExecutorGroup eventExecutorGroup, Config config ) + SessionFactoryImpl( ConnectionProvider connectionProvider, RetryLogic retryLogic, Config config ) { this.connectionProvider = connectionProvider; - this.eventExecutorGroup = eventExecutorGroup; this.leakedSessionsLoggingEnabled = config.logLeakedSessions(); this.retryLogic = retryLogic; this.logging = config.logging(); @@ -57,8 +52,8 @@ protected NetworkSession createSession( ConnectionProvider connectionProvider, R AccessMode mode, Logging logging ) { return leakedSessionsLoggingEnabled - ? new LeakLoggingNetworkSession( connectionProvider, mode, retryLogic, eventExecutorGroup, logging ) - : new NetworkSession( connectionProvider, mode, retryLogic, eventExecutorGroup, logging ); + ? new LeakLoggingNetworkSession( connectionProvider, mode, retryLogic, logging ) + : new NetworkSession( connectionProvider, mode, retryLogic, logging ); } @Override diff --git a/driver/src/main/java/org/neo4j/driver/internal/async/AsyncConnection.java b/driver/src/main/java/org/neo4j/driver/internal/async/AsyncConnection.java index a5f75a59c0..79b8b883e9 100644 --- a/driver/src/main/java/org/neo4j/driver/internal/async/AsyncConnection.java +++ b/driver/src/main/java/org/neo4j/driver/internal/async/AsyncConnection.java @@ -19,6 +19,7 @@ package org.neo4j.driver.internal.async; import java.util.Map; +import java.util.concurrent.CompletionStage; import org.neo4j.driver.internal.spi.ResponseHandler; import org.neo4j.driver.v1.Value; @@ -38,11 +39,9 @@ public interface AsyncConnection void flush(); - InternalPromise newPromise(); - void release(); - InternalFuture forceRelease(); + CompletionStage forceRelease(); ServerInfo serverInfo(); } diff --git a/driver/src/main/java/org/neo4j/driver/internal/async/Futures.java b/driver/src/main/java/org/neo4j/driver/internal/async/Futures.java index 04b0240eee..93648a0eb8 100644 --- a/driver/src/main/java/org/neo4j/driver/internal/async/Futures.java +++ b/driver/src/main/java/org/neo4j/driver/internal/async/Futures.java @@ -18,13 +18,12 @@ */ package org.neo4j.driver.internal.async; -import io.netty.bootstrap.Bootstrap; -import io.netty.util.concurrent.Future; -import io.netty.util.concurrent.GenericFutureListener; -import io.netty.util.concurrent.Promise; +import io.netty.util.internal.PlatformDependent; -import org.neo4j.driver.internal.util.BiConsumer; -import org.neo4j.driver.v1.util.Function; +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.CompletionStage; +import java.util.concurrent.ExecutionException; +import java.util.concurrent.Future; public final class Futures { @@ -32,182 +31,77 @@ private Futures() { } - public static InternalFuture thenApply( InternalFuture future, Function fn ) + public static CompletionStage asCompletionStage( io.netty.util.concurrent.Future future ) { - InternalPromise result = new InternalPromise<>( future.eventExecutor() ); - future.addListener( new ThenApplyListener<>( result, fn ) ); - return result; - } - - public static InternalFuture thenApply( Future future, Bootstrap bootstrap, Function fn ) - { - InternalPromise result = new InternalPromise<>( bootstrap ); - future.addListener( new ThenApplyListener<>( result, fn ) ); - return result; - } - - public static InternalFuture thenCompose( InternalFuture future, Function> fn ) - { - InternalPromise result = new InternalPromise<>( future.eventExecutor() ); - future.addListener( new ThenComposeListener<>( result, fn ) ); - return result; - } - - public static InternalFuture whenComplete( InternalFuture future, BiConsumer action ) - { - InternalPromise result = new InternalPromise<>( future.eventExecutor() ); - future.addListener( new CompletionListener<>( result, action ) ); - return result; - } - - private static class ThenApplyListener implements GenericFutureListener> - { - final Promise result; - final Function fn; - - ThenApplyListener( Promise result, Function fn ) - { - this.result = result; - this.fn = fn; - } - - @Override - public void operationComplete( Future future ) throws Exception + CompletableFuture result = new CompletableFuture<>(); + if ( future.isCancelled() ) { - if ( future.isCancelled() ) - { - result.cancel( true ); - } - else if ( future.isSuccess() ) - { - try - { - T originalValue = future.getNow(); - U newValue = fn.apply( originalValue ); - result.setSuccess( newValue ); - } - catch ( Throwable t ) - { - result.setFailure( t ); - } - } - else - { - result.setFailure( future.cause() ); - } + result.cancel( true ); } - } - - private static class ThenComposeListener implements GenericFutureListener> - { - final Promise result; - final Function> fn; - - ThenComposeListener( Promise result, Function> fn ) + else if ( future.isSuccess() ) { - this.result = result; - this.fn = fn; + result.complete( future.getNow() ); } - - @Override - public void operationComplete( Future future ) throws Exception + else { - if ( future.isCancelled() ) - { - result.cancel( true ); - } - else if ( future.isSuccess() ) + future.addListener( ignore -> { - try + if ( future.isCancelled() ) { - T value = future.getNow(); - InternalFuture newFuture = fn.apply( value ); - newFuture.addListener( new NestedThenCombineListener<>( result, newFuture ) ); + result.cancel( true ); } - catch ( Throwable t ) + else if ( future.isSuccess() ) { - result.setFailure( t ); + result.complete( future.getNow() ); } - } - else - { - result.setFailure( future.cause() ); - } + else + { + result.completeExceptionally( future.cause() ); + } + } ); } + return result; } - private static class NestedThenCombineListener implements GenericFutureListener> + public static CompletableFuture failedFuture( Throwable error ) { - - final Promise result; - final Future future; - - NestedThenCombineListener( Promise result, Future future ) - { - this.result = result; - this.future = future; - } - - @Override - public void operationComplete( Future future ) throws Exception - { - if ( future.isCancelled() ) - { - result.cancel( true ); - } - else if ( future.isSuccess() ) - { - result.setSuccess( future.getNow() ); - } - else - { - result.setFailure( future.cause() ); - } - } + CompletableFuture result = new CompletableFuture<>(); + result.completeExceptionally( error ); + return result; } - private static class CompletionListener implements GenericFutureListener> + public static V getBlocking( CompletionStage stage ) { - final Promise result; - final BiConsumer action; - - CompletionListener( Promise result, BiConsumer action ) - { - this.result = result; - this.action = action; - } + Future future = stage.toCompletableFuture(); + return getBlocking( future ); + } - @Override - public void operationComplete( Future future ) throws Exception + public static V getBlocking( Future future ) + { + boolean interrupted = false; + try { - if ( future.isCancelled() ) - { - result.cancel( true ); - } - else if ( future.isSuccess() ) + while ( true ) { try { - action.accept( future.getNow(), null ); - result.setSuccess( future.getNow() ); + return future.get(); } - catch ( Throwable t ) + catch ( InterruptedException e ) { - result.setFailure( t ); + interrupted = true; } - } - else - { - Throwable error = future.cause(); - try - { - action.accept( null, error ); - } - catch ( Throwable t ) + catch ( ExecutionException e ) { - error.addSuppressed( t ); + PlatformDependent.throwException( e.getCause() ); } - result.setFailure( error ); + } + } + finally + { + if ( interrupted ) + { + Thread.currentThread().interrupt(); } } } diff --git a/driver/src/main/java/org/neo4j/driver/internal/async/InternalPromise.java b/driver/src/main/java/org/neo4j/driver/internal/async/InternalPromise.java deleted file mode 100644 index 6a48065b4e..0000000000 --- a/driver/src/main/java/org/neo4j/driver/internal/async/InternalPromise.java +++ /dev/null @@ -1,267 +0,0 @@ -/* - * Copyright (c) 2002-2017 "Neo Technology," - * Network Engine for Objects in Lund AB [http://neotechnology.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 io.netty.bootstrap.Bootstrap; -import io.netty.util.concurrent.EventExecutor; -import io.netty.util.concurrent.EventExecutorGroup; -import io.netty.util.concurrent.Future; -import io.netty.util.concurrent.FutureListener; -import io.netty.util.concurrent.GenericFutureListener; -import io.netty.util.concurrent.Promise; - -import java.util.concurrent.ExecutionException; -import java.util.concurrent.TimeUnit; -import java.util.concurrent.TimeoutException; - -import org.neo4j.driver.internal.util.BiConsumer; -import org.neo4j.driver.v1.ResponseListener; -import org.neo4j.driver.v1.util.Function; - -public class InternalPromise implements InternalFuture, Promise -{ - private final EventExecutor eventExecutor; - private final Promise delegate; - - public InternalPromise( Bootstrap bootstrap ) - { - this( bootstrap.config().group() ); - } - - public InternalPromise( EventExecutorGroup eventExecutorGroup ) - { - this( eventExecutorGroup.next() ); - } - - public InternalPromise( EventExecutor eventExecutor ) - { - this.eventExecutor = eventExecutor; - this.delegate = eventExecutor.newPromise(); - } - - @Override - public EventExecutor eventExecutor() - { - return eventExecutor; - } - - @Override - public InternalFuture thenApply( Function fn ) - { - return Futures.thenApply( this, fn ); - } - - @Override - public InternalFuture thenCompose( Function> fn ) - { - return Futures.thenCompose( this, fn ); - } - - @Override - public InternalFuture whenComplete( BiConsumer action ) - { - return Futures.whenComplete( this, action ); - } - - @Override - public void addListener( final ResponseListener listener ) - { - delegate.addListener( new FutureListener() - { - @Override - public void operationComplete( Future future ) - { - if ( future.isSuccess() ) - { - listener.operationCompleted( future.getNow(), null ); - } - else - { - listener.operationCompleted( null, future.cause() ); - } - } - } ); - } - - @Override - public InternalPromise setSuccess( T result ) - { - delegate.setSuccess( result ); - return this; - } - - @Override - public boolean trySuccess( T result ) - { - return delegate.trySuccess( result ); - } - - @Override - public InternalPromise setFailure( Throwable cause ) - { - delegate.setFailure( cause ); - return this; - } - - @Override - public boolean tryFailure( Throwable cause ) - { - return delegate.tryFailure( cause ); - } - - @Override - public boolean setUncancellable() - { - return delegate.setUncancellable(); - } - - @Override - public InternalPromise addListener( GenericFutureListener> listener ) - { - delegate.addListener( listener ); - return this; - } - - @Override - public InternalPromise addListeners( GenericFutureListener>... listeners ) - { - delegate.addListeners( listeners ); - return this; - } - - @Override - public InternalPromise removeListener( GenericFutureListener> listener ) - { - delegate.removeListener( listener ); - return this; - } - - @Override - public InternalPromise removeListeners( GenericFutureListener>... listeners ) - { - delegate.removeListeners( listeners ); - return this; - } - - @Override - public InternalPromise await() throws InterruptedException - { - delegate.await(); - return this; - } - - @Override - public InternalPromise awaitUninterruptibly() - { - delegate.awaitUninterruptibly(); - return this; - } - - @Override - public InternalPromise sync() throws InterruptedException - { - delegate.sync(); - return this; - } - - @Override - public InternalPromise syncUninterruptibly() - { - delegate.syncUninterruptibly(); - return this; - } - - @Override - public boolean isSuccess() - { - return delegate.isSuccess(); - } - - @Override - public boolean isCancellable() - { - return delegate.isCancellable(); - } - - @Override - public Throwable cause() - { - return delegate.cause(); - } - - @Override - public boolean await( long timeout, TimeUnit unit ) throws InterruptedException - { - return delegate.await( timeout, unit ); - } - - @Override - public boolean await( long timeoutMillis ) throws InterruptedException - { - return delegate.await( timeoutMillis ); - } - - @Override - public boolean awaitUninterruptibly( long timeout, TimeUnit unit ) - { - return delegate.awaitUninterruptibly( timeout, unit ); - } - - @Override - public boolean awaitUninterruptibly( long timeoutMillis ) - { - return delegate.awaitUninterruptibly( timeoutMillis ); - } - - @Override - public T getNow() - { - return delegate.getNow(); - } - - @Override - public boolean cancel( boolean mayInterruptIfRunning ) - { - return delegate.cancel( mayInterruptIfRunning ); - } - - @Override - public boolean isCancelled() - { - return delegate.isCancelled(); - } - - @Override - public boolean isDone() - { - return delegate.isDone(); - } - - @Override - public T get() throws InterruptedException, ExecutionException - { - return delegate.get(); - } - - @Override - public T get( long timeout, TimeUnit unit ) throws InterruptedException, ExecutionException, TimeoutException - { - return delegate.get( timeout, unit ); - } -} diff --git a/driver/src/main/java/org/neo4j/driver/internal/async/InternalStatementResultCursor.java b/driver/src/main/java/org/neo4j/driver/internal/async/InternalStatementResultCursor.java index bb2cd5ed4b..004ddceafb 100644 --- a/driver/src/main/java/org/neo4j/driver/internal/async/InternalStatementResultCursor.java +++ b/driver/src/main/java/org/neo4j/driver/internal/async/InternalStatementResultCursor.java @@ -18,18 +18,16 @@ */ package org.neo4j.driver.internal.async; -import io.netty.util.concurrent.Future; -import io.netty.util.concurrent.FutureListener; - import java.util.ArrayList; import java.util.Collections; import java.util.List; +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.CompletionStage; +import java.util.function.Consumer; import org.neo4j.driver.internal.handlers.PullAllResponseHandler; import org.neo4j.driver.internal.handlers.RunResponseHandler; -import org.neo4j.driver.internal.util.Consumer; import org.neo4j.driver.v1.Record; -import org.neo4j.driver.v1.Response; import org.neo4j.driver.v1.StatementResultCursor; import org.neo4j.driver.v1.summary.ResultSummary; @@ -37,16 +35,13 @@ public class InternalStatementResultCursor implements StatementResultCursor { - private final AsyncConnection connection; private final RunResponseHandler runResponseHandler; private final PullAllResponseHandler pullAllHandler; - private InternalFuture peekedRecordResponse; + private CompletionStage peekedRecordFuture; - public InternalStatementResultCursor( AsyncConnection connection, RunResponseHandler runResponseHandler, - PullAllResponseHandler pullAllHandler ) + public InternalStatementResultCursor( RunResponseHandler runResponseHandler, PullAllResponseHandler pullAllHandler ) { - this.connection = requireNonNull( connection ); this.runResponseHandler = requireNonNull( runResponseHandler ); this.pullAllHandler = requireNonNull( pullAllHandler ); } @@ -55,126 +50,101 @@ public InternalStatementResultCursor( AsyncConnection connection, RunResponseHan public List keys() { List keys = runResponseHandler.statementKeys(); - return keys == null ? Collections.emptyList() : Collections.unmodifiableList( keys ); + return keys == null ? Collections.emptyList() : Collections.unmodifiableList( keys ); } @Override - public Response summaryAsync() + public CompletionStage summaryAsync() { return pullAllHandler.summaryAsync(); } @Override - public Response nextAsync() + public CompletionStage nextAsync() { - return internalNextAsync(); + if ( peekedRecordFuture != null ) + { + CompletionStage result = peekedRecordFuture; + peekedRecordFuture = null; + return result; + } + else + { + return pullAllHandler.nextAsync(); + } } @Override - public Response peekAsync() + public CompletionStage peekAsync() { - if ( peekedRecordResponse == null ) + if ( peekedRecordFuture == null ) { - peekedRecordResponse = pullAllHandler.nextAsync(); + peekedRecordFuture = pullAllHandler.nextAsync(); } - return peekedRecordResponse; + return peekedRecordFuture; } @Override - public Response forEachAsync( final Consumer action ) + public CompletionStage forEachAsync( Consumer action ) { - InternalPromise result = connection.newPromise(); - internalForEachAsync( action, result ); - return result; + CompletableFuture resultFuture = new CompletableFuture<>(); + internalForEachAsync( action, resultFuture ); + return resultFuture; } @Override - public Response> listAsync() + public CompletionStage> listAsync() { - InternalPromise> result = connection.newPromise(); - internalListAsync( new ArrayList(), result ); - return result; + CompletableFuture> resultFuture = new CompletableFuture<>(); + internalListAsync( new ArrayList<>(), resultFuture ); + return resultFuture; } - private void internalForEachAsync( final Consumer action, final InternalPromise result ) + private void internalForEachAsync( Consumer action, CompletableFuture resultFuture ) { - final InternalFuture recordFuture = internalNextAsync(); + CompletionStage recordFuture = nextAsync(); - recordFuture.addListener( new FutureListener() + // use async completion listener because of recursion, otherwise it is possible for + // the caller thread to get StackOverflowError when result is large and buffered + recordFuture.whenCompleteAsync( ( record, error ) -> { - @Override - public void operationComplete( Future future ) + if ( error != null ) + { + resultFuture.completeExceptionally( error ); + } + else if ( record != null ) { - if ( future.isCancelled() ) - { - result.cancel( true ); - } - else if ( future.isSuccess() ) - { - Record record = future.getNow(); - if ( record != null ) - { - action.accept( record ); - internalForEachAsync( action, result ); - } - else - { - result.setSuccess( null ); - } - } - else - { - result.setFailure( future.cause() ); - } + action.accept( record ); + internalForEachAsync( action, resultFuture ); + } + else + { + resultFuture.complete( null ); } } ); } - private void internalListAsync( final List records, final InternalPromise> result ) + private void internalListAsync( List records, CompletableFuture> resultFuture ) { - final InternalFuture recordFuture = internalNextAsync(); + CompletionStage recordFuture = nextAsync(); - recordFuture.addListener( new FutureListener() + // use async completion listener because of recursion, otherwise it is possible for + // the caller thread to get StackOverflowError when result is large and buffered + recordFuture.whenCompleteAsync( ( record, error ) -> { - @Override - public void operationComplete( Future future ) + if ( error != null ) + { + resultFuture.completeExceptionally( error ); + } + else if ( record != null ) + { + records.add( record ); + internalListAsync( records, resultFuture ); + } + else { - if ( future.isCancelled() ) - { - result.cancel( true ); - } - else if ( future.isSuccess() ) - { - Record record = future.getNow(); - if ( record != null ) - { - records.add( record ); - internalListAsync( records, result ); - } - else - { - result.setSuccess( records ); - } - } - else - { - result.setFailure( future.cause() ); - } + resultFuture.complete( records ); } } ); } - - private InternalFuture internalNextAsync() - { - if ( peekedRecordResponse != null ) - { - InternalFuture result = peekedRecordResponse; - peekedRecordResponse = null; - return result; - } - else - { - return pullAllHandler.nextAsync(); - } - } } diff --git a/driver/src/main/java/org/neo4j/driver/internal/async/Main.java b/driver/src/main/java/org/neo4j/driver/internal/async/Main.java index 225b1a8e46..1006f5267e 100644 --- a/driver/src/main/java/org/neo4j/driver/internal/async/Main.java +++ b/driver/src/main/java/org/neo4j/driver/internal/async/Main.java @@ -23,7 +23,7 @@ import java.util.HashMap; import java.util.List; import java.util.Map; -import java.util.concurrent.Future; +import java.util.concurrent.CompletionStage; import java.util.concurrent.TimeUnit; import org.neo4j.driver.v1.AuthToken; @@ -32,7 +32,6 @@ import org.neo4j.driver.v1.Driver; import org.neo4j.driver.v1.GraphDatabase; import org.neo4j.driver.v1.Record; -import org.neo4j.driver.v1.Response; import org.neo4j.driver.v1.Session; import org.neo4j.driver.v1.StatementResult; import org.neo4j.driver.v1.StatementResultCursor; @@ -156,8 +155,8 @@ private static void testSessionRunAsync() throws Throwable public void apply( Driver driver, MutableInt recordsRead ) { Session session = driver.session(); - Response cursorResponse = session.runAsync( QUERY, PARAMS_OBJ ); - StatementResultCursor cursor = await( cursorResponse ); + CompletionStage cursorFuture = session.runAsync( QUERY, PARAMS_OBJ ); + StatementResultCursor cursor = await( cursorFuture ); Record record; while ( (record = await( cursor.nextAsync() )) != null ) { @@ -273,11 +272,11 @@ private static double stdDev( List timings ) return (Math.sqrt( squaredDiffMean )); } - private static > T await( U future ) + private static T await( CompletionStage stage ) { try { - return future.get(); + return stage.toCompletableFuture().get(); } catch ( Throwable t ) { diff --git a/driver/src/main/java/org/neo4j/driver/internal/async/NettyChannelInitializer.java b/driver/src/main/java/org/neo4j/driver/internal/async/NettyChannelInitializer.java index 8f4b1c5a67..2d62851e56 100644 --- a/driver/src/main/java/org/neo4j/driver/internal/async/NettyChannelInitializer.java +++ b/driver/src/main/java/org/neo4j/driver/internal/async/NettyChannelInitializer.java @@ -25,7 +25,6 @@ import javax.net.ssl.SSLContext; import javax.net.ssl.SSLEngine; -import javax.net.ssl.SSLException; import org.neo4j.driver.internal.async.inbound.InboundMessageDispatcher; import org.neo4j.driver.internal.net.BoltServerAddress; @@ -67,13 +66,13 @@ protected void initChannel( Channel channel ) throws Exception channelPoolHandler.channelCreated( channel ); } - private SslHandler createSslHandler() throws SSLException + private SslHandler createSslHandler() { SSLEngine sslEngine = createSslEngine(); return new SslHandler( sslEngine ); } - private SSLEngine createSslEngine() throws SSLException + private SSLEngine createSslEngine() { SSLContext sslContext = securityPlan.sslContext(); SSLEngine sslEngine = sslContext.createSSLEngine( address.host(), address.port() ); diff --git a/driver/src/main/java/org/neo4j/driver/internal/async/NettyConnection.java b/driver/src/main/java/org/neo4j/driver/internal/async/NettyConnection.java index ebe3ab5d7c..babf98f88a 100644 --- a/driver/src/main/java/org/neo4j/driver/internal/async/NettyConnection.java +++ b/driver/src/main/java/org/neo4j/driver/internal/async/NettyConnection.java @@ -20,8 +20,10 @@ import io.netty.channel.Channel; import io.netty.channel.pool.ChannelPool; +import io.netty.util.concurrent.Promise; import java.util.Map; +import java.util.concurrent.CompletionStage; import java.util.concurrent.atomic.AtomicBoolean; import org.neo4j.driver.internal.async.inbound.InboundMessageDispatcher; @@ -35,9 +37,11 @@ import org.neo4j.driver.v1.Value; import org.neo4j.driver.v1.summary.ServerInfo; +import static java.util.concurrent.CompletableFuture.completedFuture; import static org.neo4j.driver.internal.async.ChannelAttributes.address; import static org.neo4j.driver.internal.async.ChannelAttributes.messageDispatcher; import static org.neo4j.driver.internal.async.ChannelAttributes.serverVersion; +import static org.neo4j.driver.internal.async.Futures.asCompletionStage; // todo: keep state flags to prohibit interaction with released connections public class NettyConnection implements AsyncConnection @@ -103,12 +107,6 @@ public void flush() channel.flush(); } - @Override - public InternalPromise newPromise() - { - return new InternalPromise<>( channel.eventLoop() ); - } - @Override public void release() { @@ -119,20 +117,18 @@ public void release() } @Override - public InternalFuture forceRelease() + public CompletionStage forceRelease() { - InternalPromise releasePromise = newPromise(); - if ( state.forceRelease() ) { + Promise releasePromise = channel.eventLoop().newPromise(); write( ResetMessage.RESET, new ReleaseChannelHandler( channel, channelPool, clock, releasePromise ), true ); + return asCompletionStage( releasePromise ); } else { - releasePromise.setSuccess( null ); + return completedFuture( null ); } - - return releasePromise; } @Override diff --git a/driver/src/main/java/org/neo4j/driver/internal/async/QueryRunner.java b/driver/src/main/java/org/neo4j/driver/internal/async/QueryRunner.java index d08e3fc3f2..276a1d4b7f 100644 --- a/driver/src/main/java/org/neo4j/driver/internal/async/QueryRunner.java +++ b/driver/src/main/java/org/neo4j/driver/internal/async/QueryRunner.java @@ -19,6 +19,8 @@ package org.neo4j.driver.internal.async; import java.util.Map; +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.CompletionStage; import org.neo4j.driver.internal.ExplicitTransaction; import org.neo4j.driver.internal.handlers.PullAllResponseHandler; @@ -28,7 +30,6 @@ import org.neo4j.driver.v1.Statement; import org.neo4j.driver.v1.StatementResultCursor; import org.neo4j.driver.v1.Value; -import org.neo4j.driver.v1.util.Function; import static org.neo4j.driver.v1.Values.ofValue; @@ -38,33 +39,27 @@ private QueryRunner() { } - public static InternalFuture runAsync( AsyncConnection connection, Statement statement ) + public static CompletionStage runAsync( AsyncConnection connection, Statement statement ) { return runAsync( connection, statement, null ); } - public static InternalFuture runAsync( final AsyncConnection connection, Statement statement, + public static CompletionStage runAsync( AsyncConnection connection, Statement statement, ExplicitTransaction tx ) { String query = statement.text(); Map params = statement.parameters().asMap( ofValue() ); - InternalPromise runCompletedPromise = connection.newPromise(); - final RunResponseHandler runHandler = new RunResponseHandler( runCompletedPromise, tx ); - final PullAllResponseHandler pullAllHandler = newPullAllHandler( statement, runHandler, connection, tx ); + CompletableFuture runCompletedFuture = new CompletableFuture<>(); + RunResponseHandler runHandler = new RunResponseHandler( runCompletedFuture, tx ); + PullAllResponseHandler pullAllHandler = newPullAllHandler( statement, runHandler, connection, tx ); connection.run( query, params, runHandler ); connection.pullAll( pullAllHandler ); connection.flush(); - return runCompletedPromise.thenApply( new Function() - { - @Override - public StatementResultCursor apply( Void ignore ) - { - return new InternalStatementResultCursor( connection, runHandler, pullAllHandler ); - } - } ); + return runCompletedFuture.thenApply( ignore -> + new InternalStatementResultCursor( runHandler, pullAllHandler ) ); } private static PullAllResponseHandler newPullAllHandler( Statement statement, RunResponseHandler runHandler, 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 809ad2e416..bf5e8282a3 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 @@ -56,8 +56,14 @@ public void handlerAdded( ChannelHandlerContext ctx ) protected void channelRead0( ChannelHandlerContext ctx, ByteBuf msg ) throws IOException { input.start( msg ); - reader.read( messageDispatcher ); - input.stop(); + try + { + reader.read( messageDispatcher ); + } + finally + { + input.stop(); + } } @Override 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 16a3a49f62..5c0b229fc8 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 @@ -60,8 +60,14 @@ protected void encode( ChannelHandlerContext ctx, Message msg, List out ByteBuf messageBuf = ctx.alloc().ioBuffer(); output.start( messageBuf ); - writer.write( msg ); - output.stop(); + try + { + writer.write( msg ); + } + finally + { + output.stop(); + } out.add( messageBuf ); out.add( messageBoundary() ); diff --git a/driver/src/main/java/org/neo4j/driver/internal/async/pool/AsyncConnectionPool.java b/driver/src/main/java/org/neo4j/driver/internal/async/pool/AsyncConnectionPool.java index d83fd30d97..37a137aeb7 100644 --- a/driver/src/main/java/org/neo4j/driver/internal/async/pool/AsyncConnectionPool.java +++ b/driver/src/main/java/org/neo4j/driver/internal/async/pool/AsyncConnectionPool.java @@ -18,15 +18,14 @@ */ package org.neo4j.driver.internal.async.pool; -import io.netty.util.concurrent.Future; +import java.util.concurrent.CompletionStage; import org.neo4j.driver.internal.async.AsyncConnection; -import org.neo4j.driver.internal.async.InternalFuture; import org.neo4j.driver.internal.net.BoltServerAddress; public interface AsyncConnectionPool { - InternalFuture acquire( BoltServerAddress address ); + CompletionStage acquire( BoltServerAddress address ); void purge( BoltServerAddress address ); @@ -34,5 +33,5 @@ public interface AsyncConnectionPool int activeConnections( BoltServerAddress address ); - Future closeAsync(); + CompletionStage closeAsync(); } diff --git a/driver/src/main/java/org/neo4j/driver/internal/async/pool/AsyncConnectionPoolImpl.java b/driver/src/main/java/org/neo4j/driver/internal/async/pool/AsyncConnectionPoolImpl.java index 19f1d7df59..c55fcc8c32 100644 --- a/driver/src/main/java/org/neo4j/driver/internal/async/pool/AsyncConnectionPoolImpl.java +++ b/driver/src/main/java/org/neo4j/driver/internal/async/pool/AsyncConnectionPoolImpl.java @@ -24,6 +24,7 @@ import io.netty.channel.pool.ChannelPool; import io.netty.util.concurrent.Future; +import java.util.concurrent.CompletionStage; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ConcurrentMap; import java.util.concurrent.atomic.AtomicBoolean; @@ -31,14 +32,12 @@ import org.neo4j.driver.internal.async.AsyncConnection; import org.neo4j.driver.internal.async.AsyncConnector; import org.neo4j.driver.internal.async.Futures; -import org.neo4j.driver.internal.async.InternalFuture; import org.neo4j.driver.internal.async.NettyConnection; import org.neo4j.driver.internal.net.BoltServerAddress; import org.neo4j.driver.internal.net.pooling.PoolSettings; import org.neo4j.driver.internal.util.Clock; import org.neo4j.driver.v1.Logger; import org.neo4j.driver.v1.Logging; -import org.neo4j.driver.v1.util.Function; public class AsyncConnectionPoolImpl implements AsyncConnectionPool { @@ -66,22 +65,18 @@ public AsyncConnectionPoolImpl( AsyncConnector connector, Bootstrap bootstrap, } @Override - public InternalFuture acquire( final BoltServerAddress address ) + public CompletionStage acquire( final BoltServerAddress address ) { log.debug( "Acquiring connection from pool for address: %s", address ); assertNotClosed(); - final ChannelPool pool = getOrCreatePool( address ); - final Future connectionFuture = pool.acquire(); + ChannelPool pool = getOrCreatePool( address ); + Future connectionFuture = pool.acquire(); - return Futures.thenApply( connectionFuture, bootstrap, new Function() + return Futures.asCompletionStage( connectionFuture ).thenApply( channel -> { - @Override - public AsyncConnection apply( Channel channel ) - { - assertNotClosed( address, channel, pool ); - return new NettyConnection( channel, pool, clock ); - } + assertNotClosed( address, channel, pool ); + return new NettyConnection( channel, pool, clock ); } ); } @@ -114,7 +109,7 @@ public int activeConnections( BoltServerAddress address ) } @Override - public Future closeAsync() + public CompletionStage closeAsync() { if ( closed.compareAndSet( false, true ) ) { @@ -133,7 +128,7 @@ public Future closeAsync() eventLoopGroup().shutdownGracefully(); } } - return eventLoopGroup().terminationFuture(); + return Futures.asCompletionStage( eventLoopGroup().terminationFuture() ); } private ChannelPool getOrCreatePool( BoltServerAddress address ) 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 8b4f9549ac..2cc0c2de91 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 @@ -19,10 +19,10 @@ package org.neo4j.driver.internal.cluster.loadbalancing; import java.util.Set; +import java.util.concurrent.CompletionStage; import org.neo4j.driver.internal.RoutingErrorHandler; import org.neo4j.driver.internal.async.AsyncConnection; -import org.neo4j.driver.internal.async.InternalFuture; import org.neo4j.driver.internal.cluster.AddressSet; import org.neo4j.driver.internal.cluster.ClusterComposition; import org.neo4j.driver.internal.cluster.ClusterCompositionProvider; @@ -91,7 +91,7 @@ public PooledConnection acquireConnection( AccessMode mode ) } @Override - public InternalFuture acquireAsyncConnection( AccessMode mode ) + public CompletionStage acquireAsyncConnection( AccessMode mode ) { throw new UnsupportedOperationException(); } diff --git a/driver/src/main/java/org/neo4j/driver/internal/handlers/BeginTxResponseHandler.java b/driver/src/main/java/org/neo4j/driver/internal/handlers/BeginTxResponseHandler.java index b13e12fda7..f5c8ad2990 100644 --- a/driver/src/main/java/org/neo4j/driver/internal/handlers/BeginTxResponseHandler.java +++ b/driver/src/main/java/org/neo4j/driver/internal/handlers/BeginTxResponseHandler.java @@ -18,10 +18,9 @@ */ package org.neo4j.driver.internal.handlers; -import io.netty.util.concurrent.Promise; - import java.util.Arrays; import java.util.Map; +import java.util.concurrent.CompletableFuture; import org.neo4j.driver.internal.spi.ResponseHandler; import org.neo4j.driver.v1.Transaction; @@ -31,25 +30,25 @@ public class BeginTxResponseHandler implements ResponseHandler { - private final Promise beginTxPromise; + private final CompletableFuture beginTxPromise; private final T tx; - public BeginTxResponseHandler( Promise beginTxPromise, T tx ) + public BeginTxResponseHandler( CompletableFuture beginFuture, T tx ) { - this.beginTxPromise = requireNonNull( beginTxPromise ); + this.beginTxPromise = requireNonNull( beginFuture ); this.tx = requireNonNull( tx ); } @Override public void onSuccess( Map metadata ) { - beginTxPromise.setSuccess( tx ); + beginTxPromise.complete( tx ); } @Override public void onFailure( Throwable error ) { - beginTxPromise.setFailure( error ); + beginTxPromise.completeExceptionally( error ); } @Override diff --git a/driver/src/main/java/org/neo4j/driver/internal/handlers/CommitTxResponseHandler.java b/driver/src/main/java/org/neo4j/driver/internal/handlers/CommitTxResponseHandler.java index 6bb60dafac..6d505119de 100644 --- a/driver/src/main/java/org/neo4j/driver/internal/handlers/CommitTxResponseHandler.java +++ b/driver/src/main/java/org/neo4j/driver/internal/handlers/CommitTxResponseHandler.java @@ -18,10 +18,9 @@ */ package org.neo4j.driver.internal.handlers; -import io.netty.util.concurrent.Promise; - import java.util.Arrays; import java.util.Map; +import java.util.concurrent.CompletableFuture; import org.neo4j.driver.internal.Bookmark; import org.neo4j.driver.internal.ExplicitTransaction; @@ -32,12 +31,12 @@ public class CommitTxResponseHandler implements ResponseHandler { - private final Promise commitTxPromise; + private final CompletableFuture commitFuture; private final ExplicitTransaction tx; - public CommitTxResponseHandler( Promise commitTxPromise, ExplicitTransaction tx ) + public CommitTxResponseHandler( CompletableFuture commitFuture, ExplicitTransaction tx ) { - this.commitTxPromise = requireNonNull( commitTxPromise ); + this.commitFuture = requireNonNull( commitFuture ); this.tx = requireNonNull( tx ); } @@ -53,13 +52,13 @@ public void onSuccess( Map metadata ) } } - commitTxPromise.setSuccess( null ); + commitFuture.complete( null ); } @Override public void onFailure( Throwable error ) { - commitTxPromise.setFailure( error ); + commitFuture.completeExceptionally( error ); } @Override diff --git a/driver/src/main/java/org/neo4j/driver/internal/handlers/PullAllResponseHandler.java b/driver/src/main/java/org/neo4j/driver/internal/handlers/PullAllResponseHandler.java index 6e5e1f2345..a8b59e6574 100644 --- a/driver/src/main/java/org/neo4j/driver/internal/handlers/PullAllResponseHandler.java +++ b/driver/src/main/java/org/neo4j/driver/internal/handlers/PullAllResponseHandler.java @@ -23,11 +23,11 @@ import java.util.List; import java.util.Map; import java.util.Queue; +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.CompletionStage; import org.neo4j.driver.internal.InternalRecord; import org.neo4j.driver.internal.async.AsyncConnection; -import org.neo4j.driver.internal.async.InternalFuture; -import org.neo4j.driver.internal.async.InternalPromise; import org.neo4j.driver.internal.spi.ResponseHandler; import org.neo4j.driver.internal.summary.InternalNotification; import org.neo4j.driver.internal.summary.InternalPlan; @@ -44,6 +44,8 @@ import org.neo4j.driver.v1.summary.StatementType; import static java.util.Objects.requireNonNull; +import static java.util.concurrent.CompletableFuture.completedFuture; +import static org.neo4j.driver.internal.async.Futures.failedFuture; public abstract class PullAllResponseHandler implements ResponseHandler { @@ -59,8 +61,8 @@ public abstract class PullAllResponseHandler implements ResponseHandler private Throwable failure; private ResultSummary summary; - private InternalPromise recordPromise; - private InternalPromise summaryPromise; + private CompletableFuture recordFuture; + private CompletableFuture summaryFuture; public PullAllResponseHandler( Statement statement, RunResponseHandler runResponseHandler, AsyncConnection connection ) @@ -74,15 +76,15 @@ public PullAllResponseHandler( Statement statement, RunResponseHandler runRespon public synchronized void onSuccess( Map metadata ) { summary = extractResultSummary( metadata ); - if ( summaryPromise != null ) + if ( summaryFuture != null ) { - summaryPromise.setSuccess( summary ); - summaryPromise = null; + summaryFuture.complete( summary ); + summaryFuture = null; } succeeded = true; afterSuccess(); - succeedRecordPromise( null ); + completeRecordFuture( null ); } protected abstract void afterSuccess(); @@ -92,7 +94,7 @@ public synchronized void onFailure( Throwable error ) { failure = error; afterFailure( error ); - failRecordPromise( error ); + failRecordFuture( error ); } protected abstract void afterFailure( Throwable error ); @@ -102,9 +104,9 @@ public synchronized void onRecord( Value[] fields ) { Record record = new InternalRecord( runResponseHandler.statementKeys(), fields ); - if ( recordPromise != null ) + if ( recordFuture != null ) { - succeedRecordPromise( record ); + completeRecordFuture( record ); } else { @@ -112,46 +114,46 @@ public synchronized void onRecord( Value[] fields ) } } - public synchronized InternalFuture nextAsync() + public synchronized CompletionStage nextAsync() { Record record = dequeueRecord(); if ( record == null ) { if ( succeeded ) { - return connection.newPromise().setSuccess( null ); + return completedFuture( null ); } if ( failure != null ) { - return connection.newPromise().setFailure( failure ); + return failedFuture( failure ); } - if ( recordPromise == null ) + if ( recordFuture == null ) { - recordPromise = connection.newPromise(); + recordFuture = new CompletableFuture<>(); } - return recordPromise; + return recordFuture; } else { - return connection.newPromise().setSuccess( record ); + return completedFuture( record ); } } - public synchronized InternalFuture summaryAsync() + public synchronized CompletionStage summaryAsync() { if ( summary != null ) { - return connection.newPromise().setSuccess( summary ); + return completedFuture( summary ); } else { - if ( summaryPromise == null ) + if ( summaryFuture == null ) { - summaryPromise = connection.newPromise(); + summaryFuture = new CompletableFuture<>(); } - return summaryPromise; + return summaryFuture; } } @@ -180,23 +182,23 @@ private Record dequeueRecord() return record; } - private void succeedRecordPromise( Record record ) + private void completeRecordFuture( Record record ) { - if ( recordPromise != null ) + if ( recordFuture != null ) { - InternalPromise promise = recordPromise; - recordPromise = null; - promise.setSuccess( record ); + CompletableFuture future = recordFuture; + recordFuture = null; + future.complete( record ); } } - private void failRecordPromise( Throwable error ) + private void failRecordFuture( Throwable error ) { - if ( recordPromise != null ) + if ( recordFuture != null ) { - InternalPromise promise = recordPromise; - recordPromise = null; - promise.setFailure( error ); + CompletableFuture future = recordFuture; + recordFuture = null; + future.completeExceptionally( error ); } } diff --git a/driver/src/main/java/org/neo4j/driver/internal/handlers/RecordsResponseHandler.java b/driver/src/main/java/org/neo4j/driver/internal/handlers/RecordsResponseHandler.java index 5ac756344e..bb9302943c 100644 --- a/driver/src/main/java/org/neo4j/driver/internal/handlers/RecordsResponseHandler.java +++ b/driver/src/main/java/org/neo4j/driver/internal/handlers/RecordsResponseHandler.java @@ -18,8 +18,6 @@ */ package org.neo4j.driver.internal.handlers; -import io.netty.util.concurrent.Promise; - import java.util.Collections; import java.util.List; import java.util.Map; @@ -45,7 +43,6 @@ public class RecordsResponseHandler implements ResponseHandler private final RunResponseHandler runResponseHandler; private final Queue recordBuffer; - private Promise recordAvailablePromise; private StatementType statementType; private SummaryCounters counters; @@ -73,37 +70,18 @@ public void onSuccess( Map metadata ) resultConsumedAfter = extractResultConsumedAfter( metadata ); completed = true; - - if ( recordAvailablePromise != null ) - { - boolean hasMoreRecords = !recordBuffer.isEmpty(); - recordAvailablePromise.setSuccess( hasMoreRecords ); - recordAvailablePromise = null; - } } @Override public void onFailure( Throwable error ) { completed = true; - - if ( recordAvailablePromise != null ) - { - recordAvailablePromise.setFailure( error ); - recordAvailablePromise = null; - } } @Override public void onRecord( Value[] fields ) { recordBuffer.add( new InternalRecord( runResponseHandler.statementKeys(), fields ) ); - - if ( recordAvailablePromise != null ) - { - recordAvailablePromise.setSuccess( true ); - recordAvailablePromise = null; - } } public Queue recordBuffer() diff --git a/driver/src/main/java/org/neo4j/driver/internal/handlers/RollbackTxResponseHandler.java b/driver/src/main/java/org/neo4j/driver/internal/handlers/RollbackTxResponseHandler.java index b6939424a3..414da41047 100644 --- a/driver/src/main/java/org/neo4j/driver/internal/handlers/RollbackTxResponseHandler.java +++ b/driver/src/main/java/org/neo4j/driver/internal/handlers/RollbackTxResponseHandler.java @@ -18,10 +18,9 @@ */ package org.neo4j.driver.internal.handlers; -import io.netty.util.concurrent.Promise; - import java.util.Arrays; import java.util.Map; +import java.util.concurrent.CompletableFuture; import org.neo4j.driver.internal.spi.ResponseHandler; import org.neo4j.driver.v1.Value; @@ -30,23 +29,23 @@ public class RollbackTxResponseHandler implements ResponseHandler { - private final Promise rollbackTxPromise; + private final CompletableFuture rollbackFuture; - public RollbackTxResponseHandler( Promise rollbackTxPromise ) + public RollbackTxResponseHandler( CompletableFuture rollbackFuture ) { - this.rollbackTxPromise = requireNonNull( rollbackTxPromise ); + this.rollbackFuture = requireNonNull( rollbackFuture ); } @Override public void onSuccess( Map metadata ) { - rollbackTxPromise.setSuccess( null ); + rollbackFuture.complete( null ); } @Override public void onFailure( Throwable error ) { - rollbackTxPromise.setFailure( error ); + rollbackFuture.completeExceptionally( error ); } @Override diff --git a/driver/src/main/java/org/neo4j/driver/internal/handlers/RunResponseHandler.java b/driver/src/main/java/org/neo4j/driver/internal/handlers/RunResponseHandler.java index 83ba08c75f..4544c224e7 100644 --- a/driver/src/main/java/org/neo4j/driver/internal/handlers/RunResponseHandler.java +++ b/driver/src/main/java/org/neo4j/driver/internal/handlers/RunResponseHandler.java @@ -18,12 +18,11 @@ */ package org.neo4j.driver.internal.handlers; -import io.netty.util.concurrent.Promise; - import java.util.ArrayList; import java.util.Collections; import java.util.List; import java.util.Map; +import java.util.concurrent.CompletableFuture; import org.neo4j.driver.internal.ExplicitTransaction; import org.neo4j.driver.internal.spi.ResponseHandler; @@ -31,15 +30,15 @@ public class RunResponseHandler implements ResponseHandler { - private final Promise runCompletedPromise; + private final CompletableFuture runCompletedFuture; private final ExplicitTransaction tx; private List statementKeys; private long resultAvailableAfter; - public RunResponseHandler( Promise runCompletedPromise, ExplicitTransaction tx ) + public RunResponseHandler( CompletableFuture runCompletedFuture, ExplicitTransaction tx ) { - this.runCompletedPromise = runCompletedPromise; + this.runCompletedFuture = runCompletedFuture; this.tx = tx; } @@ -49,9 +48,9 @@ public void onSuccess( Map metadata ) statementKeys = extractKeys( metadata ); resultAvailableAfter = extractResultAvailableAfter( metadata ); - if ( runCompletedPromise != null ) + if ( runCompletedFuture != null ) { - runCompletedPromise.setSuccess( null ); + runCompletedFuture.complete( null ); } } @@ -62,9 +61,9 @@ public void onFailure( Throwable error ) { tx.resultFailed( error ); } - if ( runCompletedPromise != null ) + if ( runCompletedFuture != null ) { - runCompletedPromise.setFailure( error ); + runCompletedFuture.completeExceptionally( error ); } } diff --git a/driver/src/main/java/org/neo4j/driver/internal/messaging/PackStreamMessageFormatV1.java b/driver/src/main/java/org/neo4j/driver/internal/messaging/PackStreamMessageFormatV1.java index e1e52731a9..cfeb5511fc 100644 --- a/driver/src/main/java/org/neo4j/driver/internal/messaging/PackStreamMessageFormatV1.java +++ b/driver/src/main/java/org/neo4j/driver/internal/messaging/PackStreamMessageFormatV1.java @@ -328,7 +328,8 @@ private void packValue( Value value ) throws IOException Relationship rel = seg.relationship(); long relEndId = rel.endNodeId(); long segEndId = seg.end().id(); - packer.pack( relEndId == segEndId ? relIdx.get( rel ) : -relIdx.get( rel ) ); + int size = relEndId == segEndId ? relIdx.get( rel ) : -relIdx.get( rel ); + packer.pack( size ); packer.pack( nodeIdx.get( seg.end() ) ); } break; 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 a1d675a005..c5b10d01fe 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,16 +20,14 @@ import io.netty.util.concurrent.EventExecutor; import io.netty.util.concurrent.EventExecutorGroup; -import io.netty.util.concurrent.Future; -import io.netty.util.concurrent.FutureListener; 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 org.neo4j.driver.internal.async.InternalFuture; -import org.neo4j.driver.internal.async.InternalPromise; import org.neo4j.driver.internal.util.Clock; import org.neo4j.driver.internal.util.Supplier; import org.neo4j.driver.v1.Logger; @@ -122,11 +120,11 @@ public T retry( Supplier work ) } @Override - public InternalFuture retryAsync( Supplier> work ) + public CompletionStage retryAsync( Supplier> work ) { - InternalPromise result = new InternalPromise<>( eventExecutorGroup ); - executeWorkInEventLoop( result, work ); - return result; + CompletableFuture resultFuture = new CompletableFuture<>(); + executeWorkInEventLoop( resultFuture, work ); + return resultFuture; } protected boolean canRetryOn( Throwable error ) @@ -136,23 +134,16 @@ protected boolean canRetryOn( Throwable error ) isTransientError( error ); } - private void executeWorkInEventLoop( final InternalPromise result, final Supplier> work ) + private void executeWorkInEventLoop( CompletableFuture resultFuture, Supplier> work ) { // this is the very first time we execute given work EventExecutor eventExecutor = eventExecutorGroup.next(); - eventExecutor.execute( new Runnable() - { - @Override - public void run() - { - executeWork( result, work, -1, initialRetryDelayMs, null ); - } - } ); + eventExecutor.execute( () -> executeWork( resultFuture, work, -1, initialRetryDelayMs, null ) ); } - private void retryWorkInEventLoop( final InternalPromise result, final Supplier> work, - final Throwable error, final long startTime, final long delayMs, final List errors ) + private void retryWorkInEventLoop( CompletableFuture resultFuture, Supplier> work, + Throwable error, long startTime, long delayMs, List errors ) { // work has failed before, we need to schedule retry with the given delay EventExecutor eventExecutor = eventExecutorGroup.next(); @@ -160,56 +151,44 @@ private void retryWorkInEventLoop( final InternalPromise result, final Su long delayWithJitterMs = computeDelayWithJitter( delayMs ); log.warn( "Async transaction failed and is scheduled to retry in " + delayWithJitterMs + "ms", error ); - eventExecutor.schedule( new Runnable() + eventExecutor.schedule( () -> { - @Override - public void run() - { - long newRetryDelayMs = (long) (delayMs * multiplier); - executeWork( result, work, startTime, newRetryDelayMs, errors ); - } + long newRetryDelayMs = (long) (delayMs * multiplier); + executeWork( resultFuture, work, startTime, newRetryDelayMs, errors ); }, delayWithJitterMs, TimeUnit.MILLISECONDS ); } - private void executeWork( final InternalPromise result, final Supplier> work, - final long startTime, final long retryDelayMs, final List errors ) + private void executeWork( CompletableFuture resultFuture, Supplier> work, + long startTime, long retryDelayMs, List errors ) { - InternalFuture workFuture; + CompletionStage workStage; try { - workFuture = work.get(); + workStage = work.get(); } catch ( Throwable error ) { // work failed in a sync way, attempt to schedule a retry - retryOnError( result, work, startTime, retryDelayMs, error, errors ); + retryOnError( resultFuture, work, startTime, retryDelayMs, error, errors ); return; } - workFuture.addListener( new FutureListener() + workStage.whenComplete( ( result, error ) -> { - @Override - public void operationComplete( Future future ) + if ( error != null ) { - if ( future.isCancelled() ) - { - result.cancel( true ); - } - else if ( future.isSuccess() ) - { - result.setSuccess( future.getNow() ); - } - else - { - // work failed in async way, attempt to schedule a retry - retryOnError( result, work, startTime, retryDelayMs, future.cause(), errors ); - } + // work failed in async way, attempt to schedule a retry + retryOnError( resultFuture, work, startTime, retryDelayMs, error, errors ); + } + else + { + resultFuture.complete( result ); } } ); } - private void retryOnError( InternalPromise result, Supplier> work, long startTime, - long retryDelayMs, Throwable error, List errors ) + private void retryOnError( CompletableFuture resultFuture, Supplier> work, + long startTime, long retryDelayMs, Throwable error, List errors ) { if ( canRetryOn( error ) ) { @@ -223,13 +202,13 @@ private void retryOnError( InternalPromise result, Supplier T retry( Supplier work ); - InternalFuture retryAsync( Supplier> work ); + CompletionStage retryAsync( Supplier> work ); } diff --git a/driver/src/main/java/org/neo4j/driver/internal/spi/ConnectionProvider.java b/driver/src/main/java/org/neo4j/driver/internal/spi/ConnectionProvider.java index b662f57a45..3847064589 100644 --- a/driver/src/main/java/org/neo4j/driver/internal/spi/ConnectionProvider.java +++ b/driver/src/main/java/org/neo4j/driver/internal/spi/ConnectionProvider.java @@ -18,8 +18,9 @@ */ package org.neo4j.driver.internal.spi; +import java.util.concurrent.CompletionStage; + import org.neo4j.driver.internal.async.AsyncConnection; -import org.neo4j.driver.internal.async.InternalFuture; import org.neo4j.driver.v1.AccessMode; /** @@ -36,5 +37,5 @@ public interface ConnectionProvider extends AutoCloseable */ PooledConnection acquireConnection( AccessMode mode ); - InternalFuture acquireAsyncConnection( AccessMode mode ); + CompletionStage acquireAsyncConnection( AccessMode mode ); } diff --git a/driver/src/main/java/org/neo4j/driver/v1/Session.java b/driver/src/main/java/org/neo4j/driver/v1/Session.java index a3c9e3e61e..e177e3097b 100644 --- a/driver/src/main/java/org/neo4j/driver/v1/Session.java +++ b/driver/src/main/java/org/neo4j/driver/v1/Session.java @@ -18,6 +18,8 @@ */ package org.neo4j.driver.v1; +import java.util.concurrent.CompletionStage; + import org.neo4j.driver.v1.util.Resource; /** @@ -82,7 +84,7 @@ public interface Session extends Resource, StatementRunner @Deprecated Transaction beginTransaction( String bookmark ); - Response beginTransactionAsync(); + CompletionStage beginTransactionAsync(); /** * Execute given unit of work in a {@link AccessMode#READ read} transaction. @@ -96,7 +98,7 @@ public interface Session extends Resource, StatementRunner */ T readTransaction( TransactionWork work ); - Response readTransactionAsync( TransactionWork> work ); + CompletionStage readTransactionAsync( TransactionWork> work ); /** * Execute given unit of work in a {@link AccessMode#WRITE write} transaction. @@ -110,7 +112,7 @@ public interface Session extends Resource, StatementRunner */ T writeTransaction( TransactionWork work ); - Response writeTransactionAsync( TransactionWork> work ); + CompletionStage writeTransactionAsync( TransactionWork> work ); /** * Return the bookmark received following the last completed @@ -149,5 +151,5 @@ public interface Session extends Resource, StatementRunner @Override void close(); - Response closeAsync(); + CompletionStage closeAsync(); } diff --git a/driver/src/main/java/org/neo4j/driver/v1/StatementResultCursor.java b/driver/src/main/java/org/neo4j/driver/v1/StatementResultCursor.java index 528838b63c..610c8e7f5d 100644 --- a/driver/src/main/java/org/neo4j/driver/v1/StatementResultCursor.java +++ b/driver/src/main/java/org/neo4j/driver/v1/StatementResultCursor.java @@ -19,8 +19,9 @@ package org.neo4j.driver.v1; import java.util.List; +import java.util.concurrent.CompletionStage; +import java.util.function.Consumer; -import org.neo4j.driver.internal.util.Consumer; import org.neo4j.driver.v1.summary.ResultSummary; public interface StatementResultCursor @@ -32,13 +33,13 @@ public interface StatementResultCursor */ List keys(); - Response summaryAsync(); + CompletionStage summaryAsync(); - Response nextAsync(); + CompletionStage nextAsync(); - Response peekAsync(); + CompletionStage peekAsync(); - Response forEachAsync( Consumer action ); + CompletionStage forEachAsync( Consumer action ); - Response> listAsync(); + CompletionStage> listAsync(); } diff --git a/driver/src/main/java/org/neo4j/driver/v1/StatementRunner.java b/driver/src/main/java/org/neo4j/driver/v1/StatementRunner.java index 1d7ea37622..78305a7a40 100644 --- a/driver/src/main/java/org/neo4j/driver/v1/StatementRunner.java +++ b/driver/src/main/java/org/neo4j/driver/v1/StatementRunner.java @@ -19,6 +19,7 @@ package org.neo4j.driver.v1; import java.util.Map; +import java.util.concurrent.CompletionStage; import org.neo4j.driver.v1.types.TypeSystem; import org.neo4j.driver.v1.util.Experimental; @@ -93,7 +94,7 @@ public interface StatementRunner */ StatementResult run( String statementTemplate, Value parameters ); - Response runAsync( String statementText, Value parameters ); + CompletionStage runAsync( String statementText, Value parameters ); /** * Run a statement and return a result stream. @@ -124,7 +125,7 @@ public interface StatementRunner */ StatementResult run( String statementTemplate, Map statementParameters ); - Response runAsync( String statementTemplate, Map statementParameters ); + CompletionStage runAsync( String statementTemplate, Map statementParameters ); /** * Run a statement and return a result stream. @@ -143,7 +144,7 @@ public interface StatementRunner */ StatementResult run( String statementTemplate, Record statementParameters ); - Response runAsync( String statementTemplate, Record statementParameters ); + CompletionStage runAsync( String statementTemplate, Record statementParameters ); /** * Run a statement and return a result stream. @@ -153,7 +154,7 @@ public interface StatementRunner */ StatementResult run( String statementTemplate ); - Response runAsync( String statementTemplate ); + CompletionStage runAsync( String statementTemplate ); /** * Run a statement and return a result stream. @@ -170,7 +171,7 @@ public interface StatementRunner */ StatementResult run( Statement statement ); - Response runAsync( Statement statement ); + CompletionStage runAsync( Statement statement ); /** * @return type system used by this statement runner for classifying values diff --git a/driver/src/main/java/org/neo4j/driver/v1/Transaction.java b/driver/src/main/java/org/neo4j/driver/v1/Transaction.java index d52b0cdaeb..7e4359a2f2 100644 --- a/driver/src/main/java/org/neo4j/driver/v1/Transaction.java +++ b/driver/src/main/java/org/neo4j/driver/v1/Transaction.java @@ -18,6 +18,8 @@ */ package org.neo4j.driver.v1; +import java.util.concurrent.CompletionStage; + import org.neo4j.driver.v1.util.Resource; /** @@ -79,7 +81,7 @@ public interface Transaction extends Resource, StatementRunner @Override void close(); - Response commitAsync(); + CompletionStage commitAsync(); - Response rollbackAsync(); + CompletionStage rollbackAsync(); } 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 7a89922175..dd7be8391b 100644 --- a/driver/src/test/java/org/neo4j/driver/internal/DriverFactoryTest.java +++ b/driver/src/test/java/org/neo4j/driver/internal/DriverFactoryTest.java @@ -18,7 +18,6 @@ */ package org.neo4j.driver.internal; -import io.netty.util.concurrent.EventExecutorGroup; import org.junit.Test; import org.junit.runner.RunWith; import org.junit.runners.Parameterized; @@ -170,8 +169,7 @@ protected InternalDriver createDriver( Config config, SecurityPlan securityPlan, @Override protected Driver createRoutingDriver( BoltServerAddress address, ConnectionPool connectionPool, Config config, - RoutingSettings routingSettings, SecurityPlan securityPlan, RetryLogic retryLogic, - EventExecutorGroup eventExecutorGroup ) + RoutingSettings routingSettings, SecurityPlan securityPlan, RetryLogic retryLogic ) { throw new UnsupportedOperationException( "Can't create routing driver" ); } @@ -202,10 +200,9 @@ protected LoadBalancer createLoadBalancer( BoltServerAddress address, Connection @Override protected SessionFactory createSessionFactory( ConnectionProvider connectionProvider, - RetryLogic retryLogic, EventExecutorGroup eventExecutorGroup, Config config ) + RetryLogic retryLogic, Config config ) { - SessionFactory sessionFactory = - super.createSessionFactory( connectionProvider, retryLogic, eventExecutorGroup, config ); + SessionFactory sessionFactory = super.createSessionFactory( connectionProvider, retryLogic, config ); capturedSessionFactory = sessionFactory; return sessionFactory; } diff --git a/driver/src/test/java/org/neo4j/driver/internal/LeakLoggingNetworkSessionTest.java b/driver/src/test/java/org/neo4j/driver/internal/LeakLoggingNetworkSessionTest.java index 6188e4d51b..1249acb047 100644 --- a/driver/src/test/java/org/neo4j/driver/internal/LeakLoggingNetworkSessionTest.java +++ b/driver/src/test/java/org/neo4j/driver/internal/LeakLoggingNetworkSessionTest.java @@ -18,7 +18,6 @@ */ package org.neo4j.driver.internal; -import io.netty.util.concurrent.GlobalEventExecutor; import org.junit.Rule; import org.junit.Test; import org.junit.rules.TestName; @@ -100,7 +99,7 @@ private static void finalize( Session session ) throws Exception private static LeakLoggingNetworkSession newSession( Logging logging, boolean openConnection ) { return new LeakLoggingNetworkSession( connectionProviderMock( openConnection ), READ, - new FixedRetryLogic( 0 ), GlobalEventExecutor.INSTANCE, logging ); + new FixedRetryLogic( 0 ), logging ); } private static ConnectionProvider connectionProviderMock( final boolean openConnection ) diff --git a/driver/src/test/java/org/neo4j/driver/internal/NetworkSessionTest.java b/driver/src/test/java/org/neo4j/driver/internal/NetworkSessionTest.java index 23343fff11..132a4a826f 100644 --- a/driver/src/test/java/org/neo4j/driver/internal/NetworkSessionTest.java +++ b/driver/src/test/java/org/neo4j/driver/internal/NetworkSessionTest.java @@ -18,7 +18,6 @@ */ package org.neo4j.driver.internal; -import io.netty.util.concurrent.GlobalEventExecutor; import org.junit.Before; import org.junit.Rule; import org.junit.Test; @@ -1011,8 +1010,7 @@ private static NetworkSession newSession( ConnectionProvider connectionProvider, private static NetworkSession newSession( ConnectionProvider connectionProvider, AccessMode mode, RetryLogic retryLogic, Bookmark bookmark ) { - NetworkSession session = new NetworkSession( connectionProvider, mode, retryLogic, - GlobalEventExecutor.INSTANCE, DEV_NULL_LOGGING ); + NetworkSession session = new NetworkSession( connectionProvider, mode, retryLogic, DEV_NULL_LOGGING ); session.setBookmark( bookmark ); return session; } diff --git a/driver/src/test/java/org/neo4j/driver/internal/RoutingDriverTest.java b/driver/src/test/java/org/neo4j/driver/internal/RoutingDriverTest.java index 5e6a61800d..6212951034 100644 --- a/driver/src/test/java/org/neo4j/driver/internal/RoutingDriverTest.java +++ b/driver/src/test/java/org/neo4j/driver/internal/RoutingDriverTest.java @@ -18,7 +18,6 @@ */ package org.neo4j.driver.internal; -import io.netty.util.concurrent.GlobalEventExecutor; import org.junit.Rule; import org.junit.Test; import org.junit.rules.ExpectedException; @@ -440,7 +439,7 @@ private static class NetworkSessionWithAddressFactory extends SessionFactoryImpl { NetworkSessionWithAddressFactory( ConnectionProvider connectionProvider, Config config ) { - super( connectionProvider, new FixedRetryLogic( 0 ), GlobalEventExecutor.INSTANCE, config ); + super( connectionProvider, new FixedRetryLogic( 0 ), config ); } @Override @@ -457,7 +456,7 @@ private static class NetworkSessionWithAddress extends NetworkSession NetworkSessionWithAddress( ConnectionProvider connectionProvider, AccessMode mode, Logging logging ) { - super( connectionProvider, mode, new FixedRetryLogic( 0 ), GlobalEventExecutor.INSTANCE, logging ); + super( connectionProvider, mode, new FixedRetryLogic( 0 ), logging ); try ( PooledConnection connection = connectionProvider.acquireConnection( mode ) ) { this.address = connection.boltServerAddress(); 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 fdf07758d2..fd27004032 100644 --- a/driver/src/test/java/org/neo4j/driver/internal/SessionFactoryImplTest.java +++ b/driver/src/test/java/org/neo4j/driver/internal/SessionFactoryImplTest.java @@ -18,7 +18,6 @@ */ package org.neo4j.driver.internal; -import io.netty.util.concurrent.GlobalEventExecutor; import org.junit.Test; import org.neo4j.driver.internal.retry.FixedRetryLogic; @@ -62,7 +61,6 @@ public void createsLeakLoggingNetworkSessions() private static SessionFactory newSessionFactory( Config config ) { - return new SessionFactoryImpl( mock( ConnectionProvider.class ), new FixedRetryLogic( 0 ), - GlobalEventExecutor.INSTANCE, config ); + return new SessionFactoryImpl( mock( ConnectionProvider.class ), new FixedRetryLogic( 0 ), config ); } } diff --git a/driver/src/test/java/org/neo4j/driver/internal/async/pool/AsyncConnectionPoolImplTest.java b/driver/src/test/java/org/neo4j/driver/internal/async/pool/AsyncConnectionPoolImplTest.java index ec64e68c22..9bdb48e59e 100644 --- a/driver/src/test/java/org/neo4j/driver/internal/async/pool/AsyncConnectionPoolImplTest.java +++ b/driver/src/test/java/org/neo4j/driver/internal/async/pool/AsyncConnectionPoolImplTest.java @@ -160,7 +160,7 @@ public void shouldCheckIfPoolHasAddress() public void shouldNotCloseWhenClosed() { assertNull( await( pool.closeAsync() ) ); - assertTrue( pool.closeAsync().isDone() ); + assertTrue( pool.closeAsync().toCompletableFuture().isDone() ); } private AsyncConnectionPoolImpl newPool() throws Exception 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 f80e26c9b3..4f00ecbc6c 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 @@ -387,8 +387,7 @@ private static Session newSession( LoadBalancer loadBalancer ) SleeplessClock clock = new SleeplessClock(); RetryLogic retryLogic = new ExponentialBackoffRetryLogic( RetrySettings.DEFAULT, GlobalEventExecutor.INSTANCE, clock, DEV_NULL_LOGGING ); - return new NetworkSession( loadBalancer, AccessMode.WRITE, retryLogic, GlobalEventExecutor.INSTANCE, - DEV_NULL_LOGGING ); + return new NetworkSession( loadBalancer, AccessMode.WRITE, retryLogic, DEV_NULL_LOGGING ); } private static PooledConnection newConnectionWithFailingSync( BoltServerAddress address ) 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 be0dd58a27..607e56e43c 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 @@ -23,10 +23,9 @@ import java.util.ArrayList; import java.util.List; -import java.util.concurrent.ExecutionException; +import java.util.concurrent.CompletionStage; -import org.neo4j.driver.internal.async.InternalFuture; -import org.neo4j.driver.internal.async.InternalPromise; +import org.neo4j.driver.internal.async.Futures; import org.neo4j.driver.internal.util.Clock; import org.neo4j.driver.internal.util.Supplier; import org.neo4j.driver.internal.util.TrackingEventExecutor; @@ -37,6 +36,7 @@ import org.neo4j.driver.v1.exceptions.TransientException; import static java.lang.Long.MAX_VALUE; +import static java.util.concurrent.CompletableFuture.completedFuture; import static org.hamcrest.Matchers.closeTo; import static org.hamcrest.Matchers.containsString; import static org.hamcrest.Matchers.instanceOf; @@ -54,6 +54,7 @@ import static org.mockito.Mockito.times; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; +import static org.neo4j.driver.internal.async.Futures.failedFuture; import static org.neo4j.driver.internal.logging.DevNullLogging.DEV_NULL_LOGGING; import static org.neo4j.driver.v1.util.TestUtil.await; @@ -174,9 +175,9 @@ public void nextDelayCalculatedAccordingToMultiplierAsync() throws Exception ExponentialBackoffRetryLogic retryLogic = newRetryLogic( MAX_VALUE, initialDelay, multiplier, noJitter, Clock.SYSTEM ); - InternalFuture future = retryAsync( retryLogic, retries, result ); + CompletionStage future = retryAsync( retryLogic, retries, result ); - assertEquals( result, future.get() ); + assertEquals( result, Futures.getBlocking( future ) ); assertEquals( delaysWithoutJitter( initialDelay, multiplier, retries ), eventExecutor.scheduleDelays() ); } @@ -211,8 +212,8 @@ public void nextDelayCalculatedAccordingToJitterAsync() throws Exception ExponentialBackoffRetryLogic retryLogic = newRetryLogic( MAX_VALUE, initialDelay, multiplier, jitterFactor, mock( Clock.class ) ); - InternalFuture future = retryAsync( retryLogic, retries, result ); - assertEquals( result, future.get() ); + CompletionStage future = retryAsync( retryLogic, retries, result ); + assertEquals( result, Futures.getBlocking( future ) ); List scheduleDelays = eventExecutor.scheduleDelays(); List delaysWithoutJitter = delaysWithoutJitter( initialDelay, multiplier, retries ); @@ -267,11 +268,11 @@ public void doesNotRetryWhenMaxRetryTimeExceededAsync() throws Exception ExponentialBackoffRetryLogic retryLogic = newRetryLogic( maxRetryTimeMs, initialDelay, multiplier, 0, clock ); - Supplier> workMock = newWorkMock(); + Supplier> workMock = newWorkMock(); SessionExpiredException error = sessionExpired(); when( workMock.get() ).thenReturn( failedFuture( error ) ); - InternalFuture future = retryLogic.retryAsync( workMock ); + CompletionStage future = retryLogic.retryAsync( workMock ); try { @@ -315,9 +316,9 @@ public void schedulesRetryOnServiceUnavailableException() throws Exception ExponentialBackoffRetryLogic retryLogic = newRetryLogic( 1, 42, 1, 0, clock ); - Supplier> workMock = newWorkMock(); + Supplier> workMock = newWorkMock(); SessionExpiredException error = sessionExpired(); - when( workMock.get() ).thenReturn( failedFuture( error ) ).thenReturn( succeededFuture( result ) ); + when( workMock.get() ).thenReturn( failedFuture( error ) ).thenReturn( completedFuture( result ) ); assertEquals( result, await( retryLogic.retryAsync( workMock ) ) ); @@ -351,9 +352,9 @@ public void schedulesRetryOnSessionExpiredException() throws Exception ExponentialBackoffRetryLogic retryLogic = newRetryLogic( 1, 4242, 1, 0, clock ); - Supplier> workMock = newWorkMock(); + Supplier> workMock = newWorkMock(); SessionExpiredException error = sessionExpired(); - when( workMock.get() ).thenReturn( failedFuture( error ) ).thenReturn( succeededFuture( result ) ); + when( workMock.get() ).thenReturn( failedFuture( error ) ).thenReturn( completedFuture( result ) ); assertEquals( result, await( retryLogic.retryAsync( workMock ) ) ); @@ -387,9 +388,9 @@ public void schedulesRetryOnTransientException() throws Exception ExponentialBackoffRetryLogic retryLogic = newRetryLogic( 1, 23, 1, 0, clock ); - Supplier> workMock = newWorkMock(); + Supplier> workMock = newWorkMock(); TransientException error = transientException(); - when( workMock.get() ).thenReturn( failedFuture( error ) ).thenReturn( succeededFuture( result ) ); + when( workMock.get() ).thenReturn( failedFuture( error ) ).thenReturn( completedFuture( result ) ); assertEquals( result, await( retryLogic.retryAsync( workMock ) ) ); @@ -430,7 +431,7 @@ public void doesNotRetryOnUnknownError() ExponentialBackoffRetryLogic retryLogic = newRetryLogic( 1, 1, 1, 1, clock ); - Supplier> workMock = newWorkMock(); + Supplier> workMock = newWorkMock(); IllegalStateException error = new IllegalStateException(); when( workMock.get() ).thenReturn( failedFuture( error ) ); @@ -479,7 +480,7 @@ public void doesNotRetryOnTransactionTerminatedError() ExponentialBackoffRetryLogic retryLogic = newRetryLogic( 1, 13, 1, 0, clock ); - Supplier> workMock = newWorkMock(); + Supplier> workMock = newWorkMock(); TransientException error = new TransientException( "Neo.TransientError.Transaction.Terminated", "" ); when( workMock.get() ).thenReturn( failedFuture( error ) ); @@ -528,7 +529,7 @@ public void doesNotRetryOnTransactionLockClientStoppedError() ExponentialBackoffRetryLogic retryLogic = newRetryLogic( 1, 15, 1, 0, clock ); - Supplier> workMock = newWorkMock(); + Supplier> workMock = newWorkMock(); TransientException error = new TransientException( "Neo.TransientError.Transaction.LockClientStopped", "" ); when( workMock.get() ).thenReturn( failedFuture( error ) ); @@ -625,7 +626,7 @@ public void collectsSuppressedErrorsAsync() throws Exception ExponentialBackoffRetryLogic retryLogic = newRetryLogic( maxRetryTime, initialDelay, multiplier, 0, clock ); - Supplier> workMock = newWorkMock(); + Supplier> workMock = newWorkMock(); SessionExpiredException error1 = sessionExpired(); SessionExpiredException error2 = sessionExpired(); ServiceUnavailableException error3 = serviceUnavailable(); @@ -635,19 +636,17 @@ public void collectsSuppressedErrorsAsync() throws Exception .thenReturn( failedFuture( error2 ) ) .thenReturn( failedFuture( error3 ) ) .thenReturn( failedFuture( error4 ) ) - .thenReturn( succeededFuture( result ) ); + .thenReturn( completedFuture( result ) ); try { - retryLogic.retryAsync( workMock ).get(); + Futures.getBlocking( retryLogic.retryAsync( workMock ) ); fail( "Exception expected" ); } catch ( Exception e ) { - assertThat( e, instanceOf( ExecutionException.class ) ); - Throwable cause = e.getCause(); - assertEquals( error4, cause ); - Throwable[] suppressed = cause.getSuppressed(); + assertEquals( error4, e ); + Throwable[] suppressed = e.getSuppressed(); assertEquals( 3, suppressed.length ); assertEquals( error1, suppressed[0] ); assertEquals( error2, suppressed[1] ); @@ -706,22 +705,19 @@ public void doesNotCollectSuppressedErrorsWhenSameErrorIsThrownAsync() throws Ex ExponentialBackoffRetryLogic retryLogic = newRetryLogic( maxRetryTime, initialDelay, multiplier, 0, clock ); - Supplier> workMock = newWorkMock(); + Supplier> workMock = newWorkMock(); SessionExpiredException error = sessionExpired(); when( workMock.get() ).thenReturn( failedFuture( error ) ); try { - retryLogic.retryAsync( workMock ).get(); + Futures.getBlocking( retryLogic.retryAsync( workMock ) ); fail( "Exception expected" ); } catch ( Exception e ) { - assertThat( e, instanceOf( ExecutionException.class ) ); - Throwable cause = e.getCause(); - - assertEquals( error, cause ); - assertEquals( 0, cause.getSuppressed().length ); + assertEquals( error, e ); + assertEquals( 0, e.getSuppressed().length ); } verify( workMock, times( 3 ) ).get(); @@ -791,22 +787,22 @@ public Void get() } ); } - private InternalFuture retryAsync( ExponentialBackoffRetryLogic retryLogic, final int times, + private CompletionStage retryAsync( ExponentialBackoffRetryLogic retryLogic, final int times, final Object result ) { - return retryLogic.retryAsync( new Supplier>() + return retryLogic.retryAsync( new Supplier>() { int invoked; @Override - public InternalFuture get() + public CompletionStage get() { if ( invoked < times ) { invoked++; return failedFuture( serviceUnavailable() ); } - return succeededFuture( result ); + return completedFuture( result ); } } ); } @@ -838,16 +834,6 @@ private ExponentialBackoffRetryLogic newRetryLogic( long maxRetryTimeMs, long in eventExecutor, clock, DEV_NULL_LOGGING ); } - private InternalFuture succeededFuture( Object value ) - { - return new InternalPromise<>( eventExecutor ).setSuccess( value ); - } - - private InternalFuture failedFuture( Throwable error ) - { - return new InternalPromise<>( eventExecutor ).setFailure( error ); - } - private static ServiceUnavailableException serviceUnavailable() { return new ServiceUnavailableException( "" ); diff --git a/driver/src/test/java/org/neo4j/driver/internal/util/Matchers.java b/driver/src/test/java/org/neo4j/driver/internal/util/Matchers.java index 3ee5b9e8c0..e99c1a76b3 100644 --- a/driver/src/test/java/org/neo4j/driver/internal/util/Matchers.java +++ b/driver/src/test/java/org/neo4j/driver/internal/util/Matchers.java @@ -35,6 +35,7 @@ import org.neo4j.driver.internal.net.BoltServerAddress; import org.neo4j.driver.internal.spi.ConnectionProvider; import org.neo4j.driver.v1.Driver; +import org.neo4j.driver.v1.exceptions.ClientException; import org.neo4j.driver.v1.summary.ResultSummary; import static org.neo4j.driver.internal.util.ServerVersion.v3_1_0; @@ -192,6 +193,49 @@ public void describeTo( Description description ) }; } + public static Matcher arithmeticError() + { + return new TypeSafeMatcher() + { + @Override + protected boolean matchesSafely( Throwable error ) + { + return error instanceof ClientException && + ((ClientException) error).code().contains( "ArithmeticError" ); + } + + @Override + public void describeTo( Description description ) + { + description.appendText( "client error with code 'ArithmeticError' " ); + } + }; + } + + public static Matcher syntaxError( String messagePrefix ) + { + return new TypeSafeMatcher() + { + @Override + protected boolean matchesSafely( Throwable error ) + { + if ( error instanceof ClientException ) + { + ClientException clientError = (ClientException) error; + return clientError.code().contains( "SyntaxError" ) && + clientError.getMessage().startsWith( messagePrefix ); + } + return false; + } + + @Override + public void describeTo( Description description ) + { + description.appendText( "client error with code 'SyntaxError' and prefix '" + messagePrefix + "' " ); + } + }; + } + private static boolean contains( AddressSet set, BoltServerAddress address ) { BoltServerAddress[] addresses = set.toArray(); diff --git a/driver/src/test/java/org/neo4j/driver/v1/integration/SessionAsyncIT.java b/driver/src/test/java/org/neo4j/driver/v1/integration/SessionAsyncIT.java index 7f347e43d6..6b03af217a 100644 --- a/driver/src/test/java/org/neo4j/driver/v1/integration/SessionAsyncIT.java +++ b/driver/src/test/java/org/neo4j/driver/v1/integration/SessionAsyncIT.java @@ -18,27 +18,22 @@ */ package org.neo4j.driver.v1.integration; -import io.netty.util.concurrent.GlobalEventExecutor; -import io.netty.util.concurrent.Promise; import org.junit.After; import org.junit.Before; import org.junit.Rule; import org.junit.Test; -import java.io.IOException; import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; import java.util.Iterator; import java.util.List; +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.CompletionStage; import java.util.concurrent.Future; import java.util.concurrent.atomic.AtomicInteger; -import org.neo4j.driver.internal.async.InternalPromise; -import org.neo4j.driver.internal.util.Consumer; import org.neo4j.driver.v1.Record; -import org.neo4j.driver.v1.Response; -import org.neo4j.driver.v1.ResponseListener; import org.neo4j.driver.v1.Session; import org.neo4j.driver.v1.Statement; import org.neo4j.driver.v1.StatementResultCursor; @@ -58,7 +53,7 @@ import static java.util.Collections.emptyIterator; import static org.hamcrest.Matchers.containsString; import static org.hamcrest.Matchers.instanceOf; -import static org.hamcrest.Matchers.startsWith; +import static org.hamcrest.Matchers.is; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertNotNull; @@ -67,7 +62,9 @@ import static org.junit.Assert.assertTrue; import static org.junit.Assert.fail; import static org.neo4j.driver.internal.util.Iterables.single; +import static org.neo4j.driver.internal.util.Matchers.arithmeticError; import static org.neo4j.driver.internal.util.Matchers.containsResultAvailableAfterAndResultConsumedAfter; +import static org.neo4j.driver.internal.util.Matchers.syntaxError; import static org.neo4j.driver.v1.Values.parameters; import static org.neo4j.driver.v1.util.TestUtil.await; import static org.neo4j.driver.v1.util.TestUtil.awaitAll; @@ -143,7 +140,7 @@ public void shouldFailForIncorrectQuery() } catch ( Exception e ) { - assertSyntaxError( e ); + assertThat( e, is( syntaxError( "Unexpected end of input" ) ) ); } } @@ -167,7 +164,7 @@ public void shouldFailWhenQueryFailsAtRuntime() } catch ( Exception e ) { - assertArithmeticError( e ); + assertThat( e, is( arithmeticError() ) ); } } @@ -210,8 +207,8 @@ public void shouldAllowNestedQueries() StatementResultCursor cursor = await( session.runAsync( "UNWIND [1, 2, 3] AS x CREATE (p:Person {id: x}) RETURN p" ) ); - Future>> queriesExecuted = runNestedQueries( cursor ); - List> futures = await( queriesExecuted ); + Future>> queriesExecuted = runNestedQueries( cursor ); + List> futures = await( queriesExecuted ); List futureResults = awaitAll( futures ); assertEquals( 7, futureResults.size() ); @@ -240,16 +237,16 @@ public void shouldAllowNestedQueries() } @Test - public void shouldAllowMultipleAsyncRunsWithoutConsumingResults() throws InterruptedException + public void shouldAllowMultipleAsyncRunsWithoutConsumingResults() { int queryCount = 13; - List> cursors = new ArrayList<>(); + List> cursors = new ArrayList<>(); for ( int i = 0; i < queryCount; i++ ) { cursors.add( session.runAsync( "CREATE (:Person)" ) ); } - List> records = new ArrayList<>(); + List> records = new ArrayList<>(); for ( StatementResultCursor cursor : awaitAll( cursors ) ) { records.add( cursor.nextAsync() ); @@ -361,9 +358,9 @@ public void shouldExposeResultSummaryForProfileQuery() public void shouldRunAsyncTransactionWithoutRetries() { InvocationTrackingWork work = new InvocationTrackingWork( "CREATE (:Apa) RETURN 42" ); - Response txResponse = session.writeTransactionAsync( work ); + CompletionStage txStage = session.writeTransactionAsync( work ); - Record record = await( txResponse ); + Record record = await( txStage ); assertNotNull( record ); assertEquals( 42L, record.get( 0 ).asLong() ); @@ -379,9 +376,9 @@ public void shouldRunAsyncTransactionWithRetriesOnAsyncFailures() new SessionExpiredException( "Ah!" ), new TransientException( "Code", "Message" ) ); - Response txResponse = session.writeTransactionAsync( work ); + CompletionStage txStage = session.writeTransactionAsync( work ); - Record record = await( txResponse ); + Record record = await( txStage ); assertNotNull( record ); assertEquals( 24L, record.get( 0 ).asLong() ); @@ -396,9 +393,9 @@ public void shouldRunAsyncTransactionWithRetriesOnSyncFailures() new TransientException( "Oh!", "Deadlock!" ), new ServiceUnavailableException( "Oh! Network Failure" ) ); - Response txResponse = session.writeTransactionAsync( work ); + CompletionStage txStage = session.writeTransactionAsync( work ); - Record record = await( txResponse ); + Record record = await( txStage ); assertNotNull( record ); assertEquals( 12L, record.get( 0 ).asLong() ); @@ -410,11 +407,11 @@ public void shouldRunAsyncTransactionWithRetriesOnSyncFailures() public void shouldRunAsyncTransactionThatCanNotBeRetried() { InvocationTrackingWork work = new InvocationTrackingWork( "UNWIND [10, 5, 0] AS x CREATE (:Hi) RETURN 10/x" ); - Response txResponse = session.writeTransactionAsync( work ); + CompletionStage txStage = session.writeTransactionAsync( work ); try { - await( txResponse ); + await( txStage ); fail( "Exception expected" ); } catch ( Exception e ) @@ -434,11 +431,11 @@ public void shouldRunAsyncTransactionThatCanNotBeRetriedAfterATransientFailure() InvocationTrackingWork work = new InvocationTrackingWork( "CREATE (:Person) RETURN 1" ) .withSyncFailures( new TransientException( "Oh!", "Deadlock!" ) ) .withAsyncFailures( new DatabaseException( "Oh!", "OutOfMemory!" ) ); - Response txResponse = session.writeTransactionAsync( work ); + CompletionStage txStage = session.writeTransactionAsync( work ); try { - await( txResponse ); + await( txStage ); fail( "Exception expected" ); } catch ( Exception e ) @@ -483,7 +480,7 @@ public void shouldForEachWithEmptyCursor() @Test public void shouldForEachWithNonEmptyCursor() { - testForEach( "UNWIND range(1, 10000) AS x RETURN x", 10000 ); + testForEach( "UNWIND range(1, 100000) AS x RETURN x", 100000 ); } @Test @@ -499,65 +496,57 @@ public void shouldConvertToListWithNonEmptyCursor() Arrays.asList( 1L, 11L, 21L, 31L, 41L, 51L, 61L, 71L, 81L, 91L ) ); } - private Future>> runNestedQueries( StatementResultCursor inputCursor ) + private Future>> runNestedQueries( StatementResultCursor inputCursor ) { - Promise>> resultPromise = GlobalEventExecutor.INSTANCE.newPromise(); - runNestedQueries( inputCursor, new ArrayList>(), resultPromise ); - return resultPromise; + CompletableFuture>> resultFuture = new CompletableFuture<>(); + runNestedQueries( inputCursor, new ArrayList<>(), resultFuture ); + return resultFuture; } - private void runNestedQueries( final StatementResultCursor inputCursor, final List> futures, - final Promise>> resultPromise ) + private void runNestedQueries( StatementResultCursor inputCursor, List> stages, + CompletableFuture>> resultFuture ) { - final Response recordResponse = inputCursor.nextAsync(); - futures.add( recordResponse ); + final CompletionStage recordResponse = inputCursor.nextAsync(); + stages.add( recordResponse ); - recordResponse.addListener( new ResponseListener() + recordResponse.whenComplete( ( record, error ) -> { - @Override - public void operationCompleted( Record record, Throwable error ) + if ( error != null ) { - if ( error != null ) - { - resultPromise.setFailure( error ); - } - else if ( record != null ) - { - runNestedQuery( inputCursor, record, futures, resultPromise ); - } - else - { - resultPromise.setSuccess( futures ); - } + resultFuture.completeExceptionally( error ); + } + else if ( record != null ) + { + runNestedQuery( inputCursor, record, stages, resultFuture ); + } + else + { + resultFuture.complete( stages ); } } ); } - private void runNestedQuery( final StatementResultCursor inputCursor, Record record, - final List> futures, final Promise>> resultPromise ) + private void runNestedQuery( StatementResultCursor inputCursor, Record record, + List> stages, CompletableFuture>> resultFuture ) { Node node = record.get( 0 ).asNode(); long id = node.get( "id" ).asLong(); long age = id * 10; - Response response = + CompletionStage response = session.runAsync( "MATCH (p:Person {id: $id}) SET p.age = $age RETURN p", parameters( "id", id, "age", age ) ); - response.addListener( new ResponseListener() + response.whenComplete( ( cursor, error ) -> { - @Override - public void operationCompleted( StatementResultCursor cursor, Throwable error ) + if ( error != null ) { - if ( error != null ) - { - resultPromise.setFailure( error ); - } - else - { - futures.add( cursor.nextAsync() ); - runNestedQueries( inputCursor, futures, resultPromise ); - } + resultFuture.completeExceptionally( error ); + } + else + { + stages.add( cursor.nextAsync() ); + runNestedQueries( inputCursor, stages, resultFuture ); } } ); } @@ -572,14 +561,7 @@ private void testForEach( String query, int expectedSeenRecords ) StatementResultCursor cursor = await( session.runAsync( query ) ); final AtomicInteger recordsSeen = new AtomicInteger(); - Response forEachDone = cursor.forEachAsync( new Consumer() - { - @Override - public void accept( Record record ) - { - recordsSeen.incrementAndGet(); - } - } ); + CompletionStage forEachDone = cursor.forEachAsync( record -> recordsSeen.incrementAndGet() ); assertNull( await( forEachDone ) ); assertEquals( expectedSeenRecords, recordsSeen.get() ); @@ -597,53 +579,7 @@ private void testList( String query, List expectedList ) assertEquals( expectedList, actualList ); } - private static void assertSyntaxError( Exception e ) - { - assertThat( e, instanceOf( ClientException.class ) ); - assertThat( ((ClientException) e).code(), containsString( "SyntaxError" ) ); - assertThat( e.getMessage(), startsWith( "Unexpected end of input" ) ); - } - - private static void assertArithmeticError( Exception e ) - { - assertThat( e, instanceOf( ClientException.class ) ); - assertThat( ((ClientException) e).code(), containsString( "ArithmeticError" ) ); - } - - private static class KillDbListener implements ResponseListener - { - final TestNeo4j neo4j; - volatile boolean shouldKillDb = true; - - KillDbListener( TestNeo4j neo4j ) - { - this.neo4j = neo4j; - } - - @Override - public void operationCompleted( Record record, Throwable error ) - { - if ( shouldKillDb ) - { - killDb(); - shouldKillDb = false; - } - } - - void killDb() - { - try - { - neo4j.killDb(); - } - catch ( IOException e ) - { - throw new RuntimeException( e ); - } - } - } - - private static class InvocationTrackingWork implements TransactionWork> + private static class InvocationTrackingWork implements TransactionWork> { final String query; final AtomicInteger invocationCount; @@ -675,7 +611,7 @@ int invocationCount() } @Override - public Response execute( Transaction tx ) + public CompletionStage execute( Transaction tx ) { invocationCount.incrementAndGet(); @@ -684,61 +620,48 @@ public Response execute( Transaction tx ) throw syncFailures.next(); } - final InternalPromise resultPromise = new InternalPromise<>( GlobalEventExecutor.INSTANCE ); + CompletableFuture resultFuture = new CompletableFuture<>(); - tx.runAsync( query ).addListener( new ResponseListener() - { - @Override - public void operationCompleted( final StatementResultCursor cursor, Throwable error ) - { - processQueryResult( cursor, error, resultPromise ); - } - } ); + tx.runAsync( query ).whenComplete( ( cursor, error ) -> + processQueryResult( cursor, error, resultFuture ) ); - return resultPromise; + return resultFuture; } - private void processQueryResult( final StatementResultCursor cursor, final Throwable error, - final InternalPromise resultPromise ) + private void processQueryResult( StatementResultCursor cursor, Throwable error, + CompletableFuture resultFuture ) { if ( error != null ) { - resultPromise.setFailure( error ); + resultFuture.completeExceptionally( error ); return; } - cursor.nextAsync().addListener( new ResponseListener() - { - @Override - public void operationCompleted( Record record, Throwable error ) - { - processFetchResult( record, error, resultPromise, cursor ); - } - } ); + cursor.nextAsync().whenComplete( ( record, fetchError ) -> + processFetchResult( record, fetchError, resultFuture ) ); } - private void processFetchResult( Record record, Throwable error, - InternalPromise resultPromise, StatementResultCursor cursor ) + private void processFetchResult( Record record, Throwable error, CompletableFuture resultFuture ) { if ( error != null ) { - resultPromise.setFailure( error ); + resultFuture.completeExceptionally( error ); return; } if ( record == null ) { - resultPromise.setFailure( new AssertionError( "Record not available" ) ); + resultFuture.completeExceptionally( new AssertionError( "Record not available" ) ); return; } if ( asyncFailures.hasNext() ) { - resultPromise.setFailure( asyncFailures.next() ); + resultFuture.completeExceptionally( asyncFailures.next() ); } else { - resultPromise.setSuccess( record ); + resultFuture.complete( record ); } } } diff --git a/driver/src/test/java/org/neo4j/driver/v1/integration/TransactionAsyncIT.java b/driver/src/test/java/org/neo4j/driver/v1/integration/TransactionAsyncIT.java index fe10bb78aa..d364fe6549 100644 --- a/driver/src/test/java/org/neo4j/driver/v1/integration/TransactionAsyncIT.java +++ b/driver/src/test/java/org/neo4j/driver/v1/integration/TransactionAsyncIT.java @@ -28,11 +28,10 @@ import java.util.Arrays; import java.util.Collections; import java.util.List; +import java.util.concurrent.CompletionStage; import java.util.concurrent.atomic.AtomicInteger; -import org.neo4j.driver.internal.util.Consumer; import org.neo4j.driver.v1.Record; -import org.neo4j.driver.v1.Response; import org.neo4j.driver.v1.Session; import org.neo4j.driver.v1.Statement; import org.neo4j.driver.v1.StatementResult; @@ -62,6 +61,7 @@ import static org.junit.Assume.assumeTrue; 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.ServerVersion.v3_1_0; import static org.neo4j.driver.v1.Values.parameters; import static org.neo4j.driver.v1.util.TestUtil.await; @@ -224,7 +224,7 @@ public void shouldFailToCommitAfterSingleWrongStatement() } catch ( Exception e ) { - assertSyntaxError( e ); + assertThat( e, is( syntaxError( "Unexpected end of input" ) ) ); } try @@ -250,7 +250,7 @@ public void shouldAllowRollbackAfterSingleWrongStatement() } catch ( Exception e ) { - assertSyntaxError( e ); + assertThat( e, is( syntaxError( "Unexpected end of input" ) ) ); } assertThat( await( tx.rollbackAsync() ), is( nullValue() ) ); @@ -278,7 +278,7 @@ public void shouldFailToCommitAfterCoupleCorrectAndSingleWrongStatement() } catch ( Exception e ) { - assertSyntaxError( e ); + assertThat( e, is( syntaxError( "Unexpected end of input" ) ) ); } try @@ -314,7 +314,7 @@ public void shouldAllowRollbackAfterCoupleCorrectAndSingleWrongStatement() } catch ( Exception e ) { - assertSyntaxError( e ); + assertThat( e, is( syntaxError( "Unexpected end of input" ) ) ); } assertThat( await( tx.rollbackAsync() ), is( nullValue() ) ); @@ -332,7 +332,7 @@ public void shouldNotAllowNewStatementsAfterAnIncorrectStatement() } catch ( Exception e ) { - assertSyntaxError( e ); + assertThat( e, is( syntaxError( "Unexpected end of input" ) ) ); } try @@ -374,9 +374,9 @@ public void shouldBePossibleToCommitWhenCommitted() tx.runAsync( "CREATE ()" ); assertNull( await( tx.commitAsync() ) ); - Response secondCommit = tx.commitAsync(); + CompletionStage secondCommit = tx.commitAsync(); // second commit should return a completed future - assertTrue( secondCommit.isDone() ); + assertTrue( secondCommit.toCompletableFuture().isDone() ); assertNull( await( secondCommit ) ); } @@ -387,9 +387,9 @@ public void shouldBePossibleToRollbackWhenRolledBack() tx.runAsync( "CREATE ()" ); assertNull( await( tx.rollbackAsync() ) ); - Response secondRollback = tx.rollbackAsync(); + CompletionStage secondRollback = tx.rollbackAsync(); // second rollback should return a completed future - assertTrue( secondRollback.isDone() ); + assertTrue( secondRollback.toCompletableFuture().isDone() ); assertNull( await( secondRollback ) ); } @@ -524,7 +524,6 @@ public void shouldExposeResultSummaryForProfileQuery() // asserting on profile is a bit fragile and can break when server side changes or with different // server versions; that is why do fuzzy assertions in this test based on string content String profileAsString = summary.profile().toString(); - System.out.println( profileAsString ); assertThat( profileAsString, containsString( "DbHits" ) ); assertEquals( 0, summary.notifications().size() ); assertThat( summary, containsResultAvailableAfterAndResultConsumedAfter() ); @@ -610,14 +609,7 @@ private void testForEach( String query, int expectedSeenRecords ) StatementResultCursor cursor = await( tx.runAsync( query ) ); final AtomicInteger recordsSeen = new AtomicInteger(); - Response forEachDone = cursor.forEachAsync( new Consumer() - { - @Override - public void accept( Record record ) - { - recordsSeen.incrementAndGet(); - } - } ); + CompletionStage forEachDone = cursor.forEachAsync( record -> recordsSeen.incrementAndGet() ); assertNull( await( forEachDone ) ); assertEquals( expectedSeenRecords, recordsSeen.get() ); @@ -635,11 +627,4 @@ private void testList( String query, List expectedList ) } assertEquals( expectedList, actualList ); } - - private static void assertSyntaxError( Exception e ) - { - assertThat( e, instanceOf( ClientException.class ) ); - assertThat( ((ClientException) e).code(), containsString( "SyntaxError" ) ); - assertThat( e.getMessage(), startsWith( "Unexpected end of input" ) ); - } } diff --git a/driver/src/main/java/org/neo4j/driver/internal/async/InternalFuture.java b/driver/src/test/java/org/neo4j/driver/v1/stress/AbstractAsyncQuery.java similarity index 51% rename from driver/src/main/java/org/neo4j/driver/internal/async/InternalFuture.java rename to driver/src/test/java/org/neo4j/driver/v1/stress/AbstractAsyncQuery.java index 6444384fe3..7f2e929f8b 100644 --- a/driver/src/main/java/org/neo4j/driver/internal/async/InternalFuture.java +++ b/driver/src/test/java/org/neo4j/driver/v1/stress/AbstractAsyncQuery.java @@ -16,22 +16,29 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package org.neo4j.driver.internal.async; +package org.neo4j.driver.v1.stress; -import io.netty.util.concurrent.EventExecutor; -import io.netty.util.concurrent.Future; +import org.neo4j.driver.v1.AccessMode; +import org.neo4j.driver.v1.Driver; +import org.neo4j.driver.v1.Session; -import org.neo4j.driver.internal.util.BiConsumer; -import org.neo4j.driver.v1.Response; -import org.neo4j.driver.v1.util.Function; - -public interface InternalFuture extends Future, Response +public abstract class AbstractAsyncQuery implements AsyncCommand { - EventExecutor eventExecutor(); - - InternalFuture thenApply( Function fn ); + protected final Driver driver; + protected final boolean useBookmark; - InternalFuture thenCompose( Function> fn ); + public AbstractAsyncQuery( Driver driver, boolean useBookmark ) + { + this.driver = driver; + this.useBookmark = useBookmark; + } - InternalFuture whenComplete( BiConsumer action ); + public Session newSession( AccessMode mode, C context ) + { + if ( useBookmark ) + { + return driver.session( mode, context.getBookmark() ); + } + return driver.session( mode ); + } } diff --git a/driver/src/test/java/org/neo4j/driver/v1/stress/AbstractBlockingQuery.java b/driver/src/test/java/org/neo4j/driver/v1/stress/AbstractBlockingQuery.java new file mode 100644 index 0000000000..10aa8d580c --- /dev/null +++ b/driver/src/test/java/org/neo4j/driver/v1/stress/AbstractBlockingQuery.java @@ -0,0 +1,66 @@ +/* + * Copyright (c) 2002-2017 "Neo Technology," + * Network Engine for Objects in Lund AB [http://neotechnology.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.v1.stress; + +import org.neo4j.driver.v1.AccessMode; +import org.neo4j.driver.v1.Driver; +import org.neo4j.driver.v1.Session; +import org.neo4j.driver.v1.Transaction; +import org.neo4j.driver.v1.exceptions.TransientException; + +public abstract class AbstractBlockingQuery implements BlockingCommand +{ + protected final Driver driver; + protected final boolean useBookmark; + + public AbstractBlockingQuery( Driver driver, boolean useBookmark ) + { + this.driver = driver; + this.useBookmark = useBookmark; + } + + public Session newSession( AccessMode mode, C context ) + { + if ( useBookmark ) + { + return driver.session( mode, context.getBookmark() ); + } + return driver.session( mode ); + } + + public Transaction beginTransaction( Session session, C context ) + { + if ( useBookmark ) + { + while ( true ) + { + try + { + return session.beginTransaction(); + } + catch ( TransientException e ) + { + context.bookmarkFailed(); + } + } + } + + return session.beginTransaction(); + } +} diff --git a/driver/src/test/java/org/neo4j/driver/v1/stress/AbstractContext.java b/driver/src/test/java/org/neo4j/driver/v1/stress/AbstractContext.java new file mode 100644 index 0000000000..55ab439d30 --- /dev/null +++ b/driver/src/test/java/org/neo4j/driver/v1/stress/AbstractContext.java @@ -0,0 +1,86 @@ +/* + * Copyright (c) 2002-2017 "Neo Technology," + * Network Engine for Objects in Lund AB [http://neotechnology.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.v1.stress; + +import java.util.concurrent.atomic.AtomicInteger; +import java.util.concurrent.atomic.AtomicLong; + +import org.neo4j.driver.v1.summary.ResultSummary; + +public abstract class AbstractContext +{ + private volatile boolean stopped; + private volatile String bookmark; + private final AtomicLong readNodesCount = new AtomicLong(); + private final AtomicLong createdNodesCount = new AtomicLong(); + private final AtomicInteger bookmarkFailures = new AtomicInteger(); + + public final boolean isStopped() + { + return stopped; + } + + public final void stop() + { + this.stopped = true; + } + + public final String getBookmark() + { + return bookmark; + } + + public final void setBookmark( String bookmark ) + { + this.bookmark = bookmark; + } + + public final void nodeCreated() + { + createdNodesCount.incrementAndGet(); + } + + public final long getCreatedNodesCount() + { + return createdNodesCount.get(); + } + + public final void readCompleted( ResultSummary summary ) + { + readNodesCount.incrementAndGet(); + processSummary( summary ); + } + + public abstract void processSummary( ResultSummary summary ); + + public long getReadNodesCount() + { + return readNodesCount.get(); + } + + public final void bookmarkFailed() + { + bookmarkFailures.incrementAndGet(); + } + + public final int getBookmarkFailures() + { + return bookmarkFailures.get(); + } +} diff --git a/driver/src/test/java/org/neo4j/driver/v1/stress/AbstractStressIT.java b/driver/src/test/java/org/neo4j/driver/v1/stress/AbstractStressIT.java new file mode 100644 index 0000000000..41c42462f4 --- /dev/null +++ b/driver/src/test/java/org/neo4j/driver/v1/stress/AbstractStressIT.java @@ -0,0 +1,401 @@ +/* + * Copyright (c) 2002-2017 "Neo Technology," + * Network Engine for Objects in Lund AB [http://neotechnology.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.v1.stress; + +import org.junit.After; +import org.junit.Before; +import org.junit.Test; + +import java.lang.management.ManagementFactory; +import java.lang.management.OperatingSystemMXBean; +import java.lang.reflect.Method; +import java.net.URI; +import java.util.ArrayList; +import java.util.HashSet; +import java.util.List; +import java.util.Set; +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.CompletionStage; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; +import java.util.concurrent.Future; +import java.util.concurrent.ThreadFactory; +import java.util.concurrent.ThreadLocalRandom; +import java.util.concurrent.TimeUnit; +import java.util.function.Function; +import java.util.logging.Level; + +import org.neo4j.driver.internal.logging.ConsoleLogging; +import org.neo4j.driver.internal.logging.DevNullLogger; +import org.neo4j.driver.internal.util.ConcurrentSet; +import org.neo4j.driver.v1.AuthToken; +import org.neo4j.driver.v1.Config; +import org.neo4j.driver.v1.Driver; +import org.neo4j.driver.v1.GraphDatabase; +import org.neo4j.driver.v1.Logger; +import org.neo4j.driver.v1.Logging; +import org.neo4j.driver.v1.Record; +import org.neo4j.driver.v1.Session; +import org.neo4j.driver.v1.util.DaemonThreadFactory; + +import static java.util.concurrent.TimeUnit.MINUTES; +import static org.hamcrest.Matchers.equalTo; +import static org.hamcrest.Matchers.lessThanOrEqualTo; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNull; +import static org.junit.Assert.assertThat; +import static org.junit.Assume.assumeTrue; + +abstract class AbstractStressIT +{ + private static final int THREAD_COUNT = Integer.getInteger( "threadCount", 8 ); + private static final int ASYNC_BATCH_SIZE = Integer.getInteger( "asyncBatchSize", 10 ); + private static final int EXECUTION_TIME_SECONDS = Integer.getInteger( "executionTimeSeconds", 20 ); + private static final boolean DEBUG_LOGGING_ENABLED = Boolean.getBoolean( "loggingEnabled" ); + + private LoggerNameTrackingLogging logging; + private ExecutorService executor; + + Driver driver; + + @Before + public void setUp() + { + logging = new LoggerNameTrackingLogging(); + + Config config = Config.build() + .withLogging( logging ) + .withMaxConnectionPoolSize( 100 ) + .withConnectionAcquisitionTimeout( 1, MINUTES ) + .toConfig(); + + driver = GraphDatabase.driver( databaseUri(), authToken(), config ); + + ThreadFactory threadFactory = new DaemonThreadFactory( getClass().getSimpleName() + "-worker-" ); + executor = Executors.newCachedThreadPool( threadFactory ); + } + + @After + public void tearDown() + { + executor.shutdownNow(); + if ( driver != null ) + { + driver.close(); + } + } + + @Test + public void blockingApiStressTest() throws Throwable + { + runStressTest( this::launchBlockingWorkerThreads ); + } + + @Test + public void asyncApiStressTest() throws Throwable + { + // todo: re-enable when async is supported in routing driver + assumeTrue( "bolt".equalsIgnoreCase( databaseUri().getScheme() ) ); + + runStressTest( this::launchAsyncWorkerThreads ); + } + + private void runStressTest( Function>> threadLauncher ) throws Throwable + { + C context = createContext(); + List> resultFutures = threadLauncher.apply( context ); + + ResourcesInfo resourcesInfo = sleepAndGetResourcesInfo(); + context.stop(); + + Throwable firstError = null; + for ( Future future : resultFutures ) + { + try + { + assertNull( future.get( 10, TimeUnit.SECONDS ) ); + } + catch ( Throwable error ) + { + firstError = withSuppressed( firstError, error ); + } + } + + printStats( context ); + + if ( firstError != null ) + { + throw firstError; + } + + verifyResults( context, resourcesInfo ); + } + + abstract URI databaseUri(); + + abstract AuthToken authToken(); + + abstract C createContext(); + + abstract List> createTestSpecificBlockingCommands(); + + abstract boolean handleWriteFailure( Throwable error, C context ); + + abstract void assertExpectedReadQueryDistribution( C context ); + + abstract void printStats( A context ); + + private List> launchBlockingWorkerThreads( C context ) + { + List> commands = createBlockingCommands(); + List> futures = new ArrayList<>(); + + for ( int i = 0; i < THREAD_COUNT; i++ ) + { + Future future = launchBlockingWorkerThread( executor, commands, context ); + futures.add( future ); + } + + return futures; + } + + private List> createBlockingCommands() + { + List> commands = new ArrayList<>(); + + commands.add( new BlockingReadQuery<>( driver, false ) ); + commands.add( new BlockingReadQuery<>( driver, true ) ); + + commands.add( new BlockingReadQueryInTx<>( driver, false ) ); + commands.add( new BlockingReadQueryInTx<>( driver, true ) ); + + commands.add( new BlockingWriteQuery<>( this, driver, false ) ); + commands.add( new BlockingWriteQuery<>( this, driver, true ) ); + + commands.add( new BlockingWriteQueryInTx<>( this, driver, false ) ); + commands.add( new BlockingWriteQueryInTx<>( this, driver, true ) ); + + commands.add( new BlockingWrongQuery<>( driver ) ); + commands.add( new BlockingWrongQueryInTx<>( driver ) ); + + commands.add( new BlockingFailingQuery<>( driver ) ); + commands.add( new BlockingFailingQueryInTx<>( driver ) ); + + commands.add( new FailedAuth<>( databaseUri(), logging ) ); + + commands.addAll( createTestSpecificBlockingCommands() ); + + return commands; + } + + private Future launchBlockingWorkerThread( ExecutorService executor, List> commands, + C context ) + { + return executor.submit( () -> + { + while ( !context.isStopped() ) + { + BlockingCommand command = randomOf( commands ); + command.execute( context ); + } + return null; + } ); + } + + private List> launchAsyncWorkerThreads( C context ) + { + List> commands = createAsyncCommands(); + List> futures = new ArrayList<>(); + + for ( int i = 0; i < THREAD_COUNT; i++ ) + { + Future future = launchAsyncWorkerThread( executor, commands, context ); + futures.add( future ); + } + + return futures; + } + + private List> createAsyncCommands() + { + List> commands = new ArrayList<>(); + + commands.add( new AsyncReadQuery<>( driver, false ) ); + commands.add( new AsyncReadQuery<>( driver, true ) ); + + commands.add( new AsyncReadQueryInTx<>( driver, false ) ); + commands.add( new AsyncReadQueryInTx<>( driver, true ) ); + + commands.add( new AsyncWriteQuery<>( this, driver, false ) ); + commands.add( new AsyncWriteQuery<>( this, driver, true ) ); + + commands.add( new AsyncWriteQueryInTx<>( this, driver, false ) ); + commands.add( new AsyncWriteQueryInTx<>( this, driver, true ) ); + + commands.add( new AsyncWrongQuery<>( driver ) ); + commands.add( new AsyncWrongQueryInTx<>( driver ) ); + + commands.add( new AsyncFailingQuery<>( driver ) ); + commands.add( new AsyncFailingQueryInTx<>( driver ) ); + + return commands; + } + + private Future launchAsyncWorkerThread( ExecutorService executor, List> commands, C context ) + { + return executor.submit( () -> + { + while ( !context.isStopped() ) + { + CompletableFuture allCommands = executeAsyncCommands( context, commands, ASYNC_BATCH_SIZE ); + assertNull( allCommands.get() ); + } + return null; + } ); + } + + @SuppressWarnings( "unchecked" ) + private CompletableFuture executeAsyncCommands( C context, List> commands, int count ) + { + CompletableFuture[] executions = new CompletableFuture[count]; + for ( int i = 0; i < count; i++ ) + { + AsyncCommand command = randomOf( commands ); + CompletionStage execution = command.execute( context ); + executions[i] = execution.toCompletableFuture(); + } + return CompletableFuture.allOf( executions ); + } + + private ResourcesInfo sleepAndGetResourcesInfo() throws InterruptedException + { + int halfSleepSeconds = Math.max( 1, EXECUTION_TIME_SECONDS / 2 ); + TimeUnit.SECONDS.sleep( halfSleepSeconds ); + ResourcesInfo resourcesInfo = getResourcesInfo(); + TimeUnit.SECONDS.sleep( halfSleepSeconds ); + return resourcesInfo; + } + + private ResourcesInfo getResourcesInfo() + { + long openFileDescriptorCount = getOpenFileDescriptorCount(); + Set acquiredLoggerNames = logging.getAcquiredLoggerNames(); + return new ResourcesInfo( openFileDescriptorCount, acquiredLoggerNames ); + } + + private void verifyResults( C context, ResourcesInfo resourcesInfo ) + { + assertNoFileDescriptorLeak( resourcesInfo.openFileDescriptorCount ); + assertNoLoggersLeak( resourcesInfo.acquiredLoggerNames ); + assertExpectedNumberOfNodesCreated( context.getCreatedNodesCount() ); + assertExpectedReadQueryDistribution( context ); + } + + private void assertNoFileDescriptorLeak( long previousOpenFileDescriptors ) + { + // number of open file descriptors should not go up for more than 20% + long maxOpenFileDescriptors = (long) (previousOpenFileDescriptors * 1.2); + long currentOpenFileDescriptorCount = getOpenFileDescriptorCount(); + assertThat( "Unexpectedly high number of open file descriptors", + currentOpenFileDescriptorCount, lessThanOrEqualTo( maxOpenFileDescriptors ) ); + } + + private void assertNoLoggersLeak( Set previousAcquiredLoggerNames ) + { + Set currentAcquiredLoggerNames = logging.getAcquiredLoggerNames(); + assertThat( "Unexpected amount of logger instances", + currentAcquiredLoggerNames, equalTo( previousAcquiredLoggerNames ) ); + } + + private void assertExpectedNumberOfNodesCreated( long expectedCount ) + { + try ( Session session = driver.session() ) + { + List records = session.run( "MATCH (n) RETURN count(n) AS nodesCount" ).list(); + assertEquals( 1, records.size() ); + Record record = records.get( 0 ); + long actualCount = record.get( "nodesCount" ).asLong(); + assertEquals( "Unexpected number of nodes in the database", expectedCount, actualCount ); + } + } + + private static long getOpenFileDescriptorCount() + { + try + { + OperatingSystemMXBean osBean = ManagementFactory.getOperatingSystemMXBean(); + Method method = osBean.getClass().getDeclaredMethod( "getOpenFileDescriptorCount" ); + method.setAccessible( true ); + return (long) method.invoke( osBean ); + } + catch ( Throwable t ) + { + return 0; + } + } + + private static Throwable withSuppressed( Throwable firstError, Throwable newError ) + { + if ( firstError == null ) + { + return newError; + } + firstError.addSuppressed( newError ); + return firstError; + } + + private static T randomOf( List elements ) + { + int index = ThreadLocalRandom.current().nextInt( elements.size() ); + return elements.get( index ); + } + + private static class ResourcesInfo + { + final long openFileDescriptorCount; + final Set acquiredLoggerNames; + + ResourcesInfo( long openFileDescriptorCount, Set acquiredLoggerNames ) + { + this.openFileDescriptorCount = openFileDescriptorCount; + this.acquiredLoggerNames = acquiredLoggerNames; + } + } + + private static class LoggerNameTrackingLogging implements Logging + { + private final Set acquiredLoggerNames = new ConcurrentSet<>(); + + @Override + public Logger getLog( String name ) + { + acquiredLoggerNames.add( name ); + if ( DEBUG_LOGGING_ENABLED ) + { + return new ConsoleLogging.ConsoleLogger( name, Level.FINE ); + } + return DevNullLogger.DEV_NULL_LOGGER; + } + + Set getAcquiredLoggerNames() + { + return new HashSet<>( acquiredLoggerNames ); + } + } +} diff --git a/driver/src/main/java/org/neo4j/driver/v1/Response.java b/driver/src/test/java/org/neo4j/driver/v1/stress/AsyncCommand.java similarity index 79% rename from driver/src/main/java/org/neo4j/driver/v1/Response.java rename to driver/src/test/java/org/neo4j/driver/v1/stress/AsyncCommand.java index af8a659b06..9e286bd1e1 100644 --- a/driver/src/main/java/org/neo4j/driver/v1/Response.java +++ b/driver/src/test/java/org/neo4j/driver/v1/stress/AsyncCommand.java @@ -16,11 +16,11 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package org.neo4j.driver.v1; +package org.neo4j.driver.v1.stress; -import java.util.concurrent.Future; +import java.util.concurrent.CompletionStage; -public interface Response extends Future +public interface AsyncCommand { - void addListener( ResponseListener listener ); + CompletionStage execute( C context ); } diff --git a/driver/src/test/java/org/neo4j/driver/v1/stress/AsyncFailingQuery.java b/driver/src/test/java/org/neo4j/driver/v1/stress/AsyncFailingQuery.java new file mode 100644 index 0000000000..1ad2a39882 --- /dev/null +++ b/driver/src/test/java/org/neo4j/driver/v1/stress/AsyncFailingQuery.java @@ -0,0 +1,58 @@ +/* + * Copyright (c) 2002-2017 "Neo Technology," + * Network Engine for Objects in Lund AB [http://neotechnology.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.v1.stress; + +import java.util.concurrent.CompletionStage; + +import org.neo4j.driver.v1.AccessMode; +import org.neo4j.driver.v1.Driver; +import org.neo4j.driver.v1.Session; +import org.neo4j.driver.v1.StatementResultCursor; + +import static org.hamcrest.Matchers.is; +import static org.junit.Assert.assertNull; +import static org.junit.Assert.assertThat; +import static org.neo4j.driver.internal.util.Matchers.arithmeticError; + +public class AsyncFailingQuery extends AbstractAsyncQuery +{ + public AsyncFailingQuery( Driver driver ) + { + super( driver, false ); + } + + @Override + public CompletionStage execute( C context ) + { + Session session = newSession( AccessMode.READ, context ); + + return session.runAsync( "UNWIND [10, 5, 0] AS x RETURN 10 / x" ) + .thenCompose( StatementResultCursor::listAsync ) + .handle( ( records, error ) -> + { + session.closeAsync(); + + assertNull( records ); + Throwable cause = error.getCause(); // unwrap CompletionException + assertThat( cause, is( arithmeticError() ) ); + + return null; + } ); + } +} diff --git a/driver/src/test/java/org/neo4j/driver/v1/stress/AsyncFailingQueryInTx.java b/driver/src/test/java/org/neo4j/driver/v1/stress/AsyncFailingQueryInTx.java new file mode 100644 index 0000000000..f1fa865124 --- /dev/null +++ b/driver/src/test/java/org/neo4j/driver/v1/stress/AsyncFailingQueryInTx.java @@ -0,0 +1,60 @@ +/* + * Copyright (c) 2002-2017 "Neo Technology," + * Network Engine for Objects in Lund AB [http://neotechnology.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.v1.stress; + +import java.util.concurrent.CompletionStage; + +import org.neo4j.driver.v1.AccessMode; +import org.neo4j.driver.v1.Driver; +import org.neo4j.driver.v1.Session; +import org.neo4j.driver.v1.StatementResultCursor; +import org.neo4j.driver.v1.Transaction; + +import static org.hamcrest.Matchers.is; +import static org.junit.Assert.assertNull; +import static org.junit.Assert.assertThat; +import static org.neo4j.driver.internal.util.Matchers.arithmeticError; + +public class AsyncFailingQueryInTx extends AbstractAsyncQuery +{ + public AsyncFailingQueryInTx( Driver driver ) + { + super( driver, false ); + } + + @Override + public CompletionStage execute( C context ) + { + Session session = newSession( AccessMode.READ, context ); + + return session.beginTransactionAsync() + .thenCompose( tx -> tx.runAsync( "UNWIND [10, 5, 0] AS x RETURN 10 / x" ) + .thenCompose( StatementResultCursor::listAsync ) + .handle( ( records, error ) -> + { + assertNull( records ); + Throwable cause = error.getCause(); // unwrap CompletionException + assertThat( cause, is( arithmeticError() ) ); + + return tx; + } ) ) + .thenCompose( Transaction::rollbackAsync ) + .whenComplete( ( ignore, error ) -> session.closeAsync() ); + } +} diff --git a/driver/src/test/java/org/neo4j/driver/v1/stress/AsyncReadQuery.java b/driver/src/test/java/org/neo4j/driver/v1/stress/AsyncReadQuery.java new file mode 100644 index 0000000000..cbc29a9b5a --- /dev/null +++ b/driver/src/test/java/org/neo4j/driver/v1/stress/AsyncReadQuery.java @@ -0,0 +1,70 @@ +/* + * Copyright (c) 2002-2017 "Neo Technology," + * Network Engine for Objects in Lund AB [http://neotechnology.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.v1.stress; + +import java.util.concurrent.CompletionStage; + +import org.neo4j.driver.v1.AccessMode; +import org.neo4j.driver.v1.Driver; +import org.neo4j.driver.v1.Record; +import org.neo4j.driver.v1.Session; +import org.neo4j.driver.v1.StatementResultCursor; +import org.neo4j.driver.v1.summary.ResultSummary; +import org.neo4j.driver.v1.types.Node; + +import static org.junit.Assert.assertNotNull; + +public class AsyncReadQuery extends AbstractAsyncQuery +{ + public AsyncReadQuery( Driver driver, boolean useBookmark ) + { + super( driver, useBookmark ); + } + + @Override + public CompletionStage execute( C context ) + { + Session session = newSession( AccessMode.READ, context ); + + CompletionStage queryFinished = session.runAsync( "MATCH (n) RETURN n LIMIT 1" ) + .thenCompose( cursor -> cursor.nextAsync() + .thenCompose( record -> processAndGetSummary( record, cursor ) ) ); + + queryFinished.whenComplete( ( summary, error ) -> + { + if ( summary != null ) + { + context.readCompleted( summary ); + } + session.closeAsync(); + } ); + + return queryFinished.thenApply( summary -> null ); + } + + private CompletionStage processAndGetSummary( Record record, StatementResultCursor cursor ) + { + if ( record != null ) + { + Node node = record.get( 0 ).asNode(); + assertNotNull( node ); + } + return cursor.summaryAsync(); + } +} diff --git a/driver/src/test/java/org/neo4j/driver/v1/stress/AsyncReadQueryInTx.java b/driver/src/test/java/org/neo4j/driver/v1/stress/AsyncReadQueryInTx.java new file mode 100644 index 0000000000..18d76ce273 --- /dev/null +++ b/driver/src/test/java/org/neo4j/driver/v1/stress/AsyncReadQueryInTx.java @@ -0,0 +1,72 @@ +/* + * Copyright (c) 2002-2017 "Neo Technology," + * Network Engine for Objects in Lund AB [http://neotechnology.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.v1.stress; + +import java.util.concurrent.CompletionStage; + +import org.neo4j.driver.v1.AccessMode; +import org.neo4j.driver.v1.Driver; +import org.neo4j.driver.v1.Record; +import org.neo4j.driver.v1.Session; +import org.neo4j.driver.v1.StatementResultCursor; +import org.neo4j.driver.v1.Transaction; +import org.neo4j.driver.v1.summary.ResultSummary; +import org.neo4j.driver.v1.types.Node; + +import static org.junit.Assert.assertNotNull; + +public class AsyncReadQueryInTx extends AbstractAsyncQuery +{ + public AsyncReadQueryInTx( Driver driver, boolean useBookmark ) + { + super( driver, useBookmark ); + } + + @Override + public CompletionStage execute( C ctx ) + { + Session session = newSession( AccessMode.READ, ctx ); + + CompletionStage txCommitted = session.beginTransactionAsync() + .thenCompose( tx -> tx.runAsync( "MATCH (n) RETURN n LIMIT 1" ) + .thenCompose( cursor -> cursor.nextAsync() + .thenCompose( record -> processRecordAndGetSummary( record, cursor ) + .thenCompose( summary -> processSummaryAndCommit( summary, tx, ctx ) ) ) ) ); + + txCommitted.whenComplete( ( ignore, error ) -> session.closeAsync() ); + + return txCommitted; + } + + private CompletionStage processRecordAndGetSummary( Record record, StatementResultCursor cursor ) + { + if ( record != null ) + { + Node node = record.get( 0 ).asNode(); + assertNotNull( node ); + } + return cursor.summaryAsync(); + } + + private CompletionStage processSummaryAndCommit( ResultSummary summary, Transaction tx, C context ) + { + context.readCompleted( summary ); + return tx.commitAsync(); + } +} diff --git a/driver/src/test/java/org/neo4j/driver/v1/stress/AsyncWriteQuery.java b/driver/src/test/java/org/neo4j/driver/v1/stress/AsyncWriteQuery.java new file mode 100644 index 0000000000..3bce2a3421 --- /dev/null +++ b/driver/src/test/java/org/neo4j/driver/v1/stress/AsyncWriteQuery.java @@ -0,0 +1,68 @@ +/* + * Copyright (c) 2002-2017 "Neo Technology," + * Network Engine for Objects in Lund AB [http://neotechnology.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.v1.stress; + +import java.util.concurrent.CompletionStage; + +import org.neo4j.driver.v1.AccessMode; +import org.neo4j.driver.v1.Driver; +import org.neo4j.driver.v1.Session; +import org.neo4j.driver.v1.StatementResultCursor; + +import static org.junit.Assert.assertEquals; + +public class AsyncWriteQuery extends AbstractAsyncQuery +{ + private AbstractStressIT abstractStressIT; + + public AsyncWriteQuery( AbstractStressIT abstractStressIT, Driver driver, boolean useBookmark ) + { + super( driver, useBookmark ); + this.abstractStressIT = abstractStressIT; + } + + @Override + public CompletionStage execute( C context ) + { + Session session = newSession( AccessMode.WRITE, context ); + + return session.runAsync( "CREATE ()" ) + .thenCompose( StatementResultCursor::summaryAsync ) + .handle( ( summary, error ) -> + { + session.closeAsync(); + + handleError( error, context ); + assertEquals( 1, summary.counters().nodesCreated() ); + context.nodeCreated(); + return null; + } ); + } + + private void handleError( Throwable error, C context ) + { + if ( error != null ) + { + if ( !abstractStressIT.handleWriteFailure( error, context ) ) + { + throw new RuntimeException( error ); + } + } + } +} diff --git a/driver/src/test/java/org/neo4j/driver/v1/stress/AsyncWriteQueryInTx.java b/driver/src/test/java/org/neo4j/driver/v1/stress/AsyncWriteQueryInTx.java new file mode 100644 index 0000000000..e5a8cc2052 --- /dev/null +++ b/driver/src/test/java/org/neo4j/driver/v1/stress/AsyncWriteQueryInTx.java @@ -0,0 +1,71 @@ +/* + * Copyright (c) 2002-2017 "Neo Technology," + * Network Engine for Objects in Lund AB [http://neotechnology.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.v1.stress; + +import java.util.concurrent.CompletionStage; + +import org.neo4j.driver.v1.AccessMode; +import org.neo4j.driver.v1.Driver; +import org.neo4j.driver.v1.Session; +import org.neo4j.driver.v1.summary.ResultSummary; + +import static org.junit.Assert.assertEquals; + +public class AsyncWriteQueryInTx extends AbstractAsyncQuery +{ + private AbstractStressIT abstractStressIT; + + public AsyncWriteQueryInTx( AbstractStressIT abstractStressIT, Driver driver, boolean useBookmark ) + { + super( driver, useBookmark ); + this.abstractStressIT = abstractStressIT; + } + + @Override + public CompletionStage execute( C context ) + { + Session session = newSession( AccessMode.WRITE, context ); + + CompletionStage txCommitted = session.beginTransactionAsync().thenCompose( tx -> + tx.runAsync( "CREATE ()" ).thenCompose( cursor -> + cursor.summaryAsync().thenCompose( summary -> + tx.commitAsync().thenApply( ignore -> summary ) ) ) ); + + return txCommitted.handle( ( summary, error ) -> + { + session.closeAsync(); + + handleError( error, context ); + assertEquals( 1, summary.counters().nodesCreated() ); + context.nodeCreated(); + return null; + } ); + } + + private void handleError( Throwable error, C context ) + { + if ( error != null ) + { + if ( !abstractStressIT.handleWriteFailure( error, context ) ) + { + throw new RuntimeException( error ); + } + } + } +} diff --git a/driver/src/test/java/org/neo4j/driver/v1/stress/AsyncWrongQuery.java b/driver/src/test/java/org/neo4j/driver/v1/stress/AsyncWrongQuery.java new file mode 100644 index 0000000000..7ac233f687 --- /dev/null +++ b/driver/src/test/java/org/neo4j/driver/v1/stress/AsyncWrongQuery.java @@ -0,0 +1,55 @@ +/* + * Copyright (c) 2002-2017 "Neo Technology," + * Network Engine for Objects in Lund AB [http://neotechnology.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.v1.stress; + +import java.util.concurrent.CompletionStage; + +import org.neo4j.driver.v1.AccessMode; +import org.neo4j.driver.v1.Driver; +import org.neo4j.driver.v1.Session; + +import static org.hamcrest.Matchers.is; +import static org.junit.Assert.assertNull; +import static org.junit.Assert.assertThat; +import static org.neo4j.driver.internal.util.Matchers.syntaxError; + +public class AsyncWrongQuery extends AbstractAsyncQuery +{ + public AsyncWrongQuery( Driver driver ) + { + super( driver, false ); + } + + @Override + public CompletionStage execute( C context ) + { + Session session = newSession( AccessMode.READ, context ); + + return session.runAsync( "RETURN" ).handle( ( cursor, error ) -> + { + session.closeAsync(); + + assertNull( cursor ); + Throwable cause = error.getCause(); // unwrap CompletionException + assertThat( cause, is( syntaxError( "Unexpected end of input" ) ) ); + + return null; + } ); + } +} diff --git a/driver/src/test/java/org/neo4j/driver/v1/stress/AsyncWrongQueryInTx.java b/driver/src/test/java/org/neo4j/driver/v1/stress/AsyncWrongQueryInTx.java new file mode 100644 index 0000000000..9e6f2d54b3 --- /dev/null +++ b/driver/src/test/java/org/neo4j/driver/v1/stress/AsyncWrongQueryInTx.java @@ -0,0 +1,57 @@ +/* + * Copyright (c) 2002-2017 "Neo Technology," + * Network Engine for Objects in Lund AB [http://neotechnology.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.v1.stress; + +import java.util.concurrent.CompletionStage; + +import org.neo4j.driver.v1.AccessMode; +import org.neo4j.driver.v1.Driver; +import org.neo4j.driver.v1.Session; +import org.neo4j.driver.v1.Transaction; + +import static org.hamcrest.Matchers.is; +import static org.junit.Assert.assertNull; +import static org.junit.Assert.assertThat; +import static org.neo4j.driver.internal.util.Matchers.syntaxError; + +public class AsyncWrongQueryInTx extends AbstractAsyncQuery +{ + public AsyncWrongQueryInTx( Driver driver ) + { + super( driver, false ); + } + + @Override + public CompletionStage execute( C context ) + { + Session session = newSession( AccessMode.READ, context ); + + return session.beginTransactionAsync() + .thenCompose( tx -> tx.runAsync( "RETURN" ).handle( ( cursor, error ) -> + { + assertNull( cursor ); + Throwable cause = error.getCause(); // unwrap CompletionException + assertThat( cause, is( syntaxError( "Unexpected end of input" ) ) ); + + return tx; + } ) ) + .thenCompose( Transaction::rollbackAsync ) + .whenComplete( ( ignore, error ) -> session.closeAsync() ); + } +} diff --git a/driver/src/main/java/org/neo4j/driver/internal/util/BiConsumer.java b/driver/src/test/java/org/neo4j/driver/v1/stress/BlockingCommand.java similarity index 84% rename from driver/src/main/java/org/neo4j/driver/internal/util/BiConsumer.java rename to driver/src/test/java/org/neo4j/driver/v1/stress/BlockingCommand.java index a9c09139db..a465307050 100644 --- a/driver/src/main/java/org/neo4j/driver/internal/util/BiConsumer.java +++ b/driver/src/test/java/org/neo4j/driver/v1/stress/BlockingCommand.java @@ -16,9 +16,9 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package org.neo4j.driver.internal.util; +package org.neo4j.driver.v1.stress; -public interface BiConsumer +public interface BlockingCommand { - void accept( T t, U u ); + void execute( C context ); } diff --git a/driver/src/test/java/org/neo4j/driver/v1/stress/BlockingFailingQuery.java b/driver/src/test/java/org/neo4j/driver/v1/stress/BlockingFailingQuery.java new file mode 100644 index 0000000000..41756c2624 --- /dev/null +++ b/driver/src/test/java/org/neo4j/driver/v1/stress/BlockingFailingQuery.java @@ -0,0 +1,55 @@ +/* + * Copyright (c) 2002-2017 "Neo Technology," + * Network Engine for Objects in Lund AB [http://neotechnology.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.v1.stress; + +import org.neo4j.driver.v1.AccessMode; +import org.neo4j.driver.v1.Driver; +import org.neo4j.driver.v1.Session; +import org.neo4j.driver.v1.StatementResult; + +import static org.hamcrest.Matchers.is; +import static org.junit.Assert.assertThat; +import static org.junit.Assert.fail; +import static org.neo4j.driver.internal.util.Matchers.arithmeticError; + +public class BlockingFailingQuery extends AbstractBlockingQuery +{ + public BlockingFailingQuery( Driver driver ) + { + super( driver, false ); + } + + @Override + public void execute( C context ) + { + try ( Session session = newSession( AccessMode.READ, context ) ) + { + StatementResult result = session.run( "UNWIND [10, 5, 0] AS x RETURN 10 / x" ); + try + { + result.consume(); + fail( "Exception expected" ); + } + catch ( Exception e ) + { + assertThat( e, is( arithmeticError() ) ); + } + } + } +} diff --git a/driver/src/test/java/org/neo4j/driver/v1/stress/BlockingFailingQueryInTx.java b/driver/src/test/java/org/neo4j/driver/v1/stress/BlockingFailingQueryInTx.java new file mode 100644 index 0000000000..4b41509cc6 --- /dev/null +++ b/driver/src/test/java/org/neo4j/driver/v1/stress/BlockingFailingQueryInTx.java @@ -0,0 +1,60 @@ +/* + * Copyright (c) 2002-2017 "Neo Technology," + * Network Engine for Objects in Lund AB [http://neotechnology.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.v1.stress; + +import org.neo4j.driver.v1.AccessMode; +import org.neo4j.driver.v1.Driver; +import org.neo4j.driver.v1.Session; +import org.neo4j.driver.v1.StatementResult; +import org.neo4j.driver.v1.Transaction; + +import static org.hamcrest.Matchers.is; +import static org.junit.Assert.assertThat; +import static org.junit.Assert.fail; +import static org.neo4j.driver.internal.util.Matchers.arithmeticError; + +public class BlockingFailingQueryInTx extends AbstractBlockingQuery +{ + public BlockingFailingQueryInTx( Driver driver ) + { + super( driver, false ); + } + + @Override + public void execute( C context ) + { + try ( Session session = newSession( AccessMode.READ, context ) ) + { + try ( Transaction tx = beginTransaction( session, context ) ) + { + StatementResult result = tx.run( "UNWIND [10, 5, 0] AS x RETURN 10 / x" ); + + try + { + result.consume(); + fail( "Exception expected" ); + } + catch ( Exception e ) + { + assertThat( e, is( arithmeticError() ) ); + } + } + } + } +} diff --git a/driver/src/test/java/org/neo4j/driver/v1/stress/BlockingReadQuery.java b/driver/src/test/java/org/neo4j/driver/v1/stress/BlockingReadQuery.java new file mode 100644 index 0000000000..0ce2bd4e3a --- /dev/null +++ b/driver/src/test/java/org/neo4j/driver/v1/stress/BlockingReadQuery.java @@ -0,0 +1,57 @@ +/* + * Copyright (c) 2002-2017 "Neo Technology," + * Network Engine for Objects in Lund AB [http://neotechnology.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.v1.stress; + +import java.util.List; + +import org.neo4j.driver.v1.AccessMode; +import org.neo4j.driver.v1.Driver; +import org.neo4j.driver.v1.Record; +import org.neo4j.driver.v1.Session; +import org.neo4j.driver.v1.StatementResult; +import org.neo4j.driver.v1.types.Node; + +import static org.junit.Assert.assertNotNull; +import static org.neo4j.driver.internal.util.Iterables.single; + +public class BlockingReadQuery extends AbstractBlockingQuery +{ + public BlockingReadQuery( Driver driver, boolean useBookmark ) + { + super( driver, useBookmark ); + } + + @Override + public void execute( C context ) + { + try ( Session session = newSession( AccessMode.READ, context ) ) + { + StatementResult result = session.run( "MATCH (n) RETURN n LIMIT 1" ); + List records = result.list(); + if ( !records.isEmpty() ) + { + Record record = single( records ); + Node node = record.get( 0 ).asNode(); + assertNotNull( node ); + } + + context.readCompleted( result.summary() ); + } + } +} diff --git a/driver/src/test/java/org/neo4j/driver/v1/stress/BlockingReadQueryInTx.java b/driver/src/test/java/org/neo4j/driver/v1/stress/BlockingReadQueryInTx.java new file mode 100644 index 0000000000..896327729e --- /dev/null +++ b/driver/src/test/java/org/neo4j/driver/v1/stress/BlockingReadQueryInTx.java @@ -0,0 +1,60 @@ +/* + * Copyright (c) 2002-2017 "Neo Technology," + * Network Engine for Objects in Lund AB [http://neotechnology.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.v1.stress; + +import java.util.List; + +import org.neo4j.driver.v1.AccessMode; +import org.neo4j.driver.v1.Driver; +import org.neo4j.driver.v1.Record; +import org.neo4j.driver.v1.Session; +import org.neo4j.driver.v1.StatementResult; +import org.neo4j.driver.v1.Transaction; +import org.neo4j.driver.v1.types.Node; + +import static org.junit.Assert.assertNotNull; +import static org.neo4j.driver.internal.util.Iterables.single; + +public class BlockingReadQueryInTx extends AbstractBlockingQuery +{ + public BlockingReadQueryInTx( Driver driver, boolean useBookmark ) + { + super( driver, useBookmark ); + } + + @Override + public void execute( C context ) + { + try ( Session session = newSession( AccessMode.READ, context ); + Transaction tx = beginTransaction( session, context ) ) + { + StatementResult result = tx.run( "MATCH (n) RETURN n LIMIT 1" ); + List records = result.list(); + if ( !records.isEmpty() ) + { + Record record = single( records ); + Node node = record.get( 0 ).asNode(); + assertNotNull( node ); + } + + context.readCompleted( result.summary() ); + tx.success(); + } + } +} diff --git a/driver/src/test/java/org/neo4j/driver/v1/stress/BlockingWriteQuery.java b/driver/src/test/java/org/neo4j/driver/v1/stress/BlockingWriteQuery.java new file mode 100644 index 0000000000..a83df83e59 --- /dev/null +++ b/driver/src/test/java/org/neo4j/driver/v1/stress/BlockingWriteQuery.java @@ -0,0 +1,61 @@ +/* + * Copyright (c) 2002-2017 "Neo Technology," + * Network Engine for Objects in Lund AB [http://neotechnology.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.v1.stress; + +import org.neo4j.driver.v1.AccessMode; +import org.neo4j.driver.v1.Driver; +import org.neo4j.driver.v1.Session; +import org.neo4j.driver.v1.StatementResult; + +import static org.junit.Assert.assertEquals; + +public class BlockingWriteQuery extends AbstractBlockingQuery +{ + private AbstractStressIT abstractStressIT; + + public BlockingWriteQuery( AbstractStressIT abstractStressIT, Driver driver, boolean useBookmark ) + { + super( driver, useBookmark ); + this.abstractStressIT = abstractStressIT; + } + + @Override + public void execute( C context ) + { + StatementResult result = null; + + try ( Session session = newSession( AccessMode.WRITE, context ) ) + { + result = session.run( "CREATE ()" ); + } + catch ( Throwable error ) + { + if ( !abstractStressIT.handleWriteFailure( error, context ) ) + { + throw error; + } + } + + if ( result != null ) + { + assertEquals( 1, result.summary().counters().nodesCreated() ); + context.nodeCreated(); + } + } +} diff --git a/driver/src/test/java/org/neo4j/driver/v1/stress/BlockingWriteQueryInTx.java b/driver/src/test/java/org/neo4j/driver/v1/stress/BlockingWriteQueryInTx.java new file mode 100644 index 0000000000..6197329df9 --- /dev/null +++ b/driver/src/test/java/org/neo4j/driver/v1/stress/BlockingWriteQueryInTx.java @@ -0,0 +1,68 @@ +/* + * Copyright (c) 2002-2017 "Neo Technology," + * Network Engine for Objects in Lund AB [http://neotechnology.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.v1.stress; + +import org.neo4j.driver.v1.AccessMode; +import org.neo4j.driver.v1.Driver; +import org.neo4j.driver.v1.Session; +import org.neo4j.driver.v1.StatementResult; +import org.neo4j.driver.v1.Transaction; + +import static org.junit.Assert.assertEquals; + +public class BlockingWriteQueryInTx extends AbstractBlockingQuery +{ + private AbstractStressIT abstractStressIT; + + public BlockingWriteQueryInTx( AbstractStressIT abstractStressIT, Driver driver, boolean useBookmark ) + { + super( driver, useBookmark ); + this.abstractStressIT = abstractStressIT; + } + + @Override + public void execute( C context ) + { + StatementResult result = null; + + try ( Session session = newSession( AccessMode.WRITE, context ) ) + { + try ( Transaction tx = beginTransaction( session, context ) ) + { + result = tx.run( "CREATE ()" ); + tx.success(); + } + + context.setBookmark( session.lastBookmark() ); + } + catch ( Throwable error ) + { + if ( !abstractStressIT.handleWriteFailure( error, context ) ) + { + throw error; + } + } + + if ( result != null ) + { + assertEquals( 1, result.summary().counters().nodesCreated() ); + context.nodeCreated(); + } + } +} diff --git a/driver/src/test/java/org/neo4j/driver/v1/stress/BlockingWriteQueryUsingReadSession.java b/driver/src/test/java/org/neo4j/driver/v1/stress/BlockingWriteQueryUsingReadSession.java new file mode 100644 index 0000000000..9163a44bcc --- /dev/null +++ b/driver/src/test/java/org/neo4j/driver/v1/stress/BlockingWriteQueryUsingReadSession.java @@ -0,0 +1,59 @@ +/* + * Copyright (c) 2002-2017 "Neo Technology," + * Network Engine for Objects in Lund AB [http://neotechnology.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.v1.stress; + +import org.neo4j.driver.v1.AccessMode; +import org.neo4j.driver.v1.Driver; +import org.neo4j.driver.v1.Session; +import org.neo4j.driver.v1.StatementResult; +import org.neo4j.driver.v1.exceptions.ClientException; + +import static org.hamcrest.Matchers.instanceOf; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertThat; +import static org.junit.Assert.fail; + +public class BlockingWriteQueryUsingReadSession extends AbstractBlockingQuery +{ + public BlockingWriteQueryUsingReadSession( Driver driver, boolean useBookmark ) + { + super( driver, useBookmark ); + } + + @Override + public void execute( C context ) + { + StatementResult result = null; + try + { + try ( Session session = newSession( AccessMode.READ, context ) ) + { + result = session.run( "CREATE ()" ); + } + fail( "Exception expected" ); + } + catch ( Exception e ) + { + assertThat( e, instanceOf( ClientException.class ) ); + assertNotNull( result ); + assertEquals( 0, result.summary().counters().nodesCreated() ); + } + } +} diff --git a/driver/src/test/java/org/neo4j/driver/v1/stress/BlockingWriteQueryUsingReadSessionInTx.java b/driver/src/test/java/org/neo4j/driver/v1/stress/BlockingWriteQueryUsingReadSessionInTx.java new file mode 100644 index 0000000000..47d9534caa --- /dev/null +++ b/driver/src/test/java/org/neo4j/driver/v1/stress/BlockingWriteQueryUsingReadSessionInTx.java @@ -0,0 +1,62 @@ +/* + * Copyright (c) 2002-2017 "Neo Technology," + * Network Engine for Objects in Lund AB [http://neotechnology.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.v1.stress; + +import org.neo4j.driver.v1.AccessMode; +import org.neo4j.driver.v1.Driver; +import org.neo4j.driver.v1.Session; +import org.neo4j.driver.v1.StatementResult; +import org.neo4j.driver.v1.Transaction; +import org.neo4j.driver.v1.exceptions.ClientException; + +import static org.hamcrest.Matchers.instanceOf; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertThat; +import static org.junit.Assert.fail; + +public class BlockingWriteQueryUsingReadSessionInTx extends AbstractBlockingQuery +{ + public BlockingWriteQueryUsingReadSessionInTx( Driver driver, boolean useBookmark ) + { + super( driver, useBookmark ); + } + + @Override + public void execute( C context ) + { + StatementResult result = null; + try + { + try ( Session session = newSession( AccessMode.READ, context ); + Transaction tx = beginTransaction( session, context ) ) + { + result = tx.run( "CREATE ()" ); + tx.success(); + } + fail( "Exception expected" ); + } + catch ( Exception e ) + { + assertThat( e, instanceOf( ClientException.class ) ); + assertNotNull( result ); + assertEquals( 0, result.summary().counters().nodesCreated() ); + } + } +} diff --git a/driver/src/test/java/org/neo4j/driver/v1/stress/BlockingWrongQuery.java b/driver/src/test/java/org/neo4j/driver/v1/stress/BlockingWrongQuery.java new file mode 100644 index 0000000000..2ff08afda3 --- /dev/null +++ b/driver/src/test/java/org/neo4j/driver/v1/stress/BlockingWrongQuery.java @@ -0,0 +1,53 @@ +/* + * Copyright (c) 2002-2017 "Neo Technology," + * Network Engine for Objects in Lund AB [http://neotechnology.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.v1.stress; + +import org.neo4j.driver.v1.AccessMode; +import org.neo4j.driver.v1.Driver; +import org.neo4j.driver.v1.Session; + +import static org.hamcrest.Matchers.is; +import static org.junit.Assert.assertThat; +import static org.junit.Assert.fail; +import static org.neo4j.driver.internal.util.Matchers.syntaxError; + +public class BlockingWrongQuery extends AbstractBlockingQuery +{ + public BlockingWrongQuery( Driver driver ) + { + super( driver, false ); + } + + @Override + public void execute( C context ) + { + try ( Session session = newSession( AccessMode.READ, context ) ) + { + try + { + session.run( "RETURN" ).consume(); + fail( "Exception expected" ); + } + catch ( Exception e ) + { + assertThat( e, is( syntaxError( "Unexpected end of input" ) ) ); + } + } + } +} diff --git a/driver/src/test/java/org/neo4j/driver/v1/stress/BlockingWrongQueryInTx.java b/driver/src/test/java/org/neo4j/driver/v1/stress/BlockingWrongQueryInTx.java new file mode 100644 index 0000000000..c1ed5b4fd5 --- /dev/null +++ b/driver/src/test/java/org/neo4j/driver/v1/stress/BlockingWrongQueryInTx.java @@ -0,0 +1,57 @@ +/* + * Copyright (c) 2002-2017 "Neo Technology," + * Network Engine for Objects in Lund AB [http://neotechnology.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.v1.stress; + +import org.neo4j.driver.v1.AccessMode; +import org.neo4j.driver.v1.Driver; +import org.neo4j.driver.v1.Session; +import org.neo4j.driver.v1.Transaction; + +import static org.hamcrest.Matchers.is; +import static org.junit.Assert.assertThat; +import static org.junit.Assert.fail; +import static org.neo4j.driver.internal.util.Matchers.syntaxError; + +public class BlockingWrongQueryInTx extends AbstractBlockingQuery +{ + public BlockingWrongQueryInTx( Driver driver ) + { + super( driver, false ); + } + + @Override + public void execute( C context ) + { + try ( Session session = newSession( AccessMode.READ, context ) ) + { + try ( Transaction tx = beginTransaction( session, context ) ) + { + try + { + tx.run( "RETURN" ).consume(); + fail( "Exception expected" ); + } + catch ( Exception e ) + { + assertThat( e, is( syntaxError( "Unexpected end of input" ) ) ); + } + } + } + } +} diff --git a/driver/src/test/java/org/neo4j/driver/v1/stress/CausalClusteringStressIT.java b/driver/src/test/java/org/neo4j/driver/v1/stress/CausalClusteringStressIT.java index aaa8626acc..89f5d564ba 100644 --- a/driver/src/test/java/org/neo4j/driver/v1/stress/CausalClusteringStressIT.java +++ b/driver/src/test/java/org/neo4j/driver/v1/stress/CausalClusteringStressIT.java @@ -18,240 +18,90 @@ */ package org.neo4j.driver.v1.stress; -import org.junit.After; -import org.junit.Before; import org.junit.Rule; -import org.junit.Test; -import java.lang.management.ManagementFactory; -import java.lang.management.OperatingSystemMXBean; -import java.lang.reflect.Method; import java.net.URI; -import java.util.ArrayList; +import java.util.Arrays; import java.util.HashMap; import java.util.HashSet; import java.util.List; import java.util.Map; import java.util.Set; -import java.util.concurrent.Callable; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ConcurrentMap; -import java.util.concurrent.ExecutorService; -import java.util.concurrent.Executors; -import java.util.concurrent.Future; -import java.util.concurrent.ThreadFactory; -import java.util.concurrent.ThreadLocalRandom; -import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicInteger; import java.util.concurrent.atomic.AtomicLong; -import org.neo4j.driver.internal.logging.DevNullLogger; import org.neo4j.driver.internal.util.ServerVersion; -import org.neo4j.driver.v1.AccessMode; import org.neo4j.driver.v1.AuthToken; -import org.neo4j.driver.v1.Config; import org.neo4j.driver.v1.Driver; -import org.neo4j.driver.v1.GraphDatabase; -import org.neo4j.driver.v1.Logger; -import org.neo4j.driver.v1.Logging; import org.neo4j.driver.v1.Record; import org.neo4j.driver.v1.Session; -import org.neo4j.driver.v1.StatementResult; -import org.neo4j.driver.v1.Transaction; -import org.neo4j.driver.v1.exceptions.ClientException; -import org.neo4j.driver.v1.exceptions.SecurityException; import org.neo4j.driver.v1.exceptions.SessionExpiredException; -import org.neo4j.driver.v1.exceptions.TransientException; -import org.neo4j.driver.v1.types.Node; -import org.neo4j.driver.v1.util.DaemonThreadFactory; +import org.neo4j.driver.v1.summary.ResultSummary; import org.neo4j.driver.v1.util.cc.ClusterMemberRole; import org.neo4j.driver.v1.util.cc.LocalOrRemoteClusterRule; -import static java.util.Collections.newSetFromMap; import static org.hamcrest.Matchers.both; -import static org.hamcrest.Matchers.containsString; -import static org.hamcrest.Matchers.equalTo; import static org.hamcrest.Matchers.greaterThan; import static org.hamcrest.Matchers.greaterThanOrEqualTo; -import static org.hamcrest.Matchers.instanceOf; import static org.hamcrest.Matchers.lessThanOrEqualTo; -import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertNotNull; -import static org.junit.Assert.assertNull; import static org.junit.Assert.assertThat; -import static org.junit.Assert.fail; -import static org.neo4j.driver.internal.util.Iterables.single; -import static org.neo4j.driver.v1.AuthTokens.basic; import static org.neo4j.driver.v1.util.cc.ClusterMember.SIMPLE_SCHEME; -public class CausalClusteringStressIT +public class CausalClusteringStressIT extends AbstractStressIT { - private static final int THREAD_COUNT = Integer.getInteger( "threadCount", 8 ); - private static final int EXECUTION_TIME_SECONDS = Integer.getInteger( "executionTimeSeconds", 30 ); - @Rule public final LocalOrRemoteClusterRule clusterRule = new LocalOrRemoteClusterRule(); - private TrackingDevNullLogging logging; - private ExecutorService executor; - private Driver driver; - - @Before - public void setUp() throws Exception - { - logging = new TrackingDevNullLogging(); - - URI clusterUri = clusterRule.getClusterUri(); - AuthToken authToken = clusterRule.getAuthToken(); - Config config = Config.build().withLogging( logging ).withMaxIdleSessions( THREAD_COUNT ).toConfig(); - driver = GraphDatabase.driver( clusterUri, authToken, config ); - - ThreadFactory threadFactory = new DaemonThreadFactory( getClass().getSimpleName() + "-worker-" ); - executor = Executors.newCachedThreadPool( threadFactory ); - } - - @After - public void tearDown() throws Exception + @Override + URI databaseUri() { - executor.shutdownNow(); - if ( driver != null ) - { - driver.close(); - } + return clusterRule.getClusterUri(); } - @Test - public void basicStressTest() throws Throwable + @Override + AuthToken authToken() { - Context context = new Context(); - List> resultFutures = launchWorkerThreads( context ); - - ResourcesInfo resourcesInfo = sleepAndGetResourcesInfo(); - context.stop(); - - Throwable firstError = null; - for ( Future future : resultFutures ) - { - try - { - assertNull( future.get( 10, TimeUnit.SECONDS ) ); - } - catch ( Throwable error ) - { - firstError = withSuppressed( firstError, error ); - } - } - - if ( firstError != null ) - { - throw firstError; - } - - assertNoFileDescriptorLeak( resourcesInfo.openFileDescriptorCount ); - assertNoLoggersLeak( resourcesInfo.acquiredLoggerNames ); - assertExpectedNumberOfNodesCreated( context.getCreatedNodesCount() ); - assertGoodReadQueryDistribution( context.getReadQueriesByServer() ); - - System.out.println( context.getErrors() ); + return clusterRule.getAuthToken(); } - private List> launchWorkerThreads( Context context ) + @Override + Context createContext() { - List commands = createCommands(); - List> futures = new ArrayList<>(); - - for ( int i = 0; i < THREAD_COUNT; i++ ) - { - Future future = launchWorkerThread( executor, commands, context ); - futures.add( future ); - } - - return futures; + return new Context(); } - private List createCommands() + @Override + List> createTestSpecificBlockingCommands() { - List commands = new ArrayList<>(); - - commands.add( new ReadQuery( driver, false ) ); - commands.add( new ReadQuery( driver, true ) ); - commands.add( new ReadQueryInTx( driver, false ) ); - commands.add( new ReadQueryInTx( driver, true ) ); - commands.add( new WriteQuery( driver, false ) ); - commands.add( new WriteQuery( driver, true ) ); - commands.add( new WriteQueryInTx( driver, false ) ); - commands.add( new WriteQueryInTx( driver, true ) ); - commands.add( new WriteQueryUsingReadSession( driver, false ) ); - commands.add( new WriteQueryUsingReadSession( driver, true ) ); - commands.add( new WriteQueryUsingReadSessionInTx( driver, false ) ); - commands.add( new WriteQueryUsingReadSessionInTx( driver, true ) ); - commands.add( new FailedAuth( clusterRule.getClusterUri(), logging ) ); - - return commands; + return Arrays.asList( + new BlockingWriteQueryUsingReadSession<>( driver, false ), + new BlockingWriteQueryUsingReadSession<>( driver, true ), + new BlockingWriteQueryUsingReadSessionInTx<>( driver, false ), + new BlockingWriteQueryUsingReadSessionInTx<>( driver, true ) + ); } - private static Future launchWorkerThread( final ExecutorService executor, final List commands, - final Context context ) + @Override + boolean handleWriteFailure( Throwable error, Context context ) { - return executor.submit( new Callable() + if ( error instanceof SessionExpiredException ) { - final ThreadLocalRandom random = ThreadLocalRandom.current(); - - @Override - public Void call() throws Exception + boolean isLeaderSwitch = error.getMessage().endsWith( "no longer accepts writes" ); + if ( isLeaderSwitch ) { - while ( !context.isStopped() ) - { - int randomCommandIdx = random.nextInt( commands.size() ); - Command command = commands.get( randomCommandIdx ); - command.execute( context ); - } - return null; + context.leaderSwitch(); + return true; } - } ); - } - - private ResourcesInfo sleepAndGetResourcesInfo() throws InterruptedException - { - int halfSleepSeconds = Math.max( 1, EXECUTION_TIME_SECONDS / 2 ); - TimeUnit.SECONDS.sleep( halfSleepSeconds ); - long openFileDescriptorCount = getOpenFileDescriptorCount(); - Set acquiredLoggerNames = logging.getAcquiredLoggerNames(); - ResourcesInfo resourcesInfo = new ResourcesInfo( openFileDescriptorCount, acquiredLoggerNames ); - TimeUnit.SECONDS.sleep( halfSleepSeconds ); - return resourcesInfo; - } - - private void assertNoFileDescriptorLeak( long previousOpenFileDescriptors ) - { - // number of open file descriptors should not go up for more than 20% - long maxOpenFileDescriptors = (long) (previousOpenFileDescriptors * 1.2); - long currentOpenFileDescriptorCount = getOpenFileDescriptorCount(); - assertThat( "Unexpectedly high number of open file descriptors", - currentOpenFileDescriptorCount, lessThanOrEqualTo( maxOpenFileDescriptors ) ); - } - - private void assertNoLoggersLeak( Set previousAcquiredLoggerNames ) - { - Set currentAcquiredLoggerNames = logging.getAcquiredLoggerNames(); - assertThat( "Unexpected amount of logger instances", - currentAcquiredLoggerNames, equalTo( previousAcquiredLoggerNames ) ); - } - - private void assertExpectedNumberOfNodesCreated( long expectedCount ) - { - try ( Session session = driver.session() ) - { - List records = session.run( "MATCH (n) RETURN count(n) AS nodesCount" ).list(); - assertEquals( 1, records.size() ); - Record record = records.get( 0 ); - long actualCount = record.get( "nodesCount" ).asLong(); - assertEquals( "Unexpected number of nodes in the database", expectedCount, actualCount ); } + return false; } - private void assertGoodReadQueryDistribution( Map readQueriesByServer ) + @Override + void assertExpectedReadQueryDistribution( Context context ) { + Map readQueriesByServer = context.getReadQueriesByServer(); ClusterAddresses clusterAddresses = fetchClusterAddresses( driver ); // before 3.2.0 only read replicas serve reads @@ -278,6 +128,16 @@ private void assertGoodReadQueryDistribution( Map readQueriesByServ readQueriesByServer, clusterAddresses ); } + @Override + void printStats( Context context ) + { + System.out.println( "Nodes read: " + context.getReadNodesCount() ); + System.out.println( "Nodes created: " + context.getCreatedNodesCount() ); + + System.out.println( "Leader switches: " + context.getLeaderSwitchCount() ); + System.out.println( "Bookmark failures: " + context.getBookmarkFailures() ); + } + private static ClusterAddresses fetchClusterAddresses( Driver driver ) { Set followers = new HashSet<>(); @@ -340,31 +200,6 @@ private static void assertAllAddressesServedSimilarAmountOfReadQueries( String a } } - private static long getOpenFileDescriptorCount() - { - try - { - OperatingSystemMXBean osBean = ManagementFactory.getOperatingSystemMXBean(); - Method method = osBean.getClass().getDeclaredMethod( "getOpenFileDescriptorCount" ); - method.setAccessible( true ); - return (long) method.invoke( osBean ); - } - catch ( Throwable t ) - { - return 0; - } - } - - private static Throwable withSuppressed( Throwable firstError, Throwable newError ) - { - if ( firstError == null ) - { - return newError; - } - firstError.addSuppressed( newError ); - return firstError; - } - private static long orderOfMagnitude( long number ) { long result = 1; @@ -376,47 +211,20 @@ private static long orderOfMagnitude( long number ) return result; } - private static class Context + static class Context extends AbstractContext { - volatile boolean stopped; - volatile String bookmark; - final AtomicLong createdNodesCount = new AtomicLong(); final ConcurrentMap readQueriesByServer = new ConcurrentHashMap<>(); - final Errors errors = new Errors(); - - boolean isStopped() - { - return stopped; - } - - void stop() - { - this.stopped = true; - } - - String getBookmark() - { - return bookmark; - } - - void setBookmark( String bookmark ) - { - this.bookmark = bookmark; - } - - void nodeCreated() - { - createdNodesCount.incrementAndGet(); - } + final AtomicInteger leaderSwitches = new AtomicInteger(); - long getCreatedNodesCount() + @Override + public void processSummary( ResultSummary summary ) { - return createdNodesCount.get(); - } + if ( summary == null ) + { + return; + } - void readCompleted( StatementResult result ) - { - String serverAddress = result.summary().server().address(); + String serverAddress = summary.server().address(); AtomicLong count = readQueriesByServer.get( serverAddress ); if ( count == null ) @@ -441,327 +249,14 @@ Map getReadQueriesByServer() return result; } - Errors getErrors() - { - return errors; - } - } - - private static class Errors - { - final AtomicInteger bookmarkFailures = new AtomicInteger(); - final AtomicInteger leaderSwitches = new AtomicInteger(); - - void bookmarkFailed() - { - bookmarkFailures.incrementAndGet(); - } - void leaderSwitch() { leaderSwitches.incrementAndGet(); } - @Override - public String toString() - { - return "Errors{" + - "bookmarkFailures=" + bookmarkFailures + - ", leaderSwitches=" + leaderSwitches + - '}'; - } - } - - private interface Command - { - void execute( Context context ); - } - - private static abstract class BaseQuery implements Command - { - final Driver driver; - final boolean useBookmark; - - BaseQuery( Driver driver, boolean useBookmark ) - { - this.driver = driver; - this.useBookmark = useBookmark; - } - - Session newSession( AccessMode mode, Context context ) - { - if ( useBookmark ) - { - return driver.session( mode, context.getBookmark() ); - } - return driver.session( mode ); - } - - Transaction beginTransaction( Session session, Context context ) - { - if ( useBookmark ) - { - while ( true ) - { - try - { - return session.beginTransaction(); - } - catch ( TransientException e ) - { - context.getErrors().bookmarkFailed(); - } - } - } - - return session.beginTransaction(); - } - - void handleWriteFailure( SessionExpiredException e, Context context ) - { - boolean isLeaderSwitch = e.getMessage().endsWith( "no longer accepts writes" ); - if ( isLeaderSwitch ) - { - context.getErrors().leaderSwitch(); - } - else - { - throw e; - } - } - } - - private static class ReadQuery extends BaseQuery - { - ReadQuery( Driver driver, boolean useBookmark ) - { - super( driver, useBookmark ); - } - - @Override - public void execute( Context context ) - { - try ( Session session = newSession( AccessMode.READ, context ) ) - { - StatementResult result = session.run( "MATCH (n) RETURN n LIMIT 1" ); - List records = result.list(); - if ( !records.isEmpty() ) - { - Record record = single( records ); - Node node = record.get( 0 ).asNode(); - assertNotNull( node ); - } - - context.readCompleted( result ); - } - } - } - - private static class ReadQueryInTx extends BaseQuery - { - ReadQueryInTx( Driver driver, boolean useBookmark ) - { - super( driver, useBookmark ); - } - - @Override - public void execute( Context context ) - { - try ( Session session = newSession( AccessMode.READ, context ); - Transaction tx = beginTransaction( session, context ) ) - { - StatementResult result = tx.run( "MATCH (n) RETURN n LIMIT 1" ); - List records = result.list(); - if ( !records.isEmpty() ) - { - Record record = single( records ); - Node node = record.get( 0 ).asNode(); - assertNotNull( node ); - } - - context.readCompleted( result ); - tx.success(); - } - } - } - - private static class WriteQuery extends BaseQuery - { - WriteQuery( Driver driver, boolean useBookmark ) - { - super( driver, useBookmark ); - } - - @Override - public void execute( Context context ) - { - StatementResult result = null; - - try ( Session session = newSession( AccessMode.WRITE, context ) ) - { - result = session.run( "CREATE ()" ); - } - catch ( SessionExpiredException e ) - { - handleWriteFailure( e, context ); - } - - if ( result != null ) - { - assertEquals( 1, result.summary().counters().nodesCreated() ); - context.nodeCreated(); - } - } - } - - private static class WriteQueryInTx extends BaseQuery - { - WriteQueryInTx( Driver driver, boolean useBookmark ) - { - super( driver, useBookmark ); - } - - @Override - public void execute( Context context ) - { - StatementResult result = null; - - try ( Session session = newSession( AccessMode.WRITE, context ) ) - { - try ( Transaction tx = beginTransaction( session, context ) ) - { - result = tx.run( "CREATE ()" ); - tx.success(); - } - - context.setBookmark( session.lastBookmark() ); - } - catch ( SessionExpiredException e ) - { - handleWriteFailure( e, context ); - } - - if ( result != null ) - { - assertEquals( 1, result.summary().counters().nodesCreated() ); - context.nodeCreated(); - } - } - } - - private static class WriteQueryUsingReadSession extends BaseQuery - { - WriteQueryUsingReadSession( Driver driver, boolean useBookmark ) - { - super( driver, useBookmark ); - } - - @Override - public void execute( Context context ) - { - StatementResult result = null; - try - { - try ( Session session = newSession( AccessMode.READ, context ) ) - { - result = session.run( "CREATE ()" ); - } - fail( "Exception expected" ); - } - catch ( Exception e ) - { - assertThat( e, instanceOf( ClientException.class ) ); - assertNotNull( result ); - assertEquals( 0, result.summary().counters().nodesCreated() ); - } - } - } - - private static class WriteQueryUsingReadSessionInTx extends BaseQuery - { - WriteQueryUsingReadSessionInTx( Driver driver, boolean useBookmark ) - { - super( driver, useBookmark ); - } - - @Override - public void execute( Context context ) - { - StatementResult result = null; - try - { - try ( Session session = newSession( AccessMode.READ, context ); - Transaction tx = beginTransaction( session, context ) ) - { - result = tx.run( "CREATE ()" ); - tx.success(); - } - fail( "Exception expected" ); - } - catch ( Exception e ) - { - assertThat( e, instanceOf( ClientException.class ) ); - assertNotNull( result ); - assertEquals( 0, result.summary().counters().nodesCreated() ); - } - } - } - - private static class FailedAuth implements Command - { - final URI clusterUri; - final Logging logging; - - FailedAuth( URI clusterUri, Logging logging ) - { - this.clusterUri = clusterUri; - this.logging = logging; - } - - @Override - public void execute( Context context ) - { - Config config = Config.build().withLogging( logging ).toConfig(); - - try - { - GraphDatabase.driver( clusterUri, basic( "wrongUsername", "wrongPassword" ), config ); - fail( "Exception expected" ); - } - catch ( Exception e ) - { - assertThat( e, instanceOf( SecurityException.class ) ); - assertThat( e.getMessage(), containsString( "authentication failure" ) ); - } - } - } - - private static class ResourcesInfo - { - final long openFileDescriptorCount; - final Set acquiredLoggerNames; - - ResourcesInfo( long openFileDescriptorCount, Set acquiredLoggerNames ) - { - this.openFileDescriptorCount = openFileDescriptorCount; - this.acquiredLoggerNames = acquiredLoggerNames; - } - } - - private static class TrackingDevNullLogging implements Logging - { - private final Set acquiredLoggerNames = newSetFromMap( new ConcurrentHashMap() ); - - @Override - public Logger getLog( String name ) - { - acquiredLoggerNames.add( name ); - return DevNullLogger.DEV_NULL_LOGGER; - } - - Set getAcquiredLoggerNames() + int getLeaderSwitchCount() { - return new HashSet<>( acquiredLoggerNames ); + return leaderSwitches.get(); } } diff --git a/driver/src/test/java/org/neo4j/driver/v1/stress/DriverStresser.java b/driver/src/test/java/org/neo4j/driver/v1/stress/DriverStresser.java deleted file mode 100644 index e00ba59b3b..0000000000 --- a/driver/src/test/java/org/neo4j/driver/v1/stress/DriverStresser.java +++ /dev/null @@ -1,152 +0,0 @@ -/* - * Copyright (c) 2002-2017 "Neo Technology," - * Network Engine for Objects in Lund AB [http://neotechnology.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.v1.stress; - -import java.util.ArrayList; -import java.util.List; -import java.util.concurrent.Callable; -import java.util.concurrent.ExecutorService; -import java.util.concurrent.Executors; -import java.util.concurrent.Future; -import java.util.concurrent.TimeUnit; - -import org.neo4j.driver.v1.Driver; -import org.neo4j.driver.v1.GraphDatabase; -import org.neo4j.driver.v1.Session; -import org.neo4j.driver.v1.StatementResult; -import org.neo4j.driver.v1.Value; -import org.neo4j.driver.v1.util.Neo4jRunner; -import org.neo4j.driver.v1.util.Neo4jSettings; - -import static org.neo4j.driver.v1.Values.parameters; - -public class DriverStresser -{ - - private static Neo4jRunner server; - private static Driver driver; - - public static void main( String... args ) throws Throwable - { - int iterations = 100_000; - - bench( iterations, 1, 10_000 ); - bench( (long) iterations / 2, 2, 10_000 ); - bench( (long) iterations / 4, 4, 10_000 ); - bench( (long) iterations / 8, 8, 10_000 ); - bench( (long) iterations / 16, 16, 10_000 ); - bench( (long) iterations / 32, 32, 10_000 ); - } - - public static void setup() throws Exception - { - server = Neo4jRunner.getOrCreateGlobalRunner(); - server.ensureRunning( Neo4jSettings.TEST_SETTINGS ); - driver = GraphDatabase.driver( "bolt://localhost:7687" ); - } - - static class Worker - { - private final Session session; - - public Worker() - { - session = driver.session(); - } - - public int operation() - { - String statement = "RETURN 1 AS n"; // = "CREATE (a {name:{n}}) RETURN a.name"; - Value parameters = parameters(); // = Values.parameters( "n", "Bob" ); - - int total = 0; - StatementResult result = session.run( statement, parameters ); - while ( result.hasNext() ) - { - total += result.next().get( "n" ).asInt(); - } - return total; - } - } - - public static void tearDown() throws Exception - { - driver.close(); - server.stopNeo4j(); - } - - - private static void bench( long iterations, int concurrency, long warmupIterations ) throws Exception - { - ExecutorService executorService = Executors.newFixedThreadPool( concurrency ); - - setup(); - try - { - // Warmup - awaitAll( executorService.invokeAll( workers( warmupIterations, concurrency ) ) ); - - long start = System.nanoTime(); - List> futures = executorService.invokeAll( workers( iterations, concurrency ) ); - awaitAll( futures ); - long delta = System.nanoTime() - start; - - System.out.printf( "With %d threads: %s ops/s%n", - concurrency, (iterations * concurrency) / (delta / 1_000_000_000.0) ); - } - finally - { - tearDown(); - } - - executorService.shutdownNow(); - executorService.awaitTermination( 10, TimeUnit.SECONDS ); - } - - private static void awaitAll( List> futures ) throws Exception - { - for ( Future future : futures ) - { - future.get(); - } - } - - private static List> workers( final long iterations, final int numWorkers ) - { - List> workers = new ArrayList<>(); - for ( int i = 0; i < numWorkers; i++ ) - { - final Worker worker = new Worker(); - workers.add( new Callable() - { - @Override - public Object call() throws Exception - { - int dontRemoveMyCode = 0; - for ( int i = 0; i < iterations; i++ ) - { - dontRemoveMyCode += worker.operation(); - } - return dontRemoveMyCode; - } - } ); - } - return workers; - } -} diff --git a/driver/src/test/java/org/neo4j/driver/v1/stress/FailedAuth.java b/driver/src/test/java/org/neo4j/driver/v1/stress/FailedAuth.java new file mode 100644 index 0000000000..dd490917a9 --- /dev/null +++ b/driver/src/test/java/org/neo4j/driver/v1/stress/FailedAuth.java @@ -0,0 +1,61 @@ +/* + * Copyright (c) 2002-2017 "Neo Technology," + * Network Engine for Objects in Lund AB [http://neotechnology.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.v1.stress; + +import java.net.URI; + +import org.neo4j.driver.v1.Config; +import org.neo4j.driver.v1.GraphDatabase; +import org.neo4j.driver.v1.Logging; +import org.neo4j.driver.v1.exceptions.SecurityException; + +import static org.hamcrest.Matchers.containsString; +import static org.hamcrest.Matchers.instanceOf; +import static org.junit.Assert.assertThat; +import static org.junit.Assert.fail; +import static org.neo4j.driver.v1.AuthTokens.basic; + +public class FailedAuth implements BlockingCommand +{ + private final URI clusterUri; + private final Logging logging; + + public FailedAuth( URI clusterUri, Logging logging ) + { + this.clusterUri = clusterUri; + this.logging = logging; + } + + @Override + public void execute( C context ) + { + Config config = Config.build().withLogging( logging ).toConfig(); + + try + { + GraphDatabase.driver( clusterUri, basic( "wrongUsername", "wrongPassword" ), config ); + fail( "Exception expected" ); + } + catch ( Exception e ) + { + assertThat( e, instanceOf( SecurityException.class ) ); + assertThat( e.getMessage(), containsString( "authentication failure" ) ); + } + } +} diff --git a/driver/src/test/java/org/neo4j/driver/v1/stress/SingleInstanceStressIT.java b/driver/src/test/java/org/neo4j/driver/v1/stress/SingleInstanceStressIT.java new file mode 100644 index 0000000000..0d8cfec886 --- /dev/null +++ b/driver/src/test/java/org/neo4j/driver/v1/stress/SingleInstanceStressIT.java @@ -0,0 +1,115 @@ +/* + * Copyright (c) 2002-2017 "Neo Technology," + * Network Engine for Objects in Lund AB [http://neotechnology.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.v1.stress; + +import org.junit.Rule; + +import java.net.URI; +import java.util.Collections; +import java.util.List; +import java.util.concurrent.atomic.AtomicLong; + +import org.neo4j.driver.v1.AuthToken; +import org.neo4j.driver.v1.summary.ResultSummary; +import org.neo4j.driver.v1.util.TestNeo4j; + +import static org.hamcrest.Matchers.greaterThan; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertThat; + +public class SingleInstanceStressIT extends AbstractStressIT +{ + @Rule + public final TestNeo4j neo4j = new TestNeo4j(); + + @Override + URI databaseUri() + { + return neo4j.uri(); + } + + @Override + AuthToken authToken() + { + return neo4j.authToken(); + } + + @Override + Context createContext() + { + return new Context( neo4j.address().toString() ); + } + + @Override + List> createTestSpecificBlockingCommands() + { + return Collections.emptyList(); + } + + @Override + boolean handleWriteFailure( Throwable error, Context context ) + { + // no write failures expected + return false; + } + + @Override + void assertExpectedReadQueryDistribution( Context context ) + { + assertThat( context.getReadQueryCount(), greaterThan( 0L ) ); + } + + @Override + void printStats( A context ) + { + System.out.println( "Nodes read: " + context.getReadNodesCount() ); + System.out.println( "Nodes created: " + context.getCreatedNodesCount() ); + + System.out.println( "Bookmark failures: " + context.getBookmarkFailures() ); + } + + static class Context extends AbstractContext + { + final String expectedAddress; + final AtomicLong readQueries = new AtomicLong(); + + Context( String expectedAddress ) + { + this.expectedAddress = expectedAddress; + } + + @Override + public void processSummary( ResultSummary summary ) + { + if ( summary == null ) + { + return; + } + + String address = summary.server().address(); + assertEquals( expectedAddress, address ); + readQueries.incrementAndGet(); + } + + long getReadQueryCount() + { + return readQueries.get(); + } + } +} diff --git a/driver/src/test/java/org/neo4j/driver/v1/util/Neo4jRunner.java b/driver/src/test/java/org/neo4j/driver/v1/util/Neo4jRunner.java index 5d6e50c905..f776d16048 100644 --- a/driver/src/test/java/org/neo4j/driver/v1/util/Neo4jRunner.java +++ b/driver/src/test/java/org/neo4j/driver/v1/util/Neo4jRunner.java @@ -94,7 +94,7 @@ private Neo4jRunner() throws IOException } } - public void ensureRunning( Neo4jSettings neo4jSettings ) throws IOException, InterruptedException + public void ensureRunning( Neo4jSettings neo4jSettings ) { ServerStatus status = serverStatus(); switch( status ) @@ -156,7 +156,7 @@ private void installNeo4j() throws IOException updateServerSettingsFile(); } - public void startNeo4j() throws IOException + public void startNeo4j() { debug( "Starting server..." ); executeCommand( "neoctrl-create-user", HOME_DIR, USER, PASSWORD ); @@ -164,7 +164,7 @@ public void startNeo4j() throws IOException debug( "Server started." ); } - public synchronized void stopNeo4j() throws IOException + public synchronized void stopNeo4j() { if( serverStatus() == ServerStatus.OFFLINE ) { @@ -177,7 +177,7 @@ public synchronized void stopNeo4j() throws IOException debug( "Server stopped." ); } - public void killNeo4j() throws IOException + public void killNeo4j() { if ( serverStatus() == ServerStatus.OFFLINE ) { @@ -190,7 +190,7 @@ public void killNeo4j() throws IOException debug( "Server killed." ); } - public void forceToRestart() throws IOException + public void forceToRestart() { stopNeo4j(); startNeo4j(); @@ -198,9 +198,8 @@ public void forceToRestart() throws IOException /** * Restart the server with default testing server configuration - * @throws IOException */ - public void restartNeo4j() throws IOException + public void restartNeo4j() { restartNeo4j( Neo4jSettings.TEST_SETTINGS ); } @@ -208,9 +207,8 @@ public void restartNeo4j() throws IOException /** * Will only restart the server if any configuration changes happens * @param neo4jSettings - * @throws IOException */ - public void restartNeo4j( Neo4jSettings neo4jSettings ) throws IOException + public void restartNeo4j( Neo4jSettings neo4jSettings ) { if( updateServerSettings( neo4jSettings ) ) // needs to update server setting files { diff --git a/driver/src/test/java/org/neo4j/driver/v1/util/TestNeo4j.java b/driver/src/test/java/org/neo4j/driver/v1/util/TestNeo4j.java index 90c250628d..5bdcbc5820 100644 --- a/driver/src/test/java/org/neo4j/driver/v1/util/TestNeo4j.java +++ b/driver/src/test/java/org/neo4j/driver/v1/util/TestNeo4j.java @@ -83,17 +83,17 @@ public Driver driver() return runner.driver(); } - public void restartDb() throws Exception + public void restartDb() { runner.restartNeo4j(); } - public void forceRestartDb() throws Exception + public void forceRestartDb() { runner.forceToRestart(); } - public void restartDb( Neo4jSettings neo4jSettings ) throws Exception + public void restartDb( Neo4jSettings neo4jSettings ) { runner.restartNeo4j( neo4jSettings ); } @@ -161,17 +161,17 @@ public void ensureProcedures( String jarName ) throws IOException } } - public void startDb() throws IOException + public void startDb() { runner.startNeo4j(); } - public void stopDb() throws IOException + public void stopDb() { runner.stopNeo4j(); } - public void killDb() throws IOException + public void killDb() { runner.killNeo4j(); } diff --git a/driver/src/test/java/org/neo4j/driver/v1/util/TestNeo4jSession.java b/driver/src/test/java/org/neo4j/driver/v1/util/TestNeo4jSession.java index e171f7d937..6f1c145cb0 100644 --- a/driver/src/test/java/org/neo4j/driver/v1/util/TestNeo4jSession.java +++ b/driver/src/test/java/org/neo4j/driver/v1/util/TestNeo4jSession.java @@ -22,9 +22,9 @@ import org.junit.runners.model.Statement; import java.util.Map; +import java.util.concurrent.CompletionStage; import org.neo4j.driver.v1.Record; -import org.neo4j.driver.v1.Response; import org.neo4j.driver.v1.Session; import org.neo4j.driver.v1.StatementResult; import org.neo4j.driver.v1.StatementResultCursor; @@ -98,13 +98,13 @@ public void close() } @Override - public Response closeAsync() + public CompletionStage closeAsync() { throw new UnsupportedOperationException( "Disallowed on this test session" ); } @Override - public Response runAsync( String statement, Map params ) + public CompletionStage runAsync( String statement, Map params ) { return realSession.runAsync( statement, params ); } @@ -123,7 +123,7 @@ public Transaction beginTransaction( String bookmark ) } @Override - public Response beginTransactionAsync() + public CompletionStage beginTransactionAsync() { return realSession.beginTransactionAsync(); } @@ -135,7 +135,7 @@ public T readTransaction( TransactionWork work ) } @Override - public Response readTransactionAsync( TransactionWork> work ) + public CompletionStage readTransactionAsync( TransactionWork> work ) { return realSession.readTransactionAsync( work ); } @@ -147,7 +147,7 @@ public T writeTransaction( TransactionWork work ) } @Override - public Response writeTransactionAsync( TransactionWork> work ) + public CompletionStage writeTransactionAsync( TransactionWork> work ) { return realSession.writeTransactionAsync( work ); } @@ -178,7 +178,7 @@ public StatementResult run( String statementText, Value parameters ) } @Override - public Response runAsync( String statementText, Value parameters ) + public CompletionStage runAsync( String statementText, Value parameters ) { return realSession.runAsync( statementText, parameters ); } @@ -190,7 +190,7 @@ public StatementResult run( String statementText, Record parameters ) } @Override - public Response runAsync( String statementTemplate, Record statementParameters ) + public CompletionStage runAsync( String statementTemplate, Record statementParameters ) { return realSession.runAsync( statementTemplate, statementParameters ); } @@ -202,7 +202,7 @@ public StatementResult run( String statementTemplate ) } @Override - public Response runAsync( String statementTemplate ) + public CompletionStage runAsync( String statementTemplate ) { return realSession.runAsync( statementTemplate ); } @@ -214,7 +214,7 @@ public StatementResult run( org.neo4j.driver.v1.Statement statement ) } @Override - public Response runAsync( org.neo4j.driver.v1.Statement statement ) + public CompletionStage runAsync( org.neo4j.driver.v1.Statement statement ) { return realSession.runAsync( statement ); } diff --git a/driver/src/test/java/org/neo4j/driver/v1/util/TestUtil.java b/driver/src/test/java/org/neo4j/driver/v1/util/TestUtil.java index 97b244778e..1b15faf947 100644 --- a/driver/src/test/java/org/neo4j/driver/v1/util/TestUtil.java +++ b/driver/src/test/java/org/neo4j/driver/v1/util/TestUtil.java @@ -22,6 +22,7 @@ import java.util.ArrayList; import java.util.List; +import java.util.concurrent.CompletionStage; import java.util.concurrent.ExecutionException; import java.util.concurrent.Future; import java.util.concurrent.TimeoutException; @@ -36,26 +37,23 @@ private TestUtil() { } - public static > T get( F future ) + public static List awaitAll( List> stages ) { - if ( !future.isDone() ) + List result = new ArrayList<>(); + for ( CompletionStage stage : stages ) { - throw new IllegalArgumentException( "Given future is not yet completed" ); + result.add( await( stage ) ); } - return await( future ); + return result; } - public static > List awaitAll( List futures ) + public static T await( CompletionStage stage ) { - List result = new ArrayList<>(); - for ( F future : futures ) - { - result.add( await( future ) ); - } - return result; + Future future = stage.toCompletableFuture(); + return await( future ); } - public static > T await( F future ) + public static > T await( U future ) { try { diff --git a/examples/pom.xml b/examples/pom.xml index 339035c39b..7f718a4123 100644 --- a/examples/pom.xml +++ b/examples/pom.xml @@ -104,8 +104,8 @@ maven-compiler-plugin 2.3.2 - 1.7 - 1.7 + ${java.version} + ${java.version} diff --git a/pom.xml b/pom.xml index b27631cbb5..7f63bb2576 100644 --- a/pom.xml +++ b/pom.xml @@ -5,6 +5,7 @@ UTF-8 UTF-8 + 1.8 org.neo4j.driver @@ -125,8 +126,8 @@ maven-compiler-plugin 2.3.2 - 1.7 - 1.7 + ${java.version} + ${java.version} @@ -156,7 +157,7 @@ ]]> - + --allow-script-in-comments