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 9530f198289a..3926d2997d3c 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 @@ * * @author Stephane Nicoll * @author Filip Hrisafov + * @author Yanming Zhou * @since 2.1.0 */ @ConfigurationProperties("spring.task.execution") @@ -154,6 +155,12 @@ public void setKeepAlive(Duration keepAlive) { public static class Shutdown { + /** + * Whether to accept further tasks after the application context close phase has + * begun. + */ + private boolean acceptTasksAfterContextClose; + /** * Whether the executor should wait for scheduled tasks to complete on shutdown. */ @@ -164,6 +171,14 @@ public static class Shutdown { */ private Duration awaitTerminationPeriod; + public boolean isAcceptTasksAfterContextClose() { + return this.acceptTasksAfterContextClose; + } + + public void setAcceptTasksAfterContextClose(boolean acceptTasksAfterContextClose) { + this.acceptTasksAfterContextClose = acceptTasksAfterContextClose; + } + public boolean isAwaitTermination() { return this.awaitTermination; } 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 805d5b336ce5..5d54cfd50e48 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 @@ * * @author Andy Wilkinson * @author Moritz Halbritter + * @author Yanming Zhou */ class TaskExecutorConfigurations { @@ -120,6 +121,7 @@ ThreadPoolTaskExecutorBuilder threadPoolTaskExecutorBuilder(TaskExecutionPropert builder = builder.allowCoreThreadTimeOut(pool.isAllowCoreThreadTimeout()); builder = builder.keepAlive(pool.getKeepAlive()); TaskExecutionProperties.Shutdown shutdown = properties.getShutdown(); + builder = builder.acceptTasksAfterContextClose(shutdown.isAcceptTasksAfterContextClose()); builder = builder.awaitTermination(shutdown.isAwaitTermination()); builder = builder.awaitTerminationPeriod(shutdown.getAwaitTerminationPeriod()); builder = builder.threadNamePrefix(properties.getThreadNamePrefix()); 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 88df814860ce..fad7d0c2673c 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 @@ * @author Stephane Nicoll * @author Camille Vienot * @author Moritz Halbritter + * @author Yanming Zhou */ @ExtendWith(OutputCaptureExtension.class) @SuppressWarnings("removal") @@ -124,19 +125,20 @@ void simpleAsyncTaskExecutorBuilderShouldReadProperties() { @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.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 2609245832ad..ee993a1ef3a9 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 @@ * * @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 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 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 @@ private ThreadPoolTaskExecutorBuilder(Integer queueCapacity, Integer corePoolSiz */ 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 ThreadPoolTaskExecutorBuilder queueCapacity(int queueCapacity) { */ 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 ThreadPoolTaskExecutorBuilder corePoolSize(int corePoolSize) { */ 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 ThreadPoolTaskExecutorBuilder maxPoolSize(int maxPoolSize) { */ 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 ThreadPoolTaskExecutorBuilder allowCoreThreadTimeOut(boolean allowCoreThr */ 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 ThreadPoolTaskExecutorBuilder keepAlive(Duration keepAlive) { */ 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 ThreadPoolTaskExecutorBuilder awaitTermination(boolean awaitTermination) */ 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 ThreadPoolTaskExecutorBuilder awaitTerminationPeriod(Duration awaitTermin */ 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 ThreadPoolTaskExecutorBuilder threadNamePrefix(String threadNamePrefix) { */ 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 ThreadPoolTaskExecutorBuilder customizers(ThreadPoolTaskExecutorCustomize 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 ThreadPoolTaskExecutorBuilder additionalCustomizers( 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 T configure(T taskExecutor) { 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 b57ffc6905a9..8b8dc650e680 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 @@ * * @author Stephane Nicoll * @author Filip Hrisafov + * @author Yanming Zhou */ class ThreadPoolTaskExecutorBuilderTests { @@ -56,6 +57,12 @@ void poolSettingsShouldApply() { 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();