Skip to content

Commit c8690d5

Browse files
committed
Abort discovery on bookmark failures and continue on authorization expired error
This update ensures that discovery gets aborted on `ClientException` with the following codes: - `Neo.ClientError.Transaction.InvalidBookmark` - `Neo.ClientError.Transaction.InvalidBookmarkMixture` In addition, it makes sure that it continues on `AuthorizationExpiredException`.
1 parent 4ae0f2c commit c8690d5

File tree

2 files changed

+95
-3
lines changed

2 files changed

+95
-3
lines changed

driver/src/main/java/org/neo4j/driver/internal/cluster/RediscoveryImpl.java

Lines changed: 30 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,8 @@
3434
import org.neo4j.driver.Bookmark;
3535
import org.neo4j.driver.Logger;
3636
import org.neo4j.driver.Logging;
37+
import org.neo4j.driver.exceptions.AuthorizationExpiredException;
38+
import org.neo4j.driver.exceptions.ClientException;
3739
import org.neo4j.driver.exceptions.DiscoveryException;
3840
import org.neo4j.driver.exceptions.FatalDiscoveryException;
3941
import org.neo4j.driver.exceptions.SecurityException;
@@ -61,6 +63,8 @@ public class RediscoveryImpl implements Rediscovery
6163
private static final String RECOVERABLE_DISCOVERY_ERROR_WITH_SERVER = "Received a recoverable discovery error with server '%s', " +
6264
"will continue discovery with other routing servers if available. " +
6365
"Complete failure is reported separately from this entry.";
66+
private static final String INVALID_BOOKMARK_CODE = "Neo.ClientError.Transaction.InvalidBookmark";
67+
private static final String INVALID_BOOKMARK_MIXTURE_CODE = "Neo.ClientError.Transaction.InvalidBookmarkMixture";
6468

6569
private final BoltServerAddress initialRouter;
6670
private final RoutingSettings settings;
@@ -278,10 +282,8 @@ private CompletionStage<ClusterComposition> lookupOnRouter( BoltServerAddress ro
278282
private ClusterComposition handleRoutingProcedureError( Throwable error, RoutingTable routingTable,
279283
BoltServerAddress routerAddress, Throwable baseError )
280284
{
281-
if ( error instanceof SecurityException || error instanceof FatalDiscoveryException ||
282-
(error instanceof IllegalStateException && ConnectionPool.CONNECTION_POOL_CLOSED_ERROR_MESSAGE.equals( error.getMessage() )) )
285+
if ( mustAbortDiscovery( error ) )
283286
{
284-
// auth error or routing error happened, terminate the discovery procedure immediately
285287
throw new CompletionException( error );
286288
}
287289

@@ -295,6 +297,31 @@ private ClusterComposition handleRoutingProcedureError( Throwable error, Routing
295297
return null;
296298
}
297299

300+
private boolean mustAbortDiscovery( Throwable throwable )
301+
{
302+
boolean abort = false;
303+
304+
if ( !(throwable instanceof AuthorizationExpiredException) && throwable instanceof SecurityException )
305+
{
306+
abort = true;
307+
}
308+
else if ( throwable instanceof FatalDiscoveryException )
309+
{
310+
abort = true;
311+
}
312+
else if ( throwable instanceof IllegalStateException && ConnectionPool.CONNECTION_POOL_CLOSED_ERROR_MESSAGE.equals( throwable.getMessage() ) )
313+
{
314+
abort = true;
315+
}
316+
else if ( throwable instanceof ClientException )
317+
{
318+
String code = ((ClientException) throwable).code();
319+
abort = INVALID_BOOKMARK_CODE.equals( code ) || INVALID_BOOKMARK_MIXTURE_CODE.equals( code );
320+
}
321+
322+
return abort;
323+
}
324+
298325
@Override
299326
public List<BoltServerAddress> resolve() throws UnknownHostException
300327
{

driver/src/test/java/org/neo4j/driver/internal/cluster/RediscoveryTest.java

Lines changed: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,8 @@
2020

2121
import io.netty.util.concurrent.GlobalEventExecutor;
2222
import org.junit.jupiter.api.Test;
23+
import org.junit.jupiter.params.ParameterizedTest;
24+
import org.junit.jupiter.params.provider.ValueSource;
2325
import org.mockito.ArgumentCaptor;
2426

2527
import java.io.IOException;
@@ -33,6 +35,8 @@
3335
import org.neo4j.driver.Logger;
3436
import org.neo4j.driver.Logging;
3537
import org.neo4j.driver.exceptions.AuthenticationException;
38+
import org.neo4j.driver.exceptions.AuthorizationExpiredException;
39+
import org.neo4j.driver.exceptions.ClientException;
3640
import org.neo4j.driver.exceptions.DiscoveryException;
3741
import org.neo4j.driver.exceptions.ProtocolException;
3842
import org.neo4j.driver.exceptions.ServiceUnavailableException;
@@ -143,6 +147,67 @@ void shouldFailImmediatelyOnAuthError()
143147
verify( table ).forget( A );
144148
}
145149

150+
@Test
151+
void shouldUseAnotherRouterOnAuthorizationExpiredException()
152+
{
153+
ClusterComposition expectedComposition =
154+
new ClusterComposition( 42, asOrderedSet( A, B, C ), asOrderedSet( B, C, D ), asOrderedSet( A, B ), null );
155+
156+
Map<BoltServerAddress,Object> responsesByAddress = new HashMap<>();
157+
responsesByAddress.put( A, new AuthorizationExpiredException( "Neo.ClientError.Security.AuthorizationExpired", "message" ) );
158+
responsesByAddress.put( B, expectedComposition );
159+
160+
ClusterCompositionProvider compositionProvider = compositionProviderMock( responsesByAddress );
161+
Rediscovery rediscovery = newRediscovery( A, compositionProvider, mock( ServerAddressResolver.class ) );
162+
RoutingTable table = routingTableMock( A, B, C );
163+
164+
ClusterComposition actualComposition = await( rediscovery.lookupClusterComposition( table, pool, empty(), null ) ).getClusterComposition();
165+
166+
assertEquals( expectedComposition, actualComposition );
167+
verify( table ).forget( A );
168+
verify( table, never() ).forget( B );
169+
verify( table, never() ).forget( C );
170+
}
171+
172+
@ParameterizedTest
173+
@ValueSource( strings = {"Neo.ClientError.Transaction.InvalidBookmark", "Neo.ClientError.Transaction.InvalidBookmarkMixture"} )
174+
void shouldFailImmediatelyOnBookmarkErrors( String code )
175+
{
176+
ClientException error = new ClientException( code, "Invalid" );
177+
178+
Map<BoltServerAddress,Object> responsesByAddress = new HashMap<>();
179+
responsesByAddress.put( A, new RuntimeException( "Hi!" ) );
180+
responsesByAddress.put( B, error );
181+
182+
ClusterCompositionProvider compositionProvider = compositionProviderMock( responsesByAddress );
183+
Rediscovery rediscovery = newRediscovery( A, compositionProvider, mock( ServerAddressResolver.class ) );
184+
RoutingTable table = routingTableMock( A, B, C );
185+
186+
ClientException actualError = assertThrows( ClientException.class,
187+
() -> await( rediscovery.lookupClusterComposition( table, pool, empty(), null ) ) );
188+
assertEquals( error, actualError );
189+
verify( table ).forget( A );
190+
}
191+
192+
@Test
193+
void shouldFailImmediatelyOnClosedPoolError()
194+
{
195+
IllegalStateException error = new IllegalStateException( ConnectionPool.CONNECTION_POOL_CLOSED_ERROR_MESSAGE );
196+
197+
Map<BoltServerAddress,Object> responsesByAddress = new HashMap<>();
198+
responsesByAddress.put( A, new RuntimeException( "Hi!" ) );
199+
responsesByAddress.put( B, error );
200+
201+
ClusterCompositionProvider compositionProvider = compositionProviderMock( responsesByAddress );
202+
Rediscovery rediscovery = newRediscovery( A, compositionProvider, mock( ServerAddressResolver.class ) );
203+
RoutingTable table = routingTableMock( A, B, C );
204+
205+
IllegalStateException actualError = assertThrows( IllegalStateException.class,
206+
() -> await( rediscovery.lookupClusterComposition( table, pool, empty(), null ) ) );
207+
assertEquals( error, actualError );
208+
verify( table ).forget( A );
209+
}
210+
146211
@Test
147212
void shouldFallbackToInitialRouterWhenKnownRoutersFail()
148213
{

0 commit comments

Comments
 (0)