Skip to content

Commit 6dbb999

Browse files
committed
DATAREDIS-1189 - Consistently translate Lettuce connection exceptions.
We now translate consistently connection/pooling exceptions in LettuceConnectionFactory by wrapping LettuceConnectionProvider with a variant that considers exception translation.
1 parent c125083 commit 6dbb999

File tree

4 files changed

+155
-15
lines changed

4 files changed

+155
-15
lines changed

src/main/java/org/springframework/data/redis/connection/lettuce/LettuceClusterConnection.java

Lines changed: 1 addition & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -686,11 +686,7 @@ public RedisClusterCommands<byte[], byte[]> getResourceForSpecificNode(RedisClus
686686
}
687687
}
688688

689-
try {
690-
return connection.getConnection(node.getHost(), node.getPort()).sync();
691-
} catch (RedisException e) {
692-
throw new DataAccessResourceFailureException(e.getMessage(), e);
693-
}
689+
return connection.getConnection(node.getHost(), node.getPort()).sync();
694690
}
695691

696692
@Override

src/main/java/org/springframework/data/redis/connection/lettuce/LettuceConnectionFactory.java

Lines changed: 129 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@
2121
import io.lettuce.core.ClientOptions;
2222
import io.lettuce.core.ReadFrom;
2323
import io.lettuce.core.RedisClient;
24-
import io.lettuce.core.RedisException;
24+
import io.lettuce.core.RedisConnectionException;
2525
import io.lettuce.core.RedisURI;
2626
import io.lettuce.core.api.StatefulConnection;
2727
import io.lettuce.core.api.StatefulRedisConnection;
@@ -37,12 +37,15 @@
3737
import java.util.ArrayList;
3838
import java.util.List;
3939
import java.util.Optional;
40+
import java.util.concurrent.CompletableFuture;
41+
import java.util.concurrent.CompletionStage;
4042
import java.util.concurrent.TimeUnit;
4143
import java.util.function.Consumer;
4244
import java.util.stream.Collectors;
4345

4446
import org.apache.commons.logging.Log;
4547
import org.apache.commons.logging.LogFactory;
48+
4649
import org.springframework.beans.factory.DisposableBean;
4750
import org.springframework.beans.factory.InitializingBean;
4851
import org.springframework.dao.DataAccessException;
@@ -55,6 +58,7 @@
5558
import org.springframework.data.redis.connection.RedisConfiguration.DomainSocketConfiguration;
5659
import org.springframework.data.redis.connection.RedisConfiguration.WithDatabaseIndex;
5760
import org.springframework.data.redis.connection.RedisConfiguration.WithPassword;
61+
import org.springframework.data.redis.connection.lettuce.LettuceConnection.*;
5862
import org.springframework.data.util.Optionals;
5963
import org.springframework.lang.Nullable;
6064
import org.springframework.util.Assert;
@@ -277,8 +281,9 @@ public void afterPropertiesSet() {
277281

278282
this.client = createClient();
279283

280-
this.connectionProvider = createConnectionProvider(client, CODEC);
281-
this.reactiveConnectionProvider = createConnectionProvider(client, LettuceReactiveRedisConnection.CODEC);
284+
this.connectionProvider = new ExceptionTranslatingConnectionProvider(createConnectionProvider(client, CODEC));
285+
this.reactiveConnectionProvider = new ExceptionTranslatingConnectionProvider(
286+
createConnectionProvider(client, LettuceReactiveRedisConnection.CODEC));
282287

283288
if (isClusterAware()) {
284289

@@ -1197,12 +1202,7 @@ StatefulConnection<E, E> getConnection() {
11971202
* @return the connection.
11981203
*/
11991204
private StatefulConnection<E, E> getNativeConnection() {
1200-
1201-
try {
1202-
return connectionProvider.getConnection(StatefulConnection.class);
1203-
} catch (RedisException e) {
1204-
throw new RedisConnectionFailureException("Unable to connect to Redis", e);
1205-
}
1205+
return connectionProvider.getConnection(StatefulConnection.class);
12061206
}
12071207

12081208
/**
@@ -1393,4 +1393,124 @@ public Duration getShutdownQuietPeriod() {
13931393
return shutdownTimeout;
13941394
}
13951395
}
1396+
1397+
/**
1398+
* {@link LettuceConnectionProvider} that translates connection exceptions into {@link RedisConnectionException}.
1399+
*/
1400+
private static class ExceptionTranslatingConnectionProvider
1401+
implements LettuceConnectionProvider, LettuceConnectionProvider.TargetAware, DisposableBean {
1402+
1403+
private final LettuceConnectionProvider delegate;
1404+
1405+
public ExceptionTranslatingConnectionProvider(LettuceConnectionProvider delegate) {
1406+
this.delegate = delegate;
1407+
}
1408+
1409+
/*
1410+
* (non-Javadoc)
1411+
* @see org.springframework.data.redis.connection.lettuce.LettuceConnectionProvider#getConnection(java.lang.Class)
1412+
*/
1413+
@Override
1414+
public <T extends StatefulConnection<?, ?>> T getConnection(Class<T> connectionType) {
1415+
1416+
try {
1417+
return delegate.getConnection(connectionType);
1418+
} catch (RuntimeException e) {
1419+
throw translateException(e);
1420+
}
1421+
}
1422+
1423+
/*
1424+
* (non-Javadoc)
1425+
* @see org.springframework.data.redis.connection.lettuce.LettuceConnectionProvider#getConnection(java.lang.Class, RedisURI)
1426+
*/
1427+
@Override
1428+
public <T extends StatefulConnection<?, ?>> T getConnection(Class<T> connectionType, RedisURI redisURI) {
1429+
1430+
try {
1431+
return ((TargetAware) delegate).getConnection(connectionType, redisURI);
1432+
} catch (RuntimeException e) {
1433+
throw translateException(e);
1434+
}
1435+
}
1436+
1437+
/*
1438+
* (non-Javadoc)
1439+
* @see org.springframework.data.redis.connection.lettuce.LettuceConnectionProvider#getConnectionAsync(java.lang.Class)
1440+
*/
1441+
@Override
1442+
public <T extends StatefulConnection<?, ?>> CompletionStage<T> getConnectionAsync(Class<T> connectionType) {
1443+
1444+
CompletableFuture<T> future = new CompletableFuture<>();
1445+
1446+
delegate.getConnectionAsync(connectionType).whenComplete((t, throwable) -> {
1447+
1448+
if (throwable != null) {
1449+
future.completeExceptionally(translateException(throwable));
1450+
} else {
1451+
future.complete(t);
1452+
}
1453+
});
1454+
1455+
return future;
1456+
}
1457+
1458+
/*
1459+
* (non-Javadoc)
1460+
* @see org.springframework.data.redis.connection.lettuce.LettuceConnectionProvider#getConnectionAsync(java.lang.Class, RedisURI)
1461+
*/
1462+
@Override
1463+
public <T extends StatefulConnection<?, ?>> CompletionStage<T> getConnectionAsync(Class<T> connectionType,
1464+
RedisURI redisURI) {
1465+
1466+
CompletableFuture<T> future = new CompletableFuture<>();
1467+
1468+
((TargetAware) delegate).getConnectionAsync(connectionType, redisURI).whenComplete((t, throwable) -> {
1469+
1470+
if (throwable != null) {
1471+
future.completeExceptionally(translateException(throwable));
1472+
} else {
1473+
future.complete(t);
1474+
}
1475+
});
1476+
1477+
return future;
1478+
}
1479+
1480+
/*
1481+
* (non-Javadoc)
1482+
* @see org.springframework.data.redis.connection.lettuce.LettuceConnectionProvider#release(io.lettuce.core.api.StatefulConnection)
1483+
*/
1484+
@Override
1485+
public void release(StatefulConnection<?, ?> connection) {
1486+
delegate.release(connection);
1487+
}
1488+
1489+
/*
1490+
* (non-Javadoc)
1491+
* @see org.springframework.data.redis.connection.lettuce.LettuceConnectionProvider#releaseAsync(io.lettuce.core.api.StatefulConnection)
1492+
*/
1493+
@Override
1494+
public CompletableFuture<Void> releaseAsync(StatefulConnection<?, ?> connection) {
1495+
return delegate.releaseAsync(connection);
1496+
}
1497+
1498+
/*
1499+
* (non-Javadoc)
1500+
* @see org.springframework.beans.factory.DisposableBean#destroy()
1501+
*/
1502+
@Override
1503+
public void destroy() throws Exception {
1504+
1505+
if (delegate instanceof DisposableBean) {
1506+
((DisposableBean) delegate).destroy();
1507+
}
1508+
}
1509+
1510+
private RuntimeException translateException(Throwable e) {
1511+
return e instanceof RedisConnectionFailureException ? (RedisConnectionFailureException) e
1512+
: new RedisConnectionFailureException("Unable to connect to Redis", e);
1513+
}
1514+
1515+
}
13961516
}

src/test/java/org/springframework/data/redis/connection/lettuce/LettuceConnectionFactoryTests.java

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -492,7 +492,8 @@ public void pubSubDoesNotSupportMasterReplicaConnections() {
492492
RedisConnection connection = factory.getConnection();
493493

494494
assertThatThrownBy(() -> connection.pSubscribe((message, pattern) -> {
495-
}, "foo".getBytes())).isInstanceOf(RedisSystemException.class).hasCauseInstanceOf(UnsupportedOperationException.class);
495+
}, "foo".getBytes())).isInstanceOf(RedisConnectionFailureException.class)
496+
.hasCauseInstanceOf(UnsupportedOperationException.class);
496497

497498
connection.close();
498499
factory.destroy();

src/test/java/org/springframework/data/redis/connection/lettuce/LettuceConnectionFactoryUnitTests.java

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,8 @@
4949
import org.springframework.beans.DirectFieldAccessor;
5050
import org.springframework.beans.factory.DisposableBean;
5151
import org.springframework.data.redis.ConnectionFactoryTracker;
52+
import org.springframework.data.redis.RedisConnectionFailureException;
53+
import org.springframework.data.redis.connection.PoolException;
5254
import org.springframework.data.redis.connection.RedisClusterConfiguration;
5355
import org.springframework.data.redis.connection.RedisClusterConnection;
5456
import org.springframework.data.redis.connection.RedisConfiguration;
@@ -858,6 +860,27 @@ protected LettuceConnectionProvider doCreateConnectionProvider(AbstractRedisClie
858860
verify(connectionProviderMock, times(2)).getConnection(StatefulConnection.class);
859861
}
860862

863+
@Test // DATAREDIS-1189
864+
public void shouldTranslateConnectionException() {
865+
866+
LettuceConnectionProvider connectionProviderMock = mock(LettuceConnectionProvider.class);
867+
868+
when(connectionProviderMock.getConnection(any())).thenThrow(new PoolException("error!"));
869+
870+
LettuceConnectionFactory connectionFactory = new LettuceConnectionFactory() {
871+
@Override
872+
protected LettuceConnectionProvider doCreateConnectionProvider(AbstractRedisClient client,
873+
RedisCodec<?, ?> codec) {
874+
return connectionProviderMock;
875+
}
876+
};
877+
connectionFactory.setClientResources(LettuceTestClientResources.getSharedClientResources());
878+
connectionFactory.afterPropertiesSet();
879+
880+
assertThatExceptionOfType(RedisConnectionFailureException.class)
881+
.isThrownBy(() -> connectionFactory.getConnection().ping()).withCauseInstanceOf(PoolException.class);
882+
}
883+
861884
@Test // DATAREDIS-1027
862885
public void shouldDisposeConnectionProviders() throws Exception {
863886

0 commit comments

Comments
 (0)