Use virtual threads on Spring Data Redis if enabled

Closes gh-35942
pull/37393/head
Moritz Halbritter 1 year ago
parent e3d884803e
commit 3b15d46455

@ -26,12 +26,15 @@ import org.springframework.beans.factory.ObjectProvider;
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass; import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.boot.autoconfigure.condition.ConditionalOnThreading;
import org.springframework.boot.autoconfigure.thread.Threading;
import org.springframework.boot.context.properties.PropertyMapper; import org.springframework.boot.context.properties.PropertyMapper;
import org.springframework.boot.ssl.SslBundle; import org.springframework.boot.ssl.SslBundle;
import org.springframework.boot.ssl.SslBundles; import org.springframework.boot.ssl.SslBundles;
import org.springframework.boot.ssl.SslOptions; import org.springframework.boot.ssl.SslOptions;
import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.Configuration;
import org.springframework.core.task.SimpleAsyncTaskExecutor;
import org.springframework.data.redis.connection.RedisClusterConfiguration; import org.springframework.data.redis.connection.RedisClusterConfiguration;
import org.springframework.data.redis.connection.RedisConnectionFactory; import org.springframework.data.redis.connection.RedisConnectionFactory;
import org.springframework.data.redis.connection.RedisSentinelConfiguration; import org.springframework.data.redis.connection.RedisSentinelConfiguration;
@ -69,11 +72,23 @@ class JedisConnectionConfiguration extends RedisConnectionConfiguration {
} }
@Bean @Bean
@ConditionalOnThreading(Threading.PLATFORM)
JedisConnectionFactory redisConnectionFactory( JedisConnectionFactory redisConnectionFactory(
ObjectProvider<JedisClientConfigurationBuilderCustomizer> builderCustomizers) { ObjectProvider<JedisClientConfigurationBuilderCustomizer> builderCustomizers) {
return createJedisConnectionFactory(builderCustomizers); return createJedisConnectionFactory(builderCustomizers);
} }
@Bean
@ConditionalOnThreading(Threading.VIRTUAL)
JedisConnectionFactory redisConnectionFactoryVirtualThreads(
ObjectProvider<JedisClientConfigurationBuilderCustomizer> builderCustomizers) {
JedisConnectionFactory factory = createJedisConnectionFactory(builderCustomizers);
SimpleAsyncTaskExecutor executor = new SimpleAsyncTaskExecutor("redis-");
executor.setVirtualThreads(true);
factory.setExecutor(executor);
return factory;
}
private JedisConnectionFactory createJedisConnectionFactory( private JedisConnectionFactory createJedisConnectionFactory(
ObjectProvider<JedisClientConfigurationBuilderCustomizer> builderCustomizers) { ObjectProvider<JedisClientConfigurationBuilderCustomizer> builderCustomizers) {
JedisClientConfiguration clientConfiguration = getJedisClientConfiguration(builderCustomizers); JedisClientConfiguration clientConfiguration = getJedisClientConfiguration(builderCustomizers);

@ -33,13 +33,16 @@ import org.springframework.beans.factory.ObjectProvider;
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass; import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.boot.autoconfigure.condition.ConditionalOnThreading;
import org.springframework.boot.autoconfigure.data.redis.RedisProperties.Lettuce.Cluster.Refresh; import org.springframework.boot.autoconfigure.data.redis.RedisProperties.Lettuce.Cluster.Refresh;
import org.springframework.boot.autoconfigure.data.redis.RedisProperties.Pool; import org.springframework.boot.autoconfigure.data.redis.RedisProperties.Pool;
import org.springframework.boot.autoconfigure.thread.Threading;
import org.springframework.boot.ssl.SslBundle; import org.springframework.boot.ssl.SslBundle;
import org.springframework.boot.ssl.SslBundles; import org.springframework.boot.ssl.SslBundles;
import org.springframework.boot.ssl.SslOptions; import org.springframework.boot.ssl.SslOptions;
import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.Configuration;
import org.springframework.core.task.SimpleAsyncTaskExecutor;
import org.springframework.data.redis.connection.RedisClusterConfiguration; import org.springframework.data.redis.connection.RedisClusterConfiguration;
import org.springframework.data.redis.connection.RedisConnectionFactory; import org.springframework.data.redis.connection.RedisConnectionFactory;
import org.springframework.data.redis.connection.RedisSentinelConfiguration; import org.springframework.data.redis.connection.RedisSentinelConfiguration;
@ -83,9 +86,29 @@ class LettuceConnectionConfiguration extends RedisConnectionConfiguration {
@Bean @Bean
@ConditionalOnMissingBean(RedisConnectionFactory.class) @ConditionalOnMissingBean(RedisConnectionFactory.class)
@ConditionalOnThreading(Threading.PLATFORM)
LettuceConnectionFactory redisConnectionFactory( LettuceConnectionFactory redisConnectionFactory(
ObjectProvider<LettuceClientConfigurationBuilderCustomizer> builderCustomizers, ObjectProvider<LettuceClientConfigurationBuilderCustomizer> builderCustomizers,
ClientResources clientResources) { ClientResources clientResources) {
return createConnectionFactory(builderCustomizers, clientResources);
}
@Bean
@ConditionalOnMissingBean(RedisConnectionFactory.class)
@ConditionalOnThreading(Threading.VIRTUAL)
LettuceConnectionFactory redisConnectionFactoryVirtualThreads(
ObjectProvider<LettuceClientConfigurationBuilderCustomizer> builderCustomizers,
ClientResources clientResources) {
LettuceConnectionFactory factory = createConnectionFactory(builderCustomizers, clientResources);
SimpleAsyncTaskExecutor executor = new SimpleAsyncTaskExecutor("redis-");
executor.setVirtualThreads(true);
factory.setExecutor(executor);
return factory;
}
private LettuceConnectionFactory createConnectionFactory(
ObjectProvider<LettuceClientConfigurationBuilderCustomizer> builderCustomizers,
ClientResources clientResources) {
LettuceClientConfiguration clientConfig = getLettuceClientConfiguration(builderCustomizers, clientResources, LettuceClientConfiguration clientConfig = getLettuceClientConfiguration(builderCustomizers, clientResources,
getProperties().getLettuce().getPool()); getProperties().getLettuce().getPool());
return createLettuceConnectionFactory(clientConfig); return createLettuceConnectionFactory(clientConfig);

@ -19,14 +19,18 @@ package org.springframework.boot.autoconfigure.data.redis;
import java.time.Duration; import java.time.Duration;
import org.junit.jupiter.api.Test; import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.condition.EnabledForJreRange;
import org.junit.jupiter.api.condition.JRE;
import org.springframework.beans.factory.config.BeanPostProcessor; import org.springframework.beans.factory.config.BeanPostProcessor;
import org.springframework.boot.autoconfigure.AutoConfigurations; import org.springframework.boot.autoconfigure.AutoConfigurations;
import org.springframework.boot.autoconfigure.ssl.SslAutoConfiguration; import org.springframework.boot.autoconfigure.ssl.SslAutoConfiguration;
import org.springframework.boot.test.context.runner.ApplicationContextRunner; import org.springframework.boot.test.context.runner.ApplicationContextRunner;
import org.springframework.boot.testsupport.assertj.SimpleAsyncTaskExecutorAssert;
import org.springframework.boot.testsupport.classpath.ClassPathExclusions; import org.springframework.boot.testsupport.classpath.ClassPathExclusions;
import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.Configuration;
import org.springframework.core.task.SimpleAsyncTaskExecutor;
import org.springframework.data.redis.connection.RedisConnectionFactory; import org.springframework.data.redis.connection.RedisConnectionFactory;
import org.springframework.data.redis.connection.jedis.JedisClientConfiguration.JedisClientConfigurationBuilder; import org.springframework.data.redis.connection.jedis.JedisClientConfiguration.JedisClientConfigurationBuilder;
import org.springframework.data.redis.connection.jedis.JedisConnectionFactory; import org.springframework.data.redis.connection.jedis.JedisConnectionFactory;
@ -270,6 +274,25 @@ class RedisAutoConfigurationJedisTests {
}); });
} }
@Test
void shouldUsePlatformThreadsByDefault() {
this.contextRunner.run((context) -> {
JedisConnectionFactory factory = context.getBean(JedisConnectionFactory.class);
assertThat(factory).extracting("executor").isNull();
});
}
@Test
@EnabledForJreRange(min = JRE.JAVA_21)
void shouldUseVirtualThreadsIfEnabled() {
this.contextRunner.withPropertyValues("spring.threads.virtual.enabled=true").run((context) -> {
JedisConnectionFactory factory = context.getBean(JedisConnectionFactory.class);
SimpleAsyncTaskExecutor executor = (SimpleAsyncTaskExecutor) ReflectionTestUtils.getField(factory,
"executor");
SimpleAsyncTaskExecutorAssert.assertThat(executor).usesVirtualThreads();
});
}
private String getUserName(JedisConnectionFactory factory) { private String getUserName(JedisConnectionFactory factory) {
return ReflectionTestUtils.invokeMethod(factory, "getRedisUsername"); return ReflectionTestUtils.invokeMethod(factory, "getRedisUsername");
} }

@ -31,6 +31,8 @@ import io.lettuce.core.resource.DefaultClientResources;
import io.lettuce.core.tracing.Tracing; import io.lettuce.core.tracing.Tracing;
import org.apache.commons.pool2.impl.GenericObjectPoolConfig; import org.apache.commons.pool2.impl.GenericObjectPoolConfig;
import org.junit.jupiter.api.Test; import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.condition.EnabledForJreRange;
import org.junit.jupiter.api.condition.JRE;
import org.springframework.boot.autoconfigure.AutoConfigurations; import org.springframework.boot.autoconfigure.AutoConfigurations;
import org.springframework.boot.autoconfigure.data.redis.RedisProperties.Pool; import org.springframework.boot.autoconfigure.data.redis.RedisProperties.Pool;
@ -38,8 +40,10 @@ import org.springframework.boot.autoconfigure.ssl.SslAutoConfiguration;
import org.springframework.boot.test.context.assertj.AssertableApplicationContext; import org.springframework.boot.test.context.assertj.AssertableApplicationContext;
import org.springframework.boot.test.context.runner.ApplicationContextRunner; import org.springframework.boot.test.context.runner.ApplicationContextRunner;
import org.springframework.boot.test.context.runner.ContextConsumer; import org.springframework.boot.test.context.runner.ContextConsumer;
import org.springframework.boot.testsupport.assertj.SimpleAsyncTaskExecutorAssert;
import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.Configuration;
import org.springframework.core.task.SimpleAsyncTaskExecutor;
import org.springframework.data.redis.connection.RedisClusterConfiguration; import org.springframework.data.redis.connection.RedisClusterConfiguration;
import org.springframework.data.redis.connection.RedisConnectionFactory; import org.springframework.data.redis.connection.RedisConnectionFactory;
import org.springframework.data.redis.connection.RedisNode; import org.springframework.data.redis.connection.RedisNode;
@ -582,6 +586,25 @@ class RedisAutoConfigurationTests {
}); });
} }
@Test
void shouldUsePlatformThreadsByDefault() {
this.contextRunner.run((context) -> {
LettuceConnectionFactory factory = context.getBean(LettuceConnectionFactory.class);
assertThat(factory).extracting("executor").isNull();
});
}
@Test
@EnabledForJreRange(min = JRE.JAVA_21)
void shouldUseVirtualThreadsIfEnabled() {
this.contextRunner.withPropertyValues("spring.threads.virtual.enabled=true").run((context) -> {
LettuceConnectionFactory factory = context.getBean(LettuceConnectionFactory.class);
SimpleAsyncTaskExecutor executor = (SimpleAsyncTaskExecutor) ReflectionTestUtils.getField(factory,
"executor");
SimpleAsyncTaskExecutorAssert.assertThat(executor).usesVirtualThreads();
});
}
private <T extends ClientOptions> ContextConsumer<AssertableApplicationContext> assertClientOptions( private <T extends ClientOptions> ContextConsumer<AssertableApplicationContext> assertClientOptions(
Class<T> expectedType, Consumer<T> options) { Class<T> expectedType, Consumer<T> options) {
return (context) -> { return (context) -> {

Loading…
Cancel
Save