Skip to content

Commit 5440bea

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 edaf2d7 commit 5440bea

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
@@ -688,11 +688,7 @@ public RedisClusterCommands<byte[], byte[]> getResourceForSpecificNode(RedisClus
688688
}
689689
}
690690

691-
try {
692-
return connection.getConnection(node.getHost(), node.getPort()).sync();
693-
} catch (RedisException e) {
694-
throw new DataAccessResourceFailureException(e.getMessage(), e);
695-
}
691+
return connection.getConnection(node.getHost(), node.getPort()).sync();
696692
}
697693

698694
@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;
@@ -36,12 +36,15 @@
3636
import java.util.ArrayList;
3737
import java.util.List;
3838
import java.util.Optional;
39+
import java.util.concurrent.CompletableFuture;
40+
import java.util.concurrent.CompletionStage;
3941
import java.util.concurrent.TimeUnit;
4042
import java.util.function.Consumer;
4143
import java.util.stream.Collectors;
4244

4345
import org.apache.commons.logging.Log;
4446
import org.apache.commons.logging.LogFactory;
47+
4548
import org.springframework.beans.factory.DisposableBean;
4649
import org.springframework.beans.factory.InitializingBean;
4750
import org.springframework.dao.DataAccessException;
@@ -54,6 +57,7 @@
5457
import org.springframework.data.redis.connection.RedisConfiguration.DomainSocketConfiguration;
5558
import org.springframework.data.redis.connection.RedisConfiguration.WithDatabaseIndex;
5659
import org.springframework.data.redis.connection.RedisConfiguration.WithPassword;
60+
import org.springframework.data.redis.connection.lettuce.LettuceConnection.*;
5761
import org.springframework.data.util.Optionals;
5862
import org.springframework.lang.Nullable;
5963
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

@@ -1222,12 +1227,7 @@ StatefulConnection<E, E> getConnection() {
12221227
* @return the connection.
12231228
*/
12241229
private StatefulConnection<E, E> getNativeConnection() {
1225-
1226-
try {
1227-
return connectionProvider.getConnection(StatefulConnection.class);
1228-
} catch (RedisException e) {
1229-
throw new RedisConnectionFailureException("Unable to connect to Redis", e);
1230-
}
1230+
return connectionProvider.getConnection(StatefulConnection.class);
12311231
}
12321232

12331233
/**
@@ -1418,4 +1418,124 @@ public Duration getShutdownQuietPeriod() {
14181418
return shutdownTimeout;
14191419
}
14201420
}
1421+
1422+
/**
1423+
* {@link LettuceConnectionProvider} that translates connection exceptions into {@link RedisConnectionException}.
1424+
*/
1425+
private static class ExceptionTranslatingConnectionProvider
1426+
implements LettuceConnectionProvider, LettuceConnectionProvider.TargetAware, DisposableBean {
1427+
1428+
private final LettuceConnectionProvider delegate;
1429+
1430+
public ExceptionTranslatingConnectionProvider(LettuceConnectionProvider delegate) {
1431+
this.delegate = delegate;
1432+
}
1433+
1434+
/*
1435+
* (non-Javadoc)
1436+
* @see org.springframework.data.redis.connection.lettuce.LettuceConnectionProvider#getConnection(java.lang.Class)
1437+
*/
1438+
@Override
1439+
public <T extends StatefulConnection<?, ?>> T getConnection(Class<T> connectionType) {
1440+
1441+
try {
1442+
return delegate.getConnection(connectionType);
1443+
} catch (RuntimeException e) {
1444+
throw translateException(e);
1445+
}
1446+
}
1447+
1448+
/*
1449+
* (non-Javadoc)
1450+
* @see org.springframework.data.redis.connection.lettuce.LettuceConnectionProvider#getConnection(java.lang.Class, RedisURI)
1451+
*/
1452+
@Override
1453+
public <T extends StatefulConnection<?, ?>> T getConnection(Class<T> connectionType, RedisURI redisURI) {
1454+
1455+
try {
1456+
return ((TargetAware) delegate).getConnection(connectionType, redisURI);
1457+
} catch (RuntimeException e) {
1458+
throw translateException(e);
1459+
}
1460+
}
1461+
1462+
/*
1463+
* (non-Javadoc)
1464+
* @see org.springframework.data.redis.connection.lettuce.LettuceConnectionProvider#getConnectionAsync(java.lang.Class)
1465+
*/
1466+
@Override
1467+
public <T extends StatefulConnection<?, ?>> CompletionStage<T> getConnectionAsync(Class<T> connectionType) {
1468+
1469+
CompletableFuture<T> future = new CompletableFuture<>();
1470+
1471+
delegate.getConnectionAsync(connectionType).whenComplete((t, throwable) -> {
1472+
1473+
if (throwable != null) {
1474+
future.completeExceptionally(translateException(throwable));
1475+
} else {
1476+
future.complete(t);
1477+
}
1478+
});
1479+
1480+
return future;
1481+
}
1482+
1483+
/*
1484+
* (non-Javadoc)
1485+
* @see org.springframework.data.redis.connection.lettuce.LettuceConnectionProvider#getConnectionAsync(java.lang.Class, RedisURI)
1486+
*/
1487+
@Override
1488+
public <T extends StatefulConnection<?, ?>> CompletionStage<T> getConnectionAsync(Class<T> connectionType,
1489+
RedisURI redisURI) {
1490+
1491+
CompletableFuture<T> future = new CompletableFuture<>();
1492+
1493+
((TargetAware) delegate).getConnectionAsync(connectionType, redisURI).whenComplete((t, throwable) -> {
1494+
1495+
if (throwable != null) {
1496+
future.completeExceptionally(translateException(throwable));
1497+
} else {
1498+
future.complete(t);
1499+
}
1500+
});
1501+
1502+
return future;
1503+
}
1504+
1505+
/*
1506+
* (non-Javadoc)
1507+
* @see org.springframework.data.redis.connection.lettuce.LettuceConnectionProvider#release(io.lettuce.core.api.StatefulConnection)
1508+
*/
1509+
@Override
1510+
public void release(StatefulConnection<?, ?> connection) {
1511+
delegate.release(connection);
1512+
}
1513+
1514+
/*
1515+
* (non-Javadoc)
1516+
* @see org.springframework.data.redis.connection.lettuce.LettuceConnectionProvider#releaseAsync(io.lettuce.core.api.StatefulConnection)
1517+
*/
1518+
@Override
1519+
public CompletableFuture<Void> releaseAsync(StatefulConnection<?, ?> connection) {
1520+
return delegate.releaseAsync(connection);
1521+
}
1522+
1523+
/*
1524+
* (non-Javadoc)
1525+
* @see org.springframework.beans.factory.DisposableBean#destroy()
1526+
*/
1527+
@Override
1528+
public void destroy() throws Exception {
1529+
1530+
if (delegate instanceof DisposableBean) {
1531+
((DisposableBean) delegate).destroy();
1532+
}
1533+
}
1534+
1535+
private RuntimeException translateException(Throwable e) {
1536+
return e instanceof RedisConnectionFailureException ? (RedisConnectionFailureException) e
1537+
: new RedisConnectionFailureException("Unable to connect to Redis", e);
1538+
}
1539+
1540+
}
14211541
}

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
@@ -473,7 +473,8 @@ public void pubSubDoesNotSupportMasterReplicaConnections() {
473473
RedisConnection connection = factory.getConnection();
474474

475475
assertThatThrownBy(() -> connection.pSubscribe((message, pattern) -> {
476-
}, "foo".getBytes())).isInstanceOf(RedisSystemException.class).hasCauseInstanceOf(UnsupportedOperationException.class);
476+
}, "foo".getBytes())).isInstanceOf(RedisConnectionFailureException.class)
477+
.hasCauseInstanceOf(UnsupportedOperationException.class);
477478

478479
connection.close();
479480
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;
@@ -861,6 +863,27 @@ protected LettuceConnectionProvider doCreateConnectionProvider(AbstractRedisClie
861863
verify(connectionProviderMock, times(2)).getConnection(StatefulConnection.class);
862864
}
863865

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

0 commit comments

Comments
 (0)