|
39 | 39 | import org.neo4j.driver.v1.Config;
|
40 | 40 | import org.neo4j.driver.v1.Driver;
|
41 | 41 | import org.neo4j.driver.v1.GraphDatabase;
|
| 42 | +import org.neo4j.driver.v1.Logger; |
| 43 | +import org.neo4j.driver.v1.Logging; |
42 | 44 | import org.neo4j.driver.v1.Record;
|
43 | 45 | import org.neo4j.driver.v1.Session;
|
44 | 46 | import org.neo4j.driver.v1.StatementResult;
|
|
61 | 63 | import static org.junit.jupiter.api.Assertions.assertThrows;
|
62 | 64 | import static org.junit.jupiter.api.Assertions.assertTrue;
|
63 | 65 | import static org.mockito.ArgumentMatchers.any;
|
| 66 | +import static org.mockito.ArgumentMatchers.startsWith; |
64 | 67 | import static org.mockito.Mockito.mock;
|
| 68 | +import static org.mockito.Mockito.times; |
65 | 69 | import static org.mockito.Mockito.verify;
|
66 | 70 | import static org.mockito.Mockito.when;
|
67 | 71 | import static org.neo4j.driver.v1.Logging.none;
|
@@ -726,6 +730,40 @@ void shouldRetryWriteTransactionUntilSuccess() throws Exception
|
726 | 730 | }
|
727 | 731 | }
|
728 | 732 |
|
| 733 | + @Test |
| 734 | + void shouldRetryWriteTransactionUntilSuccessWithWhenLeaderIsRemoved() throws Exception |
| 735 | + { |
| 736 | + // This test simulates a router in a cluster when a leader is removed. |
| 737 | + // The router first returns a RT with a writer inside. |
| 738 | + // However this writer is killed while the driver is running a tx with it. |
| 739 | + // Then at the second time the router returns the same RT with the killed writer inside. |
| 740 | + // At the third round, the router removes the the writer server from RT reply. |
| 741 | + // Finally, the router returns a RT with a reachable writer. |
| 742 | + StubServer router = StubServer.start( "acquire_endpoints_v3_leader_killed.script", 9001 ); |
| 743 | + StubServer brokenWriter = StubServer.start( "dead_write_server.script", 9004 ); |
| 744 | + StubServer writer = StubServer.start( "write_server.script", 9008 ); |
| 745 | + |
| 746 | + Logger logger = mock( Logger.class ); |
| 747 | + Config config = Config.builder().withoutEncryption().withLogging( mockedLogging( logger ) ).build(); |
| 748 | + try ( Driver driver = newDriverWithSleeplessClock( "bolt+routing://127.0.0.1:9001", config ); |
| 749 | + Session session = driver.session() ) |
| 750 | + { |
| 751 | + AtomicInteger invocations = new AtomicInteger(); |
| 752 | + List<Record> records = session.writeTransaction( queryWork( "CREATE (n {name:'Bob'})", invocations ) ); |
| 753 | + |
| 754 | + assertEquals( 0, records.size() ); |
| 755 | + assertEquals( 2, invocations.get() ); |
| 756 | + } |
| 757 | + finally |
| 758 | + { |
| 759 | + assertEquals( 0, router.exitStatus() ); |
| 760 | + assertEquals( 0, brokenWriter.exitStatus() ); |
| 761 | + assertEquals( 0, writer.exitStatus() ); |
| 762 | + } |
| 763 | + verify( logger, times( 3 ) ).warn( startsWith( "Transaction failed and will be retried in" ), any( SessionExpiredException.class ) ); |
| 764 | + verify( logger ).warn( startsWith( "Failed to obtain a connection towards address 127.0.0.1:9004" ), any( SessionExpiredException.class ) ); |
| 765 | + } |
| 766 | + |
729 | 767 | @Test
|
730 | 768 | void shouldRetryReadTransactionUntilFailure() throws Exception
|
731 | 769 | {
|
@@ -1159,19 +1197,24 @@ void useSessionAfterDriverIsClosed() throws Exception
|
1159 | 1197 | }
|
1160 | 1198 | }
|
1161 | 1199 |
|
1162 |
| - private static Driver newDriverWithSleeplessClock( String uriString ) |
| 1200 | + private static Driver newDriverWithSleeplessClock( String uriString, Config config ) |
1163 | 1201 | {
|
1164 | 1202 | DriverFactory driverFactory = new DriverFactoryWithClock( new SleeplessClock() );
|
1165 |
| - return newDriver( uriString, driverFactory ); |
| 1203 | + return newDriver( uriString, driverFactory, config ); |
| 1204 | + } |
| 1205 | + |
| 1206 | + private static Driver newDriverWithSleeplessClock( String uriString ) |
| 1207 | + { |
| 1208 | + return newDriverWithSleeplessClock( uriString, config ); |
1166 | 1209 | }
|
1167 | 1210 |
|
1168 | 1211 | private static Driver newDriverWithFixedRetries( String uriString, int retries )
|
1169 | 1212 | {
|
1170 | 1213 | DriverFactory driverFactory = new DriverFactoryWithFixedRetryLogic( retries );
|
1171 |
| - return newDriver( uriString, driverFactory ); |
| 1214 | + return newDriver( uriString, driverFactory, config ); |
1172 | 1215 | }
|
1173 | 1216 |
|
1174 |
| - private static Driver newDriver( String uriString, DriverFactory driverFactory ) |
| 1217 | + private static Driver newDriver( String uriString, DriverFactory driverFactory, Config config ) |
1175 | 1218 | {
|
1176 | 1219 | URI uri = URI.create( uriString );
|
1177 | 1220 | RoutingSettings routingConf = new RoutingSettings( 1, 1, null );
|
@@ -1201,4 +1244,11 @@ private static List<String> readStrings( final String query, Session session )
|
1201 | 1244 | return names;
|
1202 | 1245 | } );
|
1203 | 1246 | }
|
| 1247 | + |
| 1248 | + private static Logging mockedLogging( Logger logger ) |
| 1249 | + { |
| 1250 | + Logging logging = mock( Logging.class ); |
| 1251 | + when( logging.getLog( any() ) ).thenReturn( logger ); |
| 1252 | + return logging; |
| 1253 | + } |
1204 | 1254 | }
|
0 commit comments