diff --git a/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/task/TaskExecutionProperties.java b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/task/TaskExecutionProperties.java index 9530f198289..2781e99a0c6 100644 --- a/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/task/TaskExecutionProperties.java +++ b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/task/TaskExecutionProperties.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2023 the original author or authors. + * Copyright 2012-2024 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -25,6 +25,7 @@ import org.springframework.boot.context.properties.ConfigurationProperties; * * @author Stephane Nicoll * @author Filip Hrisafov + * @author Yanming Zhou * @since 2.1.0 */ @ConfigurationProperties("spring.task.execution") @@ -110,6 +111,8 @@ public class TaskExecutionProperties { */ private Duration keepAlive = Duration.ofSeconds(60); + private final Shutdown shutdown = new Shutdown(); + public int getQueueCapacity() { return this.queueCapacity; } @@ -150,6 +153,28 @@ public class TaskExecutionProperties { this.keepAlive = keepAlive; } + public Shutdown getShutdown() { + return this.shutdown; + } + + public static class Shutdown { + + /** + * Whether to accept further tasks after the application context close phase + * has begun. + */ + private boolean acceptTasksAfterContextClose; + + public boolean isAcceptTasksAfterContextClose() { + return this.acceptTasksAfterContextClose; + } + + public void setAcceptTasksAfterContextClose(boolean acceptTasksAfterContextClose) { + this.acceptTasksAfterContextClose = acceptTasksAfterContextClose; + } + + } + } public static class Shutdown { diff --git a/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/task/TaskExecutorConfigurations.java b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/task/TaskExecutorConfigurations.java index 805d5b336ce..9e46e106391 100644 --- a/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/task/TaskExecutorConfigurations.java +++ b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/task/TaskExecutorConfigurations.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2023 the original author or authors. + * Copyright 2012-2024 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -43,6 +43,7 @@ import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor; * * @author Andy Wilkinson * @author Moritz Halbritter + * @author Yanming Zhou */ class TaskExecutorConfigurations { @@ -119,6 +120,7 @@ class TaskExecutorConfigurations { builder = builder.maxPoolSize(pool.getMaxSize()); builder = builder.allowCoreThreadTimeOut(pool.isAllowCoreThreadTimeout()); builder = builder.keepAlive(pool.getKeepAlive()); + builder = builder.acceptTasksAfterContextClose(pool.getShutdown().isAcceptTasksAfterContextClose()); TaskExecutionProperties.Shutdown shutdown = properties.getShutdown(); builder = builder.awaitTermination(shutdown.isAwaitTermination()); builder = builder.awaitTerminationPeriod(shutdown.getAwaitTerminationPeriod()); diff --git a/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/task/TaskExecutionAutoConfigurationTests.java b/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/task/TaskExecutionAutoConfigurationTests.java index 88df814860c..2ba81d5afea 100644 --- a/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/task/TaskExecutionAutoConfigurationTests.java +++ b/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/task/TaskExecutionAutoConfigurationTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2023 the original author or authors. + * Copyright 2012-2024 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -60,6 +60,7 @@ import static org.mockito.Mockito.mock; * @author Stephane Nicoll * @author Camille Vienot * @author Moritz Halbritter + * @author Yanming Zhou */ @ExtendWith(OutputCaptureExtension.class) @SuppressWarnings("removal") @@ -124,19 +125,20 @@ class TaskExecutionAutoConfigurationTests { @Test void threadPoolTaskExecutorBuilderShouldApplyCustomSettings() { - this.contextRunner - .withPropertyValues("spring.task.execution.pool.queue-capacity=10", - "spring.task.execution.pool.core-size=2", "spring.task.execution.pool.max-size=4", - "spring.task.execution.pool.allow-core-thread-timeout=true", - "spring.task.execution.pool.keep-alive=5s", "spring.task.execution.shutdown.await-termination=true", - "spring.task.execution.shutdown.await-termination-period=30s", - "spring.task.execution.thread-name-prefix=mytest-") + this.contextRunner.withPropertyValues("spring.task.execution.pool.queue-capacity=10", + "spring.task.execution.pool.core-size=2", "spring.task.execution.pool.max-size=4", + "spring.task.execution.pool.allow-core-thread-timeout=true", "spring.task.execution.pool.keep-alive=5s", + "spring.task.execution.pool.shutdown.accept-tasks-after-context-close=true", + "spring.task.execution.shutdown.await-termination=true", + "spring.task.execution.shutdown.await-termination-period=30s", + "spring.task.execution.thread-name-prefix=mytest-") .run(assertThreadPoolTaskExecutor((taskExecutor) -> { assertThat(taskExecutor).hasFieldOrPropertyWithValue("queueCapacity", 10); assertThat(taskExecutor.getCorePoolSize()).isEqualTo(2); assertThat(taskExecutor.getMaxPoolSize()).isEqualTo(4); assertThat(taskExecutor).hasFieldOrPropertyWithValue("allowCoreThreadTimeOut", true); assertThat(taskExecutor.getKeepAliveSeconds()).isEqualTo(5); + assertThat(taskExecutor).hasFieldOrPropertyWithValue("acceptTasksAfterContextClose", true); assertThat(taskExecutor).hasFieldOrPropertyWithValue("waitForTasksToCompleteOnShutdown", true); assertThat(taskExecutor).hasFieldOrPropertyWithValue("awaitTerminationMillis", 30000L); assertThat(taskExecutor.getThreadNamePrefix()).isEqualTo("mytest-"); diff --git a/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/task/ThreadPoolTaskExecutorBuilder.java b/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/task/ThreadPoolTaskExecutorBuilder.java index 2609245832a..ee993a1ef3a 100644 --- a/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/task/ThreadPoolTaskExecutorBuilder.java +++ b/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/task/ThreadPoolTaskExecutorBuilder.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2023 the original author or authors. + * Copyright 2012-2024 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -40,6 +40,7 @@ import org.springframework.util.CollectionUtils; * * @author Stephane Nicoll * @author Filip Hrisafov + * @author Yanming Zhou * @since 3.2.0 */ public class ThreadPoolTaskExecutorBuilder { @@ -54,6 +55,8 @@ public class ThreadPoolTaskExecutorBuilder { private final Duration keepAlive; + private final Boolean acceptTasksAfterContextClose; + private final Boolean awaitTermination; private final Duration awaitTerminationPeriod; @@ -70,6 +73,7 @@ public class ThreadPoolTaskExecutorBuilder { this.maxPoolSize = null; this.allowCoreThreadTimeOut = null; this.keepAlive = null; + this.acceptTasksAfterContextClose = null; this.awaitTermination = null; this.awaitTerminationPeriod = null; this.threadNamePrefix = null; @@ -78,14 +82,15 @@ public class ThreadPoolTaskExecutorBuilder { } private ThreadPoolTaskExecutorBuilder(Integer queueCapacity, Integer corePoolSize, Integer maxPoolSize, - Boolean allowCoreThreadTimeOut, Duration keepAlive, Boolean awaitTermination, - Duration awaitTerminationPeriod, String threadNamePrefix, TaskDecorator taskDecorator, - Set customizers) { + Boolean allowCoreThreadTimeOut, Duration keepAlive, Boolean acceptTasksAfterContextClose, + Boolean awaitTermination, Duration awaitTerminationPeriod, String threadNamePrefix, + TaskDecorator taskDecorator, Set customizers) { this.queueCapacity = queueCapacity; this.corePoolSize = corePoolSize; this.maxPoolSize = maxPoolSize; this.allowCoreThreadTimeOut = allowCoreThreadTimeOut; this.keepAlive = keepAlive; + this.acceptTasksAfterContextClose = acceptTasksAfterContextClose; this.awaitTermination = awaitTermination; this.awaitTerminationPeriod = awaitTerminationPeriod; this.threadNamePrefix = threadNamePrefix; @@ -101,8 +106,8 @@ public class ThreadPoolTaskExecutorBuilder { */ public ThreadPoolTaskExecutorBuilder queueCapacity(int queueCapacity) { return new ThreadPoolTaskExecutorBuilder(queueCapacity, this.corePoolSize, this.maxPoolSize, - this.allowCoreThreadTimeOut, this.keepAlive, this.awaitTermination, this.awaitTerminationPeriod, - this.threadNamePrefix, this.taskDecorator, this.customizers); + this.allowCoreThreadTimeOut, this.keepAlive, this.acceptTasksAfterContextClose, this.awaitTermination, + this.awaitTerminationPeriod, this.threadNamePrefix, this.taskDecorator, this.customizers); } /** @@ -116,8 +121,8 @@ public class ThreadPoolTaskExecutorBuilder { */ public ThreadPoolTaskExecutorBuilder corePoolSize(int corePoolSize) { return new ThreadPoolTaskExecutorBuilder(this.queueCapacity, corePoolSize, this.maxPoolSize, - this.allowCoreThreadTimeOut, this.keepAlive, this.awaitTermination, this.awaitTerminationPeriod, - this.threadNamePrefix, this.taskDecorator, this.customizers); + this.allowCoreThreadTimeOut, this.keepAlive, this.acceptTasksAfterContextClose, this.awaitTermination, + this.awaitTerminationPeriod, this.threadNamePrefix, this.taskDecorator, this.customizers); } /** @@ -131,8 +136,8 @@ public class ThreadPoolTaskExecutorBuilder { */ public ThreadPoolTaskExecutorBuilder maxPoolSize(int maxPoolSize) { return new ThreadPoolTaskExecutorBuilder(this.queueCapacity, this.corePoolSize, maxPoolSize, - this.allowCoreThreadTimeOut, this.keepAlive, this.awaitTermination, this.awaitTerminationPeriod, - this.threadNamePrefix, this.taskDecorator, this.customizers); + this.allowCoreThreadTimeOut, this.keepAlive, this.acceptTasksAfterContextClose, this.awaitTermination, + this.awaitTerminationPeriod, this.threadNamePrefix, this.taskDecorator, this.customizers); } /** @@ -143,8 +148,8 @@ public class ThreadPoolTaskExecutorBuilder { */ public ThreadPoolTaskExecutorBuilder allowCoreThreadTimeOut(boolean allowCoreThreadTimeOut) { return new ThreadPoolTaskExecutorBuilder(this.queueCapacity, this.corePoolSize, this.maxPoolSize, - allowCoreThreadTimeOut, this.keepAlive, this.awaitTermination, this.awaitTerminationPeriod, - this.threadNamePrefix, this.taskDecorator, this.customizers); + allowCoreThreadTimeOut, this.keepAlive, this.acceptTasksAfterContextClose, this.awaitTermination, + this.awaitTerminationPeriod, this.threadNamePrefix, this.taskDecorator, this.customizers); } /** @@ -154,8 +159,21 @@ public class ThreadPoolTaskExecutorBuilder { */ public ThreadPoolTaskExecutorBuilder keepAlive(Duration keepAlive) { return new ThreadPoolTaskExecutorBuilder(this.queueCapacity, this.corePoolSize, this.maxPoolSize, - this.allowCoreThreadTimeOut, keepAlive, this.awaitTermination, this.awaitTerminationPeriod, - this.threadNamePrefix, this.taskDecorator, this.customizers); + this.allowCoreThreadTimeOut, keepAlive, this.acceptTasksAfterContextClose, this.awaitTermination, + this.awaitTerminationPeriod, this.threadNamePrefix, this.taskDecorator, this.customizers); + } + + /** + * Set whether to accept further tasks after the application context close phase has + * begun. + * @param acceptTasksAfterContextClose to accept further tasks after the application + * context close phase has begun + * @return a new builder instance + */ + public ThreadPoolTaskExecutorBuilder acceptTasksAfterContextClose(boolean acceptTasksAfterContextClose) { + return new ThreadPoolTaskExecutorBuilder(this.queueCapacity, this.corePoolSize, this.maxPoolSize, + this.allowCoreThreadTimeOut, this.keepAlive, acceptTasksAfterContextClose, this.awaitTermination, + this.awaitTerminationPeriod, this.threadNamePrefix, this.taskDecorator, this.customizers); } /** @@ -168,8 +186,8 @@ public class ThreadPoolTaskExecutorBuilder { */ public ThreadPoolTaskExecutorBuilder awaitTermination(boolean awaitTermination) { return new ThreadPoolTaskExecutorBuilder(this.queueCapacity, this.corePoolSize, this.maxPoolSize, - this.allowCoreThreadTimeOut, this.keepAlive, awaitTermination, this.awaitTerminationPeriod, - this.threadNamePrefix, this.taskDecorator, this.customizers); + this.allowCoreThreadTimeOut, this.keepAlive, this.acceptTasksAfterContextClose, awaitTermination, + this.awaitTerminationPeriod, this.threadNamePrefix, this.taskDecorator, this.customizers); } /** @@ -183,8 +201,8 @@ public class ThreadPoolTaskExecutorBuilder { */ public ThreadPoolTaskExecutorBuilder awaitTerminationPeriod(Duration awaitTerminationPeriod) { return new ThreadPoolTaskExecutorBuilder(this.queueCapacity, this.corePoolSize, this.maxPoolSize, - this.allowCoreThreadTimeOut, this.keepAlive, this.awaitTermination, awaitTerminationPeriod, - this.threadNamePrefix, this.taskDecorator, this.customizers); + this.allowCoreThreadTimeOut, this.keepAlive, this.acceptTasksAfterContextClose, this.awaitTermination, + awaitTerminationPeriod, this.threadNamePrefix, this.taskDecorator, this.customizers); } /** @@ -194,8 +212,8 @@ public class ThreadPoolTaskExecutorBuilder { */ public ThreadPoolTaskExecutorBuilder threadNamePrefix(String threadNamePrefix) { return new ThreadPoolTaskExecutorBuilder(this.queueCapacity, this.corePoolSize, this.maxPoolSize, - this.allowCoreThreadTimeOut, this.keepAlive, this.awaitTermination, this.awaitTerminationPeriod, - threadNamePrefix, this.taskDecorator, this.customizers); + this.allowCoreThreadTimeOut, this.keepAlive, this.acceptTasksAfterContextClose, this.awaitTermination, + this.awaitTerminationPeriod, threadNamePrefix, this.taskDecorator, this.customizers); } /** @@ -205,8 +223,8 @@ public class ThreadPoolTaskExecutorBuilder { */ public ThreadPoolTaskExecutorBuilder taskDecorator(TaskDecorator taskDecorator) { return new ThreadPoolTaskExecutorBuilder(this.queueCapacity, this.corePoolSize, this.maxPoolSize, - this.allowCoreThreadTimeOut, this.keepAlive, this.awaitTermination, this.awaitTerminationPeriod, - this.threadNamePrefix, taskDecorator, this.customizers); + this.allowCoreThreadTimeOut, this.keepAlive, this.acceptTasksAfterContextClose, this.awaitTermination, + this.awaitTerminationPeriod, this.threadNamePrefix, taskDecorator, this.customizers); } /** @@ -235,8 +253,8 @@ public class ThreadPoolTaskExecutorBuilder { public ThreadPoolTaskExecutorBuilder customizers(Iterable customizers) { Assert.notNull(customizers, "Customizers must not be null"); return new ThreadPoolTaskExecutorBuilder(this.queueCapacity, this.corePoolSize, this.maxPoolSize, - this.allowCoreThreadTimeOut, this.keepAlive, this.awaitTermination, this.awaitTerminationPeriod, - this.threadNamePrefix, this.taskDecorator, append(null, customizers)); + this.allowCoreThreadTimeOut, this.keepAlive, this.acceptTasksAfterContextClose, this.awaitTermination, + this.awaitTerminationPeriod, this.threadNamePrefix, this.taskDecorator, append(null, customizers)); } /** @@ -264,8 +282,9 @@ public class ThreadPoolTaskExecutorBuilder { Iterable customizers) { Assert.notNull(customizers, "Customizers must not be null"); return new ThreadPoolTaskExecutorBuilder(this.queueCapacity, this.corePoolSize, this.maxPoolSize, - this.allowCoreThreadTimeOut, this.keepAlive, this.awaitTermination, this.awaitTerminationPeriod, - this.threadNamePrefix, this.taskDecorator, append(this.customizers, customizers)); + this.allowCoreThreadTimeOut, this.keepAlive, this.acceptTasksAfterContextClose, this.awaitTermination, + this.awaitTerminationPeriod, this.threadNamePrefix, this.taskDecorator, + append(this.customizers, customizers)); } /** @@ -307,6 +326,7 @@ public class ThreadPoolTaskExecutorBuilder { map.from(this.maxPoolSize).to(taskExecutor::setMaxPoolSize); map.from(this.keepAlive).asInt(Duration::getSeconds).to(taskExecutor::setKeepAliveSeconds); map.from(this.allowCoreThreadTimeOut).to(taskExecutor::setAllowCoreThreadTimeOut); + map.from(this.acceptTasksAfterContextClose).to(taskExecutor::setAcceptTasksAfterContextClose); map.from(this.awaitTermination).to(taskExecutor::setWaitForTasksToCompleteOnShutdown); map.from(this.awaitTerminationPeriod).as(Duration::toMillis).to(taskExecutor::setAwaitTerminationMillis); map.from(this.threadNamePrefix).whenHasText().to(taskExecutor::setThreadNamePrefix); diff --git a/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/task/ThreadPoolTaskExecutorBuilderTests.java b/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/task/ThreadPoolTaskExecutorBuilderTests.java index b57ffc6905a..8b8dc650e68 100644 --- a/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/task/ThreadPoolTaskExecutorBuilderTests.java +++ b/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/task/ThreadPoolTaskExecutorBuilderTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2023 the original author or authors. + * Copyright 2012-2024 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -36,6 +36,7 @@ import static org.mockito.Mockito.spy; * * @author Stephane Nicoll * @author Filip Hrisafov + * @author Yanming Zhou */ class ThreadPoolTaskExecutorBuilderTests { @@ -56,6 +57,12 @@ class ThreadPoolTaskExecutorBuilderTests { assertThat(executor.getKeepAliveSeconds()).isEqualTo(60); } + @Test + void acceptTasksAfterContextCloseShouldApply() { + ThreadPoolTaskExecutor executor = this.builder.acceptTasksAfterContextClose(true).build(); + assertThat(executor).hasFieldOrPropertyWithValue("acceptTasksAfterContextClose", true); + } + @Test void awaitTerminationShouldApply() { ThreadPoolTaskExecutor executor = this.builder.awaitTermination(true).build();