Add configuration for TaskExecutor used by ClusterCommandsExecutor.

This change allows users to leverage the VirtualThread facilities and AsyncTaskExecutor implementations provided in and by the core Spring Framework as part of our Loom support theme.

Closes #2594
Original pull request: #2669
This commit is contained in:
John Blum
2023-08-08 17:05:42 -07:00
committed by Mark Paluch
parent 2eaf1746da
commit 8d6ebb4e99
7 changed files with 319 additions and 143 deletions

View File

@@ -15,12 +15,16 @@
*/
package org.springframework.data.redis.connection.jedis;
import static org.assertj.core.api.Assertions.*;
import static org.mockito.Mockito.*;
import redis.clients.jedis.JedisClientConfig;
import redis.clients.jedis.JedisCluster;
import redis.clients.jedis.JedisPoolConfig;
import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.assertThatIllegalStateException;
import static org.mockito.Mockito.any;
import static org.mockito.Mockito.doReturn;
import static org.mockito.Mockito.eq;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.spy;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
import java.io.IOException;
import java.security.NoSuchAlgorithmException;
@@ -33,15 +37,25 @@ import javax.net.ssl.SSLContext;
import javax.net.ssl.SSLParameters;
import javax.net.ssl.SSLSocketFactory;
import org.apache.commons.pool2.impl.GenericObjectPoolConfig;
import org.junit.jupiter.api.Test;
import org.springframework.core.task.AsyncTaskExecutor;
import org.springframework.data.redis.connection.ClusterCommandExecutor;
import org.springframework.data.redis.connection.ClusterTopologyProvider;
import org.springframework.data.redis.connection.RedisClusterConfiguration;
import org.springframework.data.redis.connection.RedisPassword;
import org.springframework.data.redis.connection.RedisSentinelConfiguration;
import org.springframework.data.redis.connection.RedisStandaloneConfiguration;
import org.springframework.data.redis.connection.jedis.JedisConnectionFactory.State;
import org.springframework.lang.Nullable;
import org.springframework.test.util.ReflectionTestUtils;
import org.apache.commons.pool2.impl.GenericObjectPoolConfig;
import redis.clients.jedis.JedisClientConfig;
import redis.clients.jedis.JedisCluster;
import redis.clients.jedis.JedisPoolConfig;
/**
* Unit tests for {@link JedisConnectionFactory}.
*
@@ -335,24 +349,59 @@ class JedisConnectionFactoryUnitTests {
assertThat(connectionFactory.isRunning()).isTrue();
}
private JedisConnectionFactory initSpyedConnectionFactory(RedisSentinelConfiguration sentinelConfig,
JedisPoolConfig poolConfig) {
@Test // GH-2594
void configuresCustomTaskExecutorCorrectly() {
AsyncTaskExecutor mockTaskExecutor = mock(AsyncTaskExecutor.class);
ClusterTopologyProvider mockClusterTopologyProvider = mock(ClusterTopologyProvider.class);
JedisCluster mockJedisCluster = mock(JedisCluster.class);
RedisClusterConfiguration clusterConfiguration = new RedisClusterConfiguration();
clusterConfiguration.setAsyncTaskExecutor(mockTaskExecutor);
JedisConnectionFactory connectionFactory = initSpyedConnectionFactory(clusterConfiguration, null);
doReturn(false).when(connectionFactory).getUsePool();
doReturn(mockJedisCluster).when(connectionFactory).createCluster();
doReturn(mockClusterTopologyProvider).when(connectionFactory).createTopologyProvider(eq(mockJedisCluster));
connectionFactory.start();
assertThat(connectionFactory.isRunning()).isTrue();
ClusterCommandExecutor clusterCommandExecutor = connectionFactory.getClusterCommandExecutor();
assertThat(clusterCommandExecutor).isNotNull();
assertThat(ReflectionTestUtils.getField(clusterCommandExecutor, "executor")).isEqualTo(mockTaskExecutor);
}
private JedisConnectionFactory initSpyedConnectionFactory(RedisSentinelConfiguration sentinelConfiguration,
@Nullable JedisPoolConfig poolConfig) {
// we have to use a spy here as jedis would start connecting to redis sentinels when the pool is created.
JedisConnectionFactory factorySpy = spy(new JedisConnectionFactory(sentinelConfig, poolConfig));
doReturn(null).when(factorySpy).createRedisSentinelPool(any(RedisSentinelConfiguration.class));
doReturn(null).when(factorySpy).createRedisPool();
return factorySpy;
JedisConnectionFactory connectionFactorySpy = spy(new JedisConnectionFactory(sentinelConfiguration, poolConfig));
doReturn(null).when(connectionFactorySpy)
.createRedisSentinelPool(any(RedisSentinelConfiguration.class));
doReturn(null).when(connectionFactorySpy).createRedisPool();
return connectionFactorySpy;
}
private JedisConnectionFactory initSpyedConnectionFactory(RedisClusterConfiguration clusterConfig,
JedisPoolConfig poolConfig) {
private JedisConnectionFactory initSpyedConnectionFactory(RedisClusterConfiguration clusterConfiguration,
@Nullable JedisPoolConfig poolConfig) {
JedisCluster clusterMock = mock(JedisCluster.class);
JedisConnectionFactory factorySpy = spy(new JedisConnectionFactory(clusterConfig));
doReturn(clusterMock).when(factorySpy).createCluster(any(RedisClusterConfiguration.class),
any(GenericObjectPoolConfig.class));
doReturn(null).when(factorySpy).createRedisPool();
return factorySpy;
JedisConnectionFactory connectionFactorySpy = spy(new JedisConnectionFactory(clusterConfiguration, poolConfig));
doReturn(clusterMock).when(connectionFactorySpy)
.createCluster(any(RedisClusterConfiguration.class), any(GenericObjectPoolConfig.class));
doReturn(null).when(connectionFactorySpy).createRedisPool();
return connectionFactorySpy;
}
}

View File

@@ -15,12 +15,15 @@
*/
package org.springframework.data.redis.connection.lettuce;
import static org.assertj.core.api.Assertions.*;
import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.assertThatExceptionOfType;
import static org.assertj.core.api.Assertions.assertThatIllegalArgumentException;
import static org.assertj.core.api.Assertions.assertThatIllegalStateException;
import static org.mockito.Mockito.*;
import static org.springframework.data.redis.connection.ClusterTestVariables.*;
import static org.springframework.data.redis.connection.RedisConfiguration.*;
import static org.springframework.data.redis.test.extension.LettuceTestClientResources.*;
import static org.springframework.test.util.ReflectionTestUtils.*;
import static org.springframework.data.redis.connection.ClusterTestVariables.CLUSTER_NODE_1;
import static org.springframework.data.redis.connection.RedisConfiguration.WithHostAndPort;
import static org.springframework.data.redis.test.extension.LettuceTestClientResources.getSharedClientResources;
import static org.springframework.test.util.ReflectionTestUtils.getField;
import io.lettuce.core.AbstractRedisClient;
import io.lettuce.core.ClientOptions;
@@ -43,6 +46,7 @@ import java.util.Collections;
import java.util.Objects;
import java.util.concurrent.CompletableFuture;
import org.assertj.core.api.InstanceOfAssertFactories;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Disabled;
@@ -50,8 +54,10 @@ import org.junit.jupiter.api.Test;
import org.mockito.ArgumentMatchers;
import org.springframework.beans.DirectFieldAccessor;
import org.springframework.beans.factory.DisposableBean;
import org.springframework.core.task.AsyncTaskExecutor;
import org.springframework.data.redis.ConnectionFactoryTracker;
import org.springframework.data.redis.RedisConnectionFailureException;
import org.springframework.data.redis.connection.ClusterCommandExecutor;
import org.springframework.data.redis.connection.PoolException;
import org.springframework.data.redis.connection.RedisClusterConfiguration;
import org.springframework.data.redis.connection.RedisClusterConnection;
@@ -63,9 +69,6 @@ import org.springframework.data.redis.connection.RedisSentinelConfiguration;
import org.springframework.data.redis.connection.RedisSocketConfiguration;
import org.springframework.data.redis.connection.RedisStandaloneConfiguration;
import org.springframework.data.redis.test.extension.LettuceTestClientResources;
import org.springframework.test.util.ReflectionTestUtils;
import org.assertj.core.api.InstanceOfAssertFactories;
/**
* Unit tests for {@link LettuceConnectionFactory}.
@@ -823,7 +826,7 @@ class LettuceConnectionFactoryUnitTests {
ConnectionFactoryTracker.add(connectionFactory);
RedisClusterConnection clusterConnection = connectionFactory.getClusterConnection();
assertThat(ReflectionTestUtils.getField(clusterConnection, "timeout")).isEqualTo(2000L);
assertThat(getField(clusterConnection, "timeout")).isEqualTo(2000L);
clusterConnection.close();
}
@@ -839,7 +842,7 @@ class LettuceConnectionFactoryUnitTests {
ConnectionFactoryTracker.add(connectionFactory);
RedisClusterConnection clusterConnection = connectionFactory.getClusterConnection();
assertThat(ReflectionTestUtils.getField(clusterConnection, "timeout")).isEqualTo(2000L);
assertThat(getField(clusterConnection, "timeout")).isEqualTo(2000L);
clusterConnection.close();
}
@@ -1250,8 +1253,8 @@ class LettuceConnectionFactoryUnitTests {
.withNoCause());
}
@Test
public void createRedisConfigurationWithValidRedisUriString() {
@Test // GH-2594
void createRedisConfigurationWithValidRedisUriString() {
RedisConfiguration redisConfiguration =
LettuceConnectionFactory.createRedisConfiguration("redis://skullbox:6789");
@@ -1269,6 +1272,31 @@ class LettuceConnectionFactoryUnitTests {
.isEqualTo(6789);
}
@Test // GH-2594
void configuresCustomTaskExecutorCorrectly() {
AsyncTaskExecutor mockTaskExecutor = mock(AsyncTaskExecutor.class);
LettuceConnectionProvider mockConnectionProvider = mock(LettuceConnectionProvider.class);
RedisClusterClient mockRedisClient = mock(RedisClusterClient.class);
RedisClusterConfiguration clusterConfiguration = new RedisClusterConfiguration();
clusterConfiguration.setAsyncTaskExecutor(mockTaskExecutor);
LettuceConnectionFactory connectionFactory = spy(new LettuceConnectionFactory(clusterConfiguration));
doReturn(mockRedisClient).when(connectionFactory).createClient();
doReturn(mockConnectionProvider).when(connectionFactory).createConnectionProvider(eq(mockRedisClient), any());
connectionFactory.start();
assertThat(connectionFactory.isRunning()).isTrue();
ClusterCommandExecutor clusterCommandExecutor = connectionFactory.getClusterCommandExecutor();
assertThat(getField(clusterCommandExecutor, "executor")).isEqualTo(mockTaskExecutor);
}
static class CustomRedisConfiguration implements RedisConfiguration, WithHostAndPort {
private String hostName;