Skip to content

Commit 3b47ba2

Browse files
filiphrsnicoll
authored andcommitted
Add support for task executor shutdown related properties
See gh-15951
1 parent 9540905 commit 3b47ba2

File tree

5 files changed

+136
-15
lines changed

5 files changed

+136
-15
lines changed

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

+3
Original file line numberDiff line numberDiff line change
@@ -75,6 +75,9 @@ public TaskExecutorBuilder taskExecutorBuilder() {
7575
builder = builder.allowCoreThreadTimeOut(pool.isAllowCoreThreadTimeout());
7676
builder = builder.keepAlive(pool.getKeepAlive());
7777
builder = builder.threadNamePrefix(this.properties.getThreadNamePrefix());
78+
builder = builder.awaitTermination(this.properties.getAwaitTermination());
79+
builder = builder.waitForTasksToCompleteOnShutdown(
80+
this.properties.isWaitForTasksToCompleteOnShutdown());
7881
builder = builder.customizers(this.taskExecutorCustomizers);
7982
builder = builder.taskDecorator(this.taskDecorator.getIfUnique());
8083
return builder;

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

+34
Original file line numberDiff line numberDiff line change
@@ -17,13 +17,16 @@
1717
package org.springframework.boot.autoconfigure.task;
1818

1919
import java.time.Duration;
20+
import java.time.temporal.ChronoUnit;
2021

2122
import org.springframework.boot.context.properties.ConfigurationProperties;
23+
import org.springframework.boot.convert.DurationUnit;
2224

2325
/**
2426
* Configuration properties for task execution.
2527
*
2628
* @author Stephane Nicoll
29+
* @author Filip Hrisafov
2730
* @since 2.1.0
2831
*/
2932
@ConfigurationProperties("spring.task.execution")
@@ -36,6 +39,20 @@ public class TaskExecutionProperties {
3639
*/
3740
private String threadNamePrefix = "task-";
3841

42+
/**
43+
* Maximum number of time that the executor is supposed to block on shutdown waiting
44+
* for remaining tasks to complete. This is particularly useful if your remaining
45+
* tasks are likely to need access to other resources that are also managed by the
46+
* container. If a duration suffix is not specified, seconds will be used.
47+
*/
48+
@DurationUnit(ChronoUnit.SECONDS)
49+
private Duration awaitTermination;
50+
51+
/**
52+
* Whether the executor should wait for scheduled tasks to complete on shutdown.
53+
*/
54+
private boolean waitForTasksToCompleteOnShutdown = false;
55+
3956
public Pool getPool() {
4057
return this.pool;
4158
}
@@ -48,6 +65,23 @@ public void setThreadNamePrefix(String threadNamePrefix) {
4865
this.threadNamePrefix = threadNamePrefix;
4966
}
5067

68+
public Duration getAwaitTermination() {
69+
return this.awaitTermination;
70+
}
71+
72+
public void setAwaitTermination(Duration awaitTermination) {
73+
this.awaitTermination = awaitTermination;
74+
}
75+
76+
public boolean isWaitForTasksToCompleteOnShutdown() {
77+
return this.waitForTasksToCompleteOnShutdown;
78+
}
79+
80+
public void setWaitForTasksToCompleteOnShutdown(
81+
boolean waitForTasksToCompleteOnShutdown) {
82+
this.waitForTasksToCompleteOnShutdown = waitForTasksToCompleteOnShutdown;
83+
}
84+
5185
public static class Pool {
5286

5387
/**

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

+13-7
Original file line numberDiff line numberDiff line change
@@ -63,13 +63,15 @@ public class TaskExecutionAutoConfigurationTests {
6363

6464
@Test
6565
public void taskExecutorBuilderShouldApplyCustomSettings() {
66-
this.contextRunner
67-
.withPropertyValues("spring.task.execution.pool.queue-capacity=10",
68-
"spring.task.execution.pool.core-size=2",
69-
"spring.task.execution.pool.max-size=4",
70-
"spring.task.execution.pool.allow-core-thread-timeout=true",
71-
"spring.task.execution.pool.keep-alive=5s",
72-
"spring.task.execution.thread-name-prefix=mytest-")
66+
this.contextRunner.withPropertyValues(
67+
"spring.task.execution.pool.queue-capacity=10",
68+
"spring.task.execution.pool.core-size=2",
69+
"spring.task.execution.pool.max-size=4",
70+
"spring.task.execution.pool.allow-core-thread-timeout=true",
71+
"spring.task.execution.pool.keep-alive=5s",
72+
"spring.task.execution.thread-name-prefix=mytest-",
73+
"spring.task.execution.await-termination=30s",
74+
"spring.task.execution.wait-for-tasks-to-complete-on-shutdown=true")
7375
.run(assertTaskExecutor((taskExecutor) -> {
7476
assertThat(taskExecutor).hasFieldOrPropertyWithValue("queueCapacity",
7577
10);
@@ -79,6 +81,10 @@ public void taskExecutorBuilderShouldApplyCustomSettings() {
7981
.hasFieldOrPropertyWithValue("allowCoreThreadTimeOut", true);
8082
assertThat(taskExecutor.getKeepAliveSeconds()).isEqualTo(5);
8183
assertThat(taskExecutor.getThreadNamePrefix()).isEqualTo("mytest-");
84+
assertThat(taskExecutor)
85+
.hasFieldOrPropertyWithValue("awaitTerminationSeconds", 30);
86+
assertThat(taskExecutor).hasFieldOrPropertyWithValue(
87+
"waitForTasksToCompleteOnShutdown", true);
8288
}));
8389
}
8490

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

+66-7
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,7 @@
4040
* bean and can be injected whenever a {@link TaskExecutor} is needed.
4141
*
4242
* @author Stephane Nicoll
43+
* @author Filip Hrisafov
4344
* @since 2.1.0
4445
*/
4546
public class TaskExecutorBuilder {
@@ -56,6 +57,10 @@ public class TaskExecutorBuilder {
5657

5758
private final String threadNamePrefix;
5859

60+
private final Duration awaitTermination;
61+
62+
private final Boolean waitForTasksToCompleteOnShutdown;
63+
5964
private final TaskDecorator taskDecorator;
6065

6166
private final Set<TaskExecutorCustomizer> customizers;
@@ -67,20 +72,25 @@ public TaskExecutorBuilder() {
6772
this.allowCoreThreadTimeOut = null;
6873
this.keepAlive = null;
6974
this.threadNamePrefix = null;
75+
this.awaitTermination = null;
76+
this.waitForTasksToCompleteOnShutdown = null;
7077
this.taskDecorator = null;
7178
this.customizers = null;
7279
}
7380

7481
private TaskExecutorBuilder(Integer queueCapacity, Integer corePoolSize,
7582
Integer maxPoolSize, Boolean allowCoreThreadTimeOut, Duration keepAlive,
76-
String threadNamePrefix, TaskDecorator taskDecorator,
83+
String threadNamePrefix, Duration awaitTermination,
84+
Boolean waitForTasksToCompleteOnShutdown, TaskDecorator taskDecorator,
7785
Set<TaskExecutorCustomizer> customizers) {
7886
this.queueCapacity = queueCapacity;
7987
this.corePoolSize = corePoolSize;
8088
this.maxPoolSize = maxPoolSize;
8189
this.allowCoreThreadTimeOut = allowCoreThreadTimeOut;
8290
this.keepAlive = keepAlive;
8391
this.threadNamePrefix = threadNamePrefix;
92+
this.awaitTermination = awaitTermination;
93+
this.waitForTasksToCompleteOnShutdown = waitForTasksToCompleteOnShutdown;
8494
this.taskDecorator = taskDecorator;
8595
this.customizers = customizers;
8696
}
@@ -94,6 +104,7 @@ private TaskExecutorBuilder(Integer queueCapacity, Integer corePoolSize,
94104
public TaskExecutorBuilder queueCapacity(int queueCapacity) {
95105
return new TaskExecutorBuilder(queueCapacity, this.corePoolSize, this.maxPoolSize,
96106
this.allowCoreThreadTimeOut, this.keepAlive, this.threadNamePrefix,
107+
this.awaitTermination, this.waitForTasksToCompleteOnShutdown,
97108
this.taskDecorator, this.customizers);
98109
}
99110

@@ -109,6 +120,7 @@ public TaskExecutorBuilder queueCapacity(int queueCapacity) {
109120
public TaskExecutorBuilder corePoolSize(int corePoolSize) {
110121
return new TaskExecutorBuilder(this.queueCapacity, corePoolSize, this.maxPoolSize,
111122
this.allowCoreThreadTimeOut, this.keepAlive, this.threadNamePrefix,
123+
this.awaitTermination, this.waitForTasksToCompleteOnShutdown,
112124
this.taskDecorator, this.customizers);
113125
}
114126

@@ -124,6 +136,7 @@ public TaskExecutorBuilder corePoolSize(int corePoolSize) {
124136
public TaskExecutorBuilder maxPoolSize(int maxPoolSize) {
125137
return new TaskExecutorBuilder(this.queueCapacity, this.corePoolSize, maxPoolSize,
126138
this.allowCoreThreadTimeOut, this.keepAlive, this.threadNamePrefix,
139+
this.awaitTermination, this.waitForTasksToCompleteOnShutdown,
127140
this.taskDecorator, this.customizers);
128141
}
129142

@@ -136,7 +149,9 @@ public TaskExecutorBuilder maxPoolSize(int maxPoolSize) {
136149
public TaskExecutorBuilder allowCoreThreadTimeOut(boolean allowCoreThreadTimeOut) {
137150
return new TaskExecutorBuilder(this.queueCapacity, this.corePoolSize,
138151
this.maxPoolSize, allowCoreThreadTimeOut, this.keepAlive,
139-
this.threadNamePrefix, this.taskDecorator, this.customizers);
152+
this.threadNamePrefix, this.awaitTermination,
153+
this.waitForTasksToCompleteOnShutdown, this.taskDecorator,
154+
this.customizers);
140155
}
141156

142157
/**
@@ -147,7 +162,9 @@ public TaskExecutorBuilder allowCoreThreadTimeOut(boolean allowCoreThreadTimeOut
147162
public TaskExecutorBuilder keepAlive(Duration keepAlive) {
148163
return new TaskExecutorBuilder(this.queueCapacity, this.corePoolSize,
149164
this.maxPoolSize, this.allowCoreThreadTimeOut, keepAlive,
150-
this.threadNamePrefix, this.taskDecorator, this.customizers);
165+
this.threadNamePrefix, this.awaitTermination,
166+
this.waitForTasksToCompleteOnShutdown, this.taskDecorator,
167+
this.customizers);
151168
}
152169

153170
/**
@@ -158,7 +175,41 @@ public TaskExecutorBuilder keepAlive(Duration keepAlive) {
158175
public TaskExecutorBuilder threadNamePrefix(String threadNamePrefix) {
159176
return new TaskExecutorBuilder(this.queueCapacity, this.corePoolSize,
160177
this.maxPoolSize, this.allowCoreThreadTimeOut, this.keepAlive,
161-
threadNamePrefix, this.taskDecorator, this.customizers);
178+
threadNamePrefix, this.awaitTermination,
179+
this.waitForTasksToCompleteOnShutdown, this.taskDecorator,
180+
this.customizers);
181+
}
182+
183+
/**
184+
* Set the maximum number of time that the executor is supposed to block on shutdown
185+
* in order to wait for remaining tasks to complete their execution before the rest of
186+
* the container continues to shut down. This is particularly useful if your remaining
187+
* tasks are likely to need access to other resources that are also managed by the
188+
* container.
189+
* @param awaitTermination the await termination to set
190+
* @return a new builder instance
191+
*/
192+
public TaskExecutorBuilder awaitTermination(Duration awaitTermination) {
193+
return new TaskExecutorBuilder(this.queueCapacity, this.corePoolSize,
194+
this.maxPoolSize, this.allowCoreThreadTimeOut, this.keepAlive,
195+
this.threadNamePrefix, awaitTermination,
196+
this.waitForTasksToCompleteOnShutdown, this.taskDecorator,
197+
this.customizers);
198+
}
199+
200+
/**
201+
* Set whether the executor should wait for scheduled tasks to complete on shutdown,
202+
* not interrupting running tasks and executing all tasks in the queue.
203+
* @param waitForTasksToCompleteOnShutdown if executor needs to wait for the tasks to
204+
* complete on shutdown
205+
* @return a new builder instance
206+
*/
207+
public TaskExecutorBuilder waitForTasksToCompleteOnShutdown(
208+
boolean waitForTasksToCompleteOnShutdown) {
209+
return new TaskExecutorBuilder(this.queueCapacity, this.corePoolSize,
210+
this.maxPoolSize, this.allowCoreThreadTimeOut, this.keepAlive,
211+
this.threadNamePrefix, this.awaitTermination,
212+
waitForTasksToCompleteOnShutdown, this.taskDecorator, this.customizers);
162213
}
163214

164215
/**
@@ -169,7 +220,8 @@ public TaskExecutorBuilder threadNamePrefix(String threadNamePrefix) {
169220
public TaskExecutorBuilder taskDecorator(TaskDecorator taskDecorator) {
170221
return new TaskExecutorBuilder(this.queueCapacity, this.corePoolSize,
171222
this.maxPoolSize, this.allowCoreThreadTimeOut, this.keepAlive,
172-
this.threadNamePrefix, taskDecorator, this.customizers);
223+
this.threadNamePrefix, this.awaitTermination,
224+
this.waitForTasksToCompleteOnShutdown, taskDecorator, this.customizers);
173225
}
174226

175227
/**
@@ -199,7 +251,9 @@ public TaskExecutorBuilder customizers(Iterable<TaskExecutorCustomizer> customiz
199251
Assert.notNull(customizers, "Customizers must not be null");
200252
return new TaskExecutorBuilder(this.queueCapacity, this.corePoolSize,
201253
this.maxPoolSize, this.allowCoreThreadTimeOut, this.keepAlive,
202-
this.threadNamePrefix, this.taskDecorator, append(null, customizers));
254+
this.threadNamePrefix, this.awaitTermination,
255+
this.waitForTasksToCompleteOnShutdown, this.taskDecorator,
256+
append(null, customizers));
203257
}
204258

205259
/**
@@ -229,7 +283,8 @@ public TaskExecutorBuilder additionalCustomizers(
229283
Assert.notNull(customizers, "Customizers must not be null");
230284
return new TaskExecutorBuilder(this.queueCapacity, this.corePoolSize,
231285
this.maxPoolSize, this.allowCoreThreadTimeOut, this.keepAlive,
232-
this.threadNamePrefix, this.taskDecorator,
286+
this.threadNamePrefix, this.awaitTermination,
287+
this.waitForTasksToCompleteOnShutdown, this.taskDecorator,
233288
append(this.customizers, customizers));
234289
}
235290

@@ -275,6 +330,10 @@ public <T extends ThreadPoolTaskExecutor> T configure(T taskExecutor) {
275330
map.from(this.allowCoreThreadTimeOut).to(taskExecutor::setAllowCoreThreadTimeOut);
276331
map.from(this.threadNamePrefix).whenHasText()
277332
.to(taskExecutor::setThreadNamePrefix);
333+
map.from(this.awaitTermination).asInt(Duration::getSeconds)
334+
.to(taskExecutor::setAwaitTerminationSeconds);
335+
map.from(this.waitForTasksToCompleteOnShutdown)
336+
.to(taskExecutor::setWaitForTasksToCompleteOnShutdown);
278337
map.from(this.taskDecorator).to(taskExecutor::setTaskDecorator);
279338
if (!CollectionUtils.isEmpty(this.customizers)) {
280339
this.customizers.forEach((customizer) -> customizer.customize(taskExecutor));

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

+20-1
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,7 @@
3737
* Tests for {@link TaskExecutorBuilder}.
3838
*
3939
* @author Stephane Nicoll
40+
* @author Filip Hrisafov
4041
*/
4142
public class TaskExecutorBuilderTests {
4243

@@ -60,6 +61,21 @@ public void threadNamePrefixShouldApply() {
6061
assertThat(executor.getThreadNamePrefix()).isEqualTo("test-");
6162
}
6263

64+
@Test
65+
public void awaitTerminationShouldApply() {
66+
ThreadPoolTaskExecutor executor = this.builder
67+
.awaitTermination(Duration.ofMinutes(1)).build();
68+
assertThat(executor).hasFieldOrPropertyWithValue("awaitTerminationSeconds", 60);
69+
}
70+
71+
@Test
72+
public void waitForTasksToCompleteOnShutdownShouldApply() {
73+
ThreadPoolTaskExecutor executor = this.builder
74+
.waitForTasksToCompleteOnShutdown(true).build();
75+
assertThat(executor)
76+
.hasFieldOrPropertyWithValue("waitForTasksToCompleteOnShutdown", true);
77+
}
78+
6379
@Test
6480
public void taskDecoratorShouldApply() {
6581
TaskDecorator taskDecorator = mock(TaskDecorator.class);
@@ -97,14 +113,17 @@ public void customizersShouldBeAppliedLast() {
97113
ThreadPoolTaskExecutor executor = spy(new ThreadPoolTaskExecutor());
98114
this.builder.queueCapacity(10).corePoolSize(4).maxPoolSize(8)
99115
.allowCoreThreadTimeOut(true).keepAlive(Duration.ofMinutes(1))
100-
.threadNamePrefix("test-").taskDecorator(taskDecorator)
116+
.threadNamePrefix("test-").awaitTermination(Duration.ofSeconds(30))
117+
.waitForTasksToCompleteOnShutdown(true).taskDecorator(taskDecorator)
101118
.additionalCustomizers((taskExecutor) -> {
102119
verify(taskExecutor).setQueueCapacity(10);
103120
verify(taskExecutor).setCorePoolSize(4);
104121
verify(taskExecutor).setMaxPoolSize(8);
105122
verify(taskExecutor).setAllowCoreThreadTimeOut(true);
106123
verify(taskExecutor).setKeepAliveSeconds(60);
107124
verify(taskExecutor).setThreadNamePrefix("test-");
125+
verify(taskExecutor).setAwaitTerminationSeconds(30);
126+
verify(taskExecutor).setWaitForTasksToCompleteOnShutdown(true);
108127
verify(taskExecutor).setTaskDecorator(taskDecorator);
109128
});
110129
this.builder.configure(executor);

0 commit comments

Comments
 (0)