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:
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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()) {
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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")
|
||||
|
||||
@@ -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();
|
||||
|
||||
Reference in New Issue
Block a user