Skip to content

Commit 390f59e

Browse files
authored
Merge pull request #584 from zhenlineo/2.0-stress-tests
Added stress tests for reactive sessions
2 parents 488e33d + 4615152 commit 390f59e

12 files changed

+796
-1
lines changed
Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
/*
2+
* Copyright (c) 2002-2019 "Neo4j,"
3+
* Neo4j Sweden AB [http://neo4j.com]
4+
*
5+
* This file is part of Neo4j.
6+
*
7+
* Licensed under the Apache License, Version 2.0 (the "License");
8+
* you may not use this file except in compliance with the License.
9+
* You may obtain a copy of the License at
10+
*
11+
* http://www.apache.org/licenses/LICENSE-2.0
12+
*
13+
* Unless required by applicable law or agreed to in writing, software
14+
* distributed under the License is distributed on an "AS IS" BASIS,
15+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
16+
* See the License for the specific language governing permissions and
17+
* limitations under the License.
18+
*/
19+
package org.neo4j.driver.stress;
20+
21+
import org.neo4j.driver.AccessMode;
22+
import org.neo4j.driver.Driver;
23+
import org.neo4j.driver.reactive.RxSession;
24+
25+
public abstract class AbstractRxQuery<C extends AbstractContext> implements RxCommand<C>
26+
{
27+
protected final Driver driver;
28+
protected final boolean useBookmark;
29+
30+
public AbstractRxQuery( Driver driver, boolean useBookmark )
31+
{
32+
this.driver = driver;
33+
this.useBookmark = useBookmark;
34+
}
35+
36+
public RxSession newSession( AccessMode mode, C context )
37+
{
38+
if ( useBookmark )
39+
{
40+
return driver.rxSession( t -> t.withDefaultAccessMode( mode ).withBookmarks( context.getBookmark() ) );
41+
}
42+
return driver.rxSession( t -> t.withDefaultAccessMode( mode ) );
43+
}
44+
}

driver/src/test/java/org/neo4j/driver/stress/AbstractStressTestBase.java

Lines changed: 140 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,12 +22,16 @@
2222
import org.junit.jupiter.api.AfterEach;
2323
import org.junit.jupiter.api.BeforeEach;
2424
import org.junit.jupiter.api.Test;
25+
import org.reactivestreams.Publisher;
26+
import reactor.core.publisher.Flux;
2527

2628
import java.lang.management.ManagementFactory;
2729
import java.lang.management.OperatingSystemMXBean;
2830
import java.lang.reflect.Method;
2931
import java.net.URI;
32+
import java.time.Duration;
3033
import java.util.ArrayList;
34+
import java.util.Arrays;
3135
import java.util.HashMap;
3236
import java.util.HashSet;
3337
import java.util.List;
@@ -61,8 +65,12 @@
6165
import org.neo4j.driver.async.StatementResultCursor;
6266
import org.neo4j.driver.internal.InternalDriver;
6367
import org.neo4j.driver.internal.logging.DevNullLogger;
68+
import org.neo4j.driver.internal.util.EnabledOnNeo4jWith;
6469
import org.neo4j.driver.internal.util.Futures;
6570
import org.neo4j.driver.internal.util.Iterables;
71+
import org.neo4j.driver.internal.util.Neo4jFeature;
72+
import org.neo4j.driver.reactive.RxSession;
73+
import org.neo4j.driver.reactive.RxTransaction;
6674
import org.neo4j.driver.types.Node;
6775
import org.neo4j.driver.util.DaemonThreadFactory;
6876

@@ -88,6 +96,7 @@ abstract class AbstractStressTestBase<C extends AbstractContext>
8896

8997
private static final int BIG_DATA_TEST_NODE_COUNT = Integer.getInteger( "bigDataTestNodeCount", 30_000 );
9098
private static final int BIG_DATA_TEST_BATCH_SIZE = Integer.getInteger( "bigDataTestBatchSize", 10_000 );
99+
private static final Duration DEFAULT_BLOCKING_TIME_OUT = Duration.ofMinutes( 5 );
91100

92101
private LoggerNameTrackingLogging logging;
93102
private ExecutorService executor;
@@ -115,7 +124,6 @@ void setUp()
115124
@AfterEach
116125
void tearDown()
117126
{
118-
System.out.println( driver.metrics() );
119127
executor.shutdownNow();
120128
if ( driver != null )
121129
{
@@ -136,6 +144,13 @@ void asyncApiStressTest() throws Throwable
136144
runStressTest( this::launchAsyncWorkerThreads );
137145
}
138146

147+
@Test
148+
@EnabledOnNeo4jWith( Neo4jFeature.BOLT_V4 )
149+
void rxApiStressTest() throws Throwable
150+
{
151+
runStressTest( this::launchRxWorkerThreads );
152+
}
153+
139154
@Test
140155
void blockingApiBigDataTest()
141156
{
@@ -150,6 +165,14 @@ void asyncApiBigDataTest() throws Throwable
150165
readNodesAsync( driver, bookmark, BIG_DATA_TEST_NODE_COUNT );
151166
}
152167

168+
@Test
169+
@EnabledOnNeo4jWith( Neo4jFeature.BOLT_V4 )
170+
void rxApiBigDataTest() throws Throwable
171+
{
172+
String bookmark = createNodesRx( bigDataTestBatchCount(), BIG_DATA_TEST_BATCH_SIZE, driver );
173+
readNodesRx( driver, bookmark, BIG_DATA_TEST_NODE_COUNT );
174+
}
175+
153176
private void runStressTest( Function<C,List<Future<?>>> threadLauncher ) throws Throwable
154177
{
155178
C context = createContext();
@@ -252,6 +275,71 @@ private Future<Void> launchBlockingWorkerThread( ExecutorService executor, List<
252275
} );
253276
}
254277

278+
private List<Future<?>> launchRxWorkerThreads( C context )
279+
{
280+
List<RxCommand<C>> commands = createRxCommands();
281+
List<Future<?>> futures = new ArrayList<>();
282+
283+
for ( int i = 0; i < THREAD_COUNT; i++ )
284+
{
285+
Future<Void> future = launchRxWorkerThread( executor, commands, context );
286+
futures.add( future );
287+
}
288+
return futures;
289+
}
290+
291+
private List<RxCommand<C>> createRxCommands()
292+
{
293+
return Arrays.asList(
294+
new RxReadQuery<>( driver, false ),
295+
new RxReadQuery<>( driver, true ),
296+
297+
new RxWriteQuery<>( this, driver, false ),
298+
new RxWriteQuery<>( this, driver, true ),
299+
300+
new RxReadQueryInTx<>( driver, false ),
301+
new RxReadQueryInTx<>( driver, true ),
302+
303+
new RxWriteQueryInTx<>( this, driver, false ),
304+
new RxWriteQueryInTx<>( this, driver, true ),
305+
306+
new RxReadQueryWithRetries<>( driver, false ),
307+
new RxReadQueryWithRetries<>( driver, false ),
308+
309+
new RxWriteQueryWithRetries<>( this, driver, false ),
310+
new RxWriteQueryWithRetries<>( this, driver, true ),
311+
312+
new RxFailingQuery<>( driver ),
313+
new RxFailingQueryInTx<>( driver ),
314+
new RxFailingQueryWithRetries<>( driver )
315+
);
316+
}
317+
318+
private Future<Void> launchRxWorkerThread( ExecutorService executor, List<RxCommand<C>> commands, C context )
319+
{
320+
return executor.submit( () ->
321+
{
322+
while ( !context.isStopped() )
323+
{
324+
CompletableFuture<Void> allCommands = executeRxCommands( context, commands, ASYNC_BATCH_SIZE );
325+
assertNull( allCommands.get() );
326+
}
327+
return null;
328+
} );
329+
}
330+
331+
private CompletableFuture<Void> executeRxCommands( C context, List<RxCommand<C>> commands, int count )
332+
{
333+
CompletableFuture<Void>[] executions = new CompletableFuture[count];
334+
for ( int i = 0; i < count; i++ )
335+
{
336+
RxCommand<C> command = randomOf( commands );
337+
CompletionStage<Void> execution = command.execute( context );
338+
executions[i] = execution.toCompletableFuture();
339+
}
340+
return CompletableFuture.allOf( executions );
341+
}
342+
255343
private List<Future<?>> launchAsyncWorkerThreads( C context )
256344
{
257345
List<AsyncCommand<C>> commands = createAsyncCommands();
@@ -529,6 +617,57 @@ private static void readNodesAsync( Driver driver, String bookmark, int expected
529617
System.out.println( "Reading nodes with async API took: " + NANOSECONDS.toMillis( end - start ) + "ms" );
530618
}
531619

620+
private String createNodesRx( int batchCount, int batchSize, InternalDriver driver )
621+
{
622+
long start = System.nanoTime();
623+
624+
RxSession session = driver.rxSession();
625+
626+
Flux.concat( Flux.range( 0, batchCount ).map( batchIndex ->
627+
session.writeTransaction( tx -> createNodesInTxRx( tx, batchIndex, batchSize ) )
628+
) ).blockLast( DEFAULT_BLOCKING_TIME_OUT ); // throw any error if happened
629+
630+
long end = System.nanoTime();
631+
System.out.println( "Node creation with reactive API took: " + NANOSECONDS.toMillis( end - start ) + "ms" );
632+
633+
return session.lastBookmark();
634+
}
635+
636+
private Publisher<Void> createNodesInTxRx( RxTransaction tx, int batchIndex, int batchSize )
637+
{
638+
return Flux.concat( Flux.range( 0, batchSize ).map( index -> batchIndex * batchSize + index ).map( nodeIndex -> {
639+
Statement statement = createNodeInTxStatement( nodeIndex );
640+
return Flux.from( tx.run( statement ).summary() ).then(); // As long as there is no error
641+
} ) );
642+
}
643+
644+
private void readNodesRx( InternalDriver driver, String bookmark, int expectedNodeCount )
645+
{
646+
long start = System.nanoTime();
647+
648+
RxSession session = driver.rxSession( t -> t.withBookmarks( bookmark ) );
649+
AtomicInteger nodesSeen = new AtomicInteger();
650+
651+
Publisher<Void> readQuery = session.readTransaction( tx -> Flux.from( tx.run( "MATCH (n:Node) RETURN n" ).records() ).doOnNext( record -> {
652+
Node node = record.get( 0 ).asNode();
653+
nodesSeen.incrementAndGet();
654+
655+
List<String> labels = Iterables.asList( node.labels() );
656+
assertEquals( 2, labels.size() );
657+
assertTrue( labels.contains( "Test" ) );
658+
assertTrue( labels.contains( "Node" ) );
659+
660+
verifyNodeProperties( node );
661+
} ).then() );
662+
663+
Flux.from( readQuery ).blockLast( DEFAULT_BLOCKING_TIME_OUT );
664+
665+
assertEquals( expectedNodeCount, nodesSeen.get() );
666+
667+
long end = System.nanoTime();
668+
System.out.println( "Reading nodes with async API took: " + NANOSECONDS.toMillis( end - start ) + "ms" );
669+
}
670+
532671
private static Void createNodesInTx( Transaction tx, int batchIndex, int batchSize )
533672
{
534673
for ( int index = 0; index < batchSize; index++ )
Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
/*
2+
* Copyright (c) 2002-2019 "Neo4j,"
3+
* Neo4j Sweden AB [http://neo4j.com]
4+
*
5+
* This file is part of Neo4j.
6+
*
7+
* Licensed under the Apache License, Version 2.0 (the "License");
8+
* you may not use this file except in compliance with the License.
9+
* You may obtain a copy of the License at
10+
*
11+
* http://www.apache.org/licenses/LICENSE-2.0
12+
*
13+
* Unless required by applicable law or agreed to in writing, software
14+
* distributed under the License is distributed on an "AS IS" BASIS,
15+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
16+
* See the License for the specific language governing permissions and
17+
* limitations under the License.
18+
*/
19+
package org.neo4j.driver.stress;
20+
21+
import java.util.concurrent.CompletionStage;
22+
23+
public interface RxCommand<C extends AbstractContext>
24+
{
25+
CompletionStage<Void> execute( C context );
26+
}
Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,61 @@
1+
/*
2+
* Copyright (c) 2002-2019 "Neo4j,"
3+
* Neo4j Sweden AB [http://neo4j.com]
4+
*
5+
* This file is part of Neo4j.
6+
*
7+
* Licensed under the Apache License, Version 2.0 (the "License");
8+
* you may not use this file except in compliance with the License.
9+
* You may obtain a copy of the License at
10+
*
11+
* http://www.apache.org/licenses/LICENSE-2.0
12+
*
13+
* Unless required by applicable law or agreed to in writing, software
14+
* distributed under the License is distributed on an "AS IS" BASIS,
15+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
16+
* See the License for the specific language governing permissions and
17+
* limitations under the License.
18+
*/
19+
package org.neo4j.driver.stress;
20+
21+
import reactor.core.publisher.Flux;
22+
23+
import java.util.concurrent.CompletableFuture;
24+
import java.util.concurrent.CompletionStage;
25+
26+
import org.neo4j.driver.AccessMode;
27+
import org.neo4j.driver.Driver;
28+
import org.neo4j.driver.internal.util.Futures;
29+
import org.neo4j.driver.reactive.RxSession;
30+
31+
import static org.hamcrest.Matchers.either;
32+
import static org.hamcrest.Matchers.equalTo;
33+
import static org.hamcrest.Matchers.is;
34+
import static org.hamcrest.junit.MatcherAssert.assertThat;
35+
import static org.neo4j.driver.internal.util.Matchers.arithmeticError;
36+
37+
public class RxFailingQuery<C extends AbstractContext> extends AbstractRxQuery<C>
38+
{
39+
public RxFailingQuery( Driver driver )
40+
{
41+
super( driver, false );
42+
}
43+
44+
@Override
45+
public CompletionStage<Void> execute( C context )
46+
{
47+
CompletableFuture<Void> queryFinished = new CompletableFuture<>();
48+
Flux.using( () -> newSession( AccessMode.READ, context ),
49+
session -> session.run( "UNWIND [10, 5, 0] AS x RETURN 10 / x" ).records(),
50+
RxSession::close )
51+
.subscribe( record -> {
52+
assertThat( record.get( 0 ).asInt(), either( equalTo( 1 ) ).or( equalTo( 2 ) ) );
53+
queryFinished.complete( null );
54+
}, error -> {
55+
Throwable cause = Futures.completionExceptionCause( error );
56+
assertThat( cause, is( arithmeticError() ) );
57+
queryFinished.complete( null );
58+
});
59+
return queryFinished;
60+
}
61+
}

0 commit comments

Comments
 (0)