Skip to content

Commit 5b00d5f

Browse files
committed
Auto-configure SimpleAsyncTaskScheduler when virtual threads are enabled
This auto-configures a new SimpleAsyncTaskSchedulerBuilder bean in the context. This bean is configured to use virtual threads, if enabled. SimpleAsyncTaskSchedulerCustomizers can be used to customize the built SimpleAsyncTaskScheduler. If virtual threads are enabled, the application task scheduler is configured to be a SimpleAsyncTaskScheduler. Adds a new configuration property spring.task.scheduling.simple .concurrency-limit Closes gh-36609
1 parent 3c12f39 commit 5b00d5f

File tree

8 files changed

+509
-7
lines changed

8 files changed

+509
-7
lines changed

spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/task/TaskSchedulingAutoConfiguration.java

+2-1
Original file line numberDiff line numberDiff line change
@@ -40,7 +40,8 @@
4040
@EnableConfigurationProperties(TaskSchedulingProperties.class)
4141
@Import({ TaskSchedulingConfigurations.ThreadPoolTaskSchedulerBuilderConfiguration.class,
4242
TaskSchedulingConfigurations.TaskSchedulerBuilderConfiguration.class,
43-
TaskSchedulingConfigurations.ThreadPoolTaskSchedulerConfiguration.class })
43+
TaskSchedulingConfigurations.SimpleAsyncTaskSchedulerBuilderConfiguration.class,
44+
TaskSchedulingConfigurations.TaskSchedulerConfiguration.class })
4445
public class TaskSchedulingAutoConfiguration {
4546

4647
@Bean

spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/task/TaskSchedulingConfigurations.java

+53-1
Original file line numberDiff line numberDiff line change
@@ -21,13 +21,18 @@
2121
import org.springframework.beans.factory.ObjectProvider;
2222
import org.springframework.boot.autoconfigure.condition.ConditionalOnBean;
2323
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
24+
import org.springframework.boot.autoconfigure.condition.ConditionalOnThreading;
25+
import org.springframework.boot.autoconfigure.thread.Threading;
26+
import org.springframework.boot.task.SimpleAsyncTaskSchedulerBuilder;
27+
import org.springframework.boot.task.SimpleAsyncTaskSchedulerCustomizer;
2428
import org.springframework.boot.task.TaskSchedulerBuilder;
2529
import org.springframework.boot.task.TaskSchedulerCustomizer;
2630
import org.springframework.boot.task.ThreadPoolTaskSchedulerBuilder;
2731
import org.springframework.boot.task.ThreadPoolTaskSchedulerCustomizer;
2832
import org.springframework.context.annotation.Bean;
2933
import org.springframework.context.annotation.Configuration;
3034
import org.springframework.scheduling.TaskScheduler;
35+
import org.springframework.scheduling.concurrent.SimpleAsyncTaskScheduler;
3136
import org.springframework.scheduling.concurrent.ThreadPoolTaskScheduler;
3237
import org.springframework.scheduling.config.TaskManagementConfigUtils;
3338

@@ -43,9 +48,16 @@ class TaskSchedulingConfigurations {
4348
@ConditionalOnBean(name = TaskManagementConfigUtils.SCHEDULED_ANNOTATION_PROCESSOR_BEAN_NAME)
4449
@ConditionalOnMissingBean({ TaskScheduler.class, ScheduledExecutorService.class })
4550
@SuppressWarnings("removal")
46-
static class ThreadPoolTaskSchedulerConfiguration {
51+
static class TaskSchedulerConfiguration {
52+
53+
@Bean(name = "taskScheduler")
54+
@ConditionalOnThreading(Threading.VIRTUAL)
55+
SimpleAsyncTaskScheduler taskSchedulerVirtualThreads(SimpleAsyncTaskSchedulerBuilder builder) {
56+
return builder.build();
57+
}
4758

4859
@Bean
60+
@ConditionalOnThreading(Threading.PLATFORM)
4961
ThreadPoolTaskScheduler taskScheduler(TaskSchedulerBuilder taskSchedulerBuilder,
5062
ObjectProvider<ThreadPoolTaskSchedulerBuilder> threadPoolTaskSchedulerBuilderProvider) {
5163
ThreadPoolTaskSchedulerBuilder threadPoolTaskSchedulerBuilder = threadPoolTaskSchedulerBuilderProvider
@@ -105,4 +117,44 @@ private ThreadPoolTaskSchedulerCustomizer adapt(TaskSchedulerCustomizer customiz
105117

106118
}
107119

120+
@Configuration(proxyBeanMethods = false)
121+
static class SimpleAsyncTaskSchedulerBuilderConfiguration {
122+
123+
private final TaskSchedulingProperties properties;
124+
125+
private final ObjectProvider<SimpleAsyncTaskSchedulerCustomizer> taskSchedulerCustomizers;
126+
127+
SimpleAsyncTaskSchedulerBuilderConfiguration(TaskSchedulingProperties properties,
128+
ObjectProvider<SimpleAsyncTaskSchedulerCustomizer> taskSchedulerCustomizers) {
129+
this.properties = properties;
130+
this.taskSchedulerCustomizers = taskSchedulerCustomizers;
131+
}
132+
133+
@Bean
134+
@ConditionalOnMissingBean
135+
@ConditionalOnThreading(Threading.PLATFORM)
136+
SimpleAsyncTaskSchedulerBuilder simpleAsyncTaskSchedulerBuilder() {
137+
return builder();
138+
}
139+
140+
@Bean(name = "simpleAsyncTaskSchedulerBuilder")
141+
@ConditionalOnMissingBean
142+
@ConditionalOnThreading(Threading.VIRTUAL)
143+
SimpleAsyncTaskSchedulerBuilder simpleAsyncTaskSchedulerBuilderVirtualThreads() {
144+
SimpleAsyncTaskSchedulerBuilder builder = builder();
145+
builder = builder.virtualThreads(true);
146+
return builder;
147+
}
148+
149+
private SimpleAsyncTaskSchedulerBuilder builder() {
150+
SimpleAsyncTaskSchedulerBuilder builder = new SimpleAsyncTaskSchedulerBuilder();
151+
builder = builder.threadNamePrefix(this.properties.getThreadNamePrefix());
152+
builder = builder.customizers(this.taskSchedulerCustomizers.orderedStream()::iterator);
153+
TaskSchedulingProperties.Simple simple = this.properties.getSimple();
154+
builder = builder.concurrencyLimit(simple.getConcurrencyLimit());
155+
return builder;
156+
}
157+
158+
}
159+
108160
}

spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/task/TaskSchedulingProperties.java

+25-1
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2012-2019 the original author or authors.
2+
* Copyright 2012-2023 the original author or authors.
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -31,6 +31,8 @@ public class TaskSchedulingProperties {
3131

3232
private final Pool pool = new Pool();
3333

34+
private final Simple simple = new Simple();
35+
3436
private final Shutdown shutdown = new Shutdown();
3537

3638
/**
@@ -42,6 +44,10 @@ public Pool getPool() {
4244
return this.pool;
4345
}
4446

47+
public Simple getSimple() {
48+
return this.simple;
49+
}
50+
4551
public Shutdown getShutdown() {
4652
return this.shutdown;
4753
}
@@ -71,6 +77,24 @@ public void setSize(int size) {
7177

7278
}
7379

80+
public static class Simple {
81+
82+
/**
83+
* Set the maximum number of parallel accesses allowed. -1 indicates no
84+
* concurrency limit at all.
85+
*/
86+
private Integer concurrencyLimit;
87+
88+
public Integer getConcurrencyLimit() {
89+
return this.concurrencyLimit;
90+
}
91+
92+
public void setConcurrencyLimit(Integer concurrencyLimit) {
93+
this.concurrencyLimit = concurrencyLimit;
94+
}
95+
96+
}
97+
7498
public static class Shutdown {
7599

76100
/**

spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/task/TaskSchedulingAutoConfigurationTests.java

+57
Original file line numberDiff line numberDiff line change
@@ -28,9 +28,13 @@
2828

2929
import org.awaitility.Awaitility;
3030
import org.junit.jupiter.api.Test;
31+
import org.junit.jupiter.api.condition.EnabledForJreRange;
32+
import org.junit.jupiter.api.condition.JRE;
3133

3234
import org.springframework.boot.LazyInitializationBeanFactoryPostProcessor;
3335
import org.springframework.boot.autoconfigure.AutoConfigurations;
36+
import org.springframework.boot.task.SimpleAsyncTaskSchedulerBuilder;
37+
import org.springframework.boot.task.SimpleAsyncTaskSchedulerCustomizer;
3438
import org.springframework.boot.task.TaskSchedulerBuilder;
3539
import org.springframework.boot.task.TaskSchedulerCustomizer;
3640
import org.springframework.boot.task.ThreadPoolTaskSchedulerBuilder;
@@ -45,6 +49,7 @@
4549
import org.springframework.scheduling.annotation.SchedulingConfigurer;
4650
import org.springframework.scheduling.concurrent.ThreadPoolTaskScheduler;
4751
import org.springframework.scheduling.config.ScheduledTaskRegistrar;
52+
import org.springframework.test.util.ReflectionTestUtils;
4853

4954
import static org.assertj.core.api.Assertions.assertThat;
5055

@@ -110,6 +115,58 @@ void enableSchedulingWithNoTaskExecutorAutoConfiguresOne() {
110115
});
111116
}
112117

118+
@Test
119+
void simpleAsyncTaskSchedulerBuilderShouldReadProperties() {
120+
this.contextRunner
121+
.withPropertyValues("spring.task.scheduling.simple.concurrency-limit=1",
122+
"spring.task.scheduling.thread-name-prefix=scheduling-test-")
123+
.withUserConfiguration(SchedulingConfiguration.class)
124+
.run((context) -> {
125+
assertThat(context).hasSingleBean(SimpleAsyncTaskSchedulerBuilder.class);
126+
SimpleAsyncTaskSchedulerBuilder builder = context.getBean(SimpleAsyncTaskSchedulerBuilder.class);
127+
assertThat(builder).hasFieldOrPropertyWithValue("threadNamePrefix", "scheduling-test-");
128+
assertThat(builder).hasFieldOrPropertyWithValue("concurrencyLimit", 1);
129+
});
130+
}
131+
132+
@Test
133+
@EnabledForJreRange(min = JRE.JAVA_21)
134+
void simpleAsyncTaskSchedulerBuilderShouldUseVirtualThreadsIfEnabled() {
135+
this.contextRunner.withPropertyValues("spring.threads.virtual.enabled=true")
136+
.withUserConfiguration(SchedulingConfiguration.class)
137+
.run((context) -> {
138+
assertThat(context).hasSingleBean(SimpleAsyncTaskSchedulerBuilder.class);
139+
SimpleAsyncTaskSchedulerBuilder builder = context.getBean(SimpleAsyncTaskSchedulerBuilder.class);
140+
assertThat(builder).hasFieldOrPropertyWithValue("virtualThreads", true);
141+
});
142+
}
143+
144+
@Test
145+
@EnabledForJreRange(min = JRE.JAVA_21)
146+
void simpleAsyncTaskSchedulerBuilderShouldUsePlatformThreadsByDefault() {
147+
this.contextRunner.withUserConfiguration(SchedulingConfiguration.class).run((context) -> {
148+
assertThat(context).hasSingleBean(SimpleAsyncTaskSchedulerBuilder.class);
149+
SimpleAsyncTaskSchedulerBuilder builder = context.getBean(SimpleAsyncTaskSchedulerBuilder.class);
150+
assertThat(builder).hasFieldOrPropertyWithValue("virtualThreads", null);
151+
});
152+
}
153+
154+
@Test
155+
@SuppressWarnings("unchecked")
156+
void simpleAsyncTaskSchedulerBuilderShouldApplyCustomizers() {
157+
SimpleAsyncTaskSchedulerCustomizer customizer = (scheduler) -> {
158+
};
159+
this.contextRunner.withBean(SimpleAsyncTaskSchedulerCustomizer.class, () -> customizer)
160+
.withUserConfiguration(SchedulingConfiguration.class)
161+
.run((context) -> {
162+
assertThat(context).hasSingleBean(SimpleAsyncTaskSchedulerBuilder.class);
163+
SimpleAsyncTaskSchedulerBuilder builder = context.getBean(SimpleAsyncTaskSchedulerBuilder.class);
164+
Set<SimpleAsyncTaskSchedulerCustomizer> customizers = (Set<SimpleAsyncTaskSchedulerCustomizer>) ReflectionTestUtils
165+
.getField(builder, "customizers");
166+
assertThat(customizers).as("SimpleAsyncTaskSchedulerBuilder.customizers").contains(customizer);
167+
});
168+
}
169+
113170
@Test
114171
void enableSchedulingWithNoTaskExecutorAppliesTaskSchedulerCustomizers() {
115172
this.contextRunner.withPropertyValues("spring.task.scheduling.thread-name-prefix=scheduling-test-")

spring-boot-project/spring-boot-docs/src/docs/asciidoc/features/task-execution-and-scheduling.adoc

+7-4
Original file line numberDiff line numberDiff line change
@@ -36,8 +36,11 @@ Those default settings can be fine-tuned using the `spring.task.execution` names
3636
This changes the thread pool to use a bounded queue so that when the queue is full (100 tasks), the thread pool increases to maximum 16 threads.
3737
Shrinking of the pool is more aggressive as threads are reclaimed when they are idle for 10 seconds (rather than 60 seconds by default).
3838

39-
A `ThreadPoolTaskScheduler` can also be auto-configured if need to be associated to scheduled task execution (using `@EnableScheduling` for instance).
40-
The thread pool uses one thread by default and its settings can be fine-tuned using the `spring.task.scheduling` namespace, as shown in the following example:
39+
A scheduler can also be auto-configured if need to be associated to scheduled task execution (using `@EnableScheduling` for instance).
40+
When virtual threads are enabled (using Java 21+ and configprop:spring.threads.virtual.enabled[] set to `true`) this will be a `SimpleAsyncTaskScheduler` that uses virtual threads.
41+
Otherwise, it will be a `ThreadPoolTaskScheduler` with sensible defaults.
42+
43+
The `ThreadPoolTaskScheduler` uses one thread by default and its settings can be fine-tuned using the `spring.task.scheduling` namespace, as shown in the following example:
4144

4245
[source,yaml,indent=0,subs="verbatim",configprops,configblocks]
4346
----
@@ -49,5 +52,5 @@ The thread pool uses one thread by default and its settings can be fine-tuned us
4952
size: 2
5053
----
5154

52-
A `ThreadPoolTaskExecutorBuilder` bean, a `SimpleAsyncTaskExecutorBuilder` bean and a `ThreadPoolTaskSchedulerBuilder` bean are made available in the context if a custom executor or scheduler needs to be created.
53-
The `SimpleAsyncTaskExecutorBuilder` is auto-configured to use virtual threads if they are enabled (using Java 21+ and configprop:spring.threads.virtual.enabled[] set to `true`).
55+
A `ThreadPoolTaskExecutorBuilder` bean, a `SimpleAsyncTaskExecutorBuilder` bean, a `ThreadPoolTaskSchedulerBuilder` bean and a `SimpleAsyncTaskSchedulerBuilder` are made available in the context if a custom executor or scheduler needs to be created.
56+
The `SimpleAsyncTaskExecutorBuilder` and `SimpleAsyncTaskSchedulerBuilder` beans are auto-configured to use virtual threads if they are enabled (using Java 21+ and configprop:spring.threads.virtual.enabled[] set to `true`).

0 commit comments

Comments
 (0)