Correctly release connection after switching to Pub/Sub mode.

LettuceConnection.switchToPubSub now correctly releases its underlying connection when switching to Pub/Sub. Also, we improved safeguards to avoid using closed connections.

Closes #2331
This commit is contained in:
Mark Paluch
2022-06-03 09:14:18 +02:00
parent 14eebce19c
commit e7abb9a516
6 changed files with 39 additions and 25 deletions

View File

@@ -331,11 +331,18 @@ public class JedisConnection extends AbstractRedisConnection {
super.close();
JedisSubscription subscription = this.subscription;
if (subscription != null) {
subscription.close();
this.subscription = null;
}
// return the connection to the pool
if (pool != null) {
jedis.close();
return;
}
// else close the connection normally (doing the try/catch dance)
Exception exc = null;
try {
@@ -348,8 +355,10 @@ public class JedisConnection extends AbstractRedisConnection {
} catch (Exception ex) {
exc = ex;
}
if (exc != null)
if (exc != null) {
throw convertJedisAccessException(exc);
}
}
/*

View File

@@ -63,6 +63,7 @@ import org.springframework.dao.InvalidDataAccessApiUsageException;
import org.springframework.dao.QueryTimeoutException;
import org.springframework.data.redis.ExceptionTranslationStrategy;
import org.springframework.data.redis.FallbackExceptionTranslationStrategy;
import org.springframework.data.redis.RedisSystemException;
import org.springframework.data.redis.connection.*;
import org.springframework.data.redis.connection.convert.TransactionResultConverter;
import org.springframework.data.redis.connection.lettuce.LettuceConnectionProvider.TargetAware;
@@ -423,22 +424,29 @@ public class LettuceConnection extends AbstractRedisConnection {
isClosed = true;
reset();
}
private void reset() {
if (asyncDedicatedConn != null) {
try {
if (customizedDatabaseIndex()) {
potentiallySelectDatabase(defaultDbIndex);
}
connectionProvider.release(asyncDedicatedConn);
asyncDedicatedConn = null;
} catch (RuntimeException ex) {
throw convertLettuceAccessException(ex);
}
}
LettuceSubscription subscription = this.subscription;
if (subscription != null) {
if (subscription.isAlive()) {
subscription.doClose();
}
subscription = null;
this.subscription = null;
}
this.dbIndex = defaultDbIndex;
@@ -461,7 +469,8 @@ public class LettuceConnection extends AbstractRedisConnection {
public RedisClusterAsyncCommands<byte[], byte[]> getNativeConnection() {
LettuceSubscription subscription = this.subscription;
return (subscription != null ? subscription.getNativeConnection().async() : getAsyncConnection());
return (subscription != null && subscription.isAlive() ? subscription.getNativeConnection().async()
: getAsyncConnection());
}
/*
@@ -609,8 +618,8 @@ public class LettuceConnection extends AbstractRedisConnection {
LettuceTransactionResultConverter resultConverter = new LettuceTransactionResultConverter(
new LinkedList<>(txResults), exceptionConverter);
pipeline(newLettuceResult(exec, source -> resultConverter
.convert(LettuceConverters.transactionResultUnwrapper().convert(source))));
pipeline(newLettuceResult(exec,
source -> resultConverter.convert(LettuceConverters.transactionResultUnwrapper().convert(source))));
return null;
}
@@ -813,7 +822,8 @@ public class LettuceConnection extends AbstractRedisConnection {
@SuppressWarnings("unchecked")
protected StatefulRedisPubSubConnection<byte[], byte[]> switchToPubSub() {
close();
checkSubscription();
reset();
return connectionProvider.getConnection(StatefulRedisPubSubConnection.class);
}
@@ -988,6 +998,10 @@ public class LettuceConnection extends AbstractRedisConnection {
protected RedisClusterAsyncCommands<byte[], byte[]> getAsyncDedicatedConnection() {
if (isClosed()) {
throw new RedisSystemException("Connection is closed", null);
}
StatefulConnection<byte[], byte[]> connection = getOrCreateDedicatedConnection();
if (connection instanceof StatefulRedisConnection) {

View File

@@ -60,7 +60,8 @@ public class LettuceSubscription extends AbstractSubscription {
this.connection = pubsubConnection;
this.listener = new LettuceMessageListener(listener,
listener instanceof SubscriptionListener ? (SubscriptionListener) listener : SubscriptionListener.NO_OP_SUBSCRIPTION_LISTENER);
listener instanceof SubscriptionListener ? (SubscriptionListener) listener
: SubscriptionListener.NO_OP_SUBSCRIPTION_LISTENER);
this.connectionProvider = connectionProvider;
this.pubsub = connection.sync();
this.pubSubAsync = connection.async();
@@ -79,6 +80,10 @@ public class LettuceSubscription extends AbstractSubscription {
@Override
protected void doClose() {
if (!isAlive()) {
return;
}
List<CompletableFuture<?>> futures = new ArrayList<>();
if (!getChannels().isEmpty()) {

View File

@@ -104,6 +104,7 @@ public abstract class AbstractSubscription implements Subscription {
@Override
public void close() {
doClose();
alive.set(false);
}
/**
@@ -275,8 +276,7 @@ public abstract class AbstractSubscription implements Subscription {
private void closeIfUnsubscribed() {
if (channels.isEmpty() && patterns.isEmpty()) {
alive.set(false);
doClose();
close();
}
}

View File

@@ -22,14 +22,12 @@ import io.lettuce.core.EpollProvider;
import io.lettuce.core.KqueueProvider;
import io.lettuce.core.ReadFrom;
import io.lettuce.core.RedisException;
import io.lettuce.core.RedisFuture;
import io.lettuce.core.api.async.RedisAsyncCommands;
import io.lettuce.core.api.reactive.BaseRedisReactiveCommands;
import reactor.test.StepVerifier;
import java.io.File;
import java.time.Duration;
import java.util.concurrent.ExecutionException;
import java.util.function.Consumer;
import org.apache.commons.pool2.impl.GenericObjectPoolConfig;
@@ -225,19 +223,11 @@ class LettuceConnectionFactoryTests {
factory.setShareNativeConnection(false);
RedisConnection conn2 = factory.getConnection();
assertThat(conn2.getNativeConnection()).isNotSameAs(connection.getNativeConnection());
// Give some time for native connection to asynchronously initialize, else close doesn't work
Thread.sleep(100);
conn2.close();
assertThat(conn2.isClosed()).isTrue();
// Give some time for native connection to asynchronously close
Thread.sleep(100);
RedisFuture<String> future = ((RedisAsyncCommands<byte[], byte[]>) conn2.getNativeConnection()).ping();
try {
future.get();
fail("The native connection should be closed");
} catch (ExecutionException e) {
// expected, Lettuce async failures are signalled on the Future
}
assertThatExceptionOfType(RedisSystemException.class).isThrownBy(conn2::getNativeConnection);
}
@SuppressWarnings("unchecked")

View File

@@ -154,10 +154,6 @@ public class LettuceConnectionIntegrationTests extends AbstractConnectionIntegra
RedisConnection connection = factory2.getConnection();
// Use the connection to make sure the channel is initialized, else nothing happens on close
connection.ping();
connection.close();
// The dedicated connection should not be closed
connection.ping();
connection.close();
factory2.destroy();
pool.destroy();