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 759d336bc23..8436af58e39 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 @@ -77,12 +77,25 @@ public class TaskExecutionProperties { public static class Simple { + /** + * Whether to reject tasks when the concurrency limit has been reached. + */ + private boolean rejectTasksWhenLimitReached; + /** * Set the maximum number of parallel accesses allowed. -1 indicates no * concurrency limit at all. */ private Integer concurrencyLimit; + public boolean isRejectTasksWhenLimitReached() { + return this.rejectTasksWhenLimitReached; + } + + public void setRejectTasksWhenLimitReached(boolean rejectTasksWhenLimitReached) { + this.rejectTasksWhenLimitReached = rejectTasksWhenLimitReached; + } + public Integer getConcurrencyLimit() { return this.concurrencyLimit; } 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 6c07757aa32..776f16ccda5 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 @@ -135,6 +135,7 @@ class TaskExecutorConfigurations { builder = builder.customizers(this.taskExecutorCustomizers.orderedStream()::iterator); builder = builder.taskDecorator(this.taskDecorator.getIfUnique()); TaskExecutionProperties.Simple simple = this.properties.getSimple(); + builder = builder.rejectTasksWhenLimitReached(simple.isRejectTasksWhenLimitReached()); builder = builder.concurrencyLimit(simple.getConcurrencyLimit()); TaskExecutionProperties.Shutdown shutdown = this.properties.getShutdown(); if (shutdown.isAwaitTermination()) { 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 bb2bfad8ed4..7ab22fc6cfa 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 @@ -83,10 +83,12 @@ class TaskExecutionAutoConfigurationTests { void simpleAsyncTaskExecutorBuilderShouldReadProperties() { this.contextRunner .withPropertyValues("spring.task.execution.thread-name-prefix=mytest-", + "spring.task.execution.simple.reject-tasks-when-limit-reached=true", "spring.task.execution.simple.concurrency-limit=1", "spring.task.execution.shutdown.await-termination=true", "spring.task.execution.shutdown.await-termination-period=30s") .run(assertSimpleAsyncTaskExecutor((taskExecutor) -> { + assertThat(taskExecutor).hasFieldOrPropertyWithValue("rejectTasksWhenLimitReached", true); assertThat(taskExecutor.getConcurrencyLimit()).isEqualTo(1); assertThat(taskExecutor.getThreadNamePrefix()).isEqualTo("mytest-"); assertThat(taskExecutor).hasFieldOrPropertyWithValue("taskTerminationTimeout", 30000L); diff --git a/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/task/SimpleAsyncTaskExecutorBuilder.java b/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/task/SimpleAsyncTaskExecutorBuilder.java index e96c791329c..403199ce280 100644 --- a/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/task/SimpleAsyncTaskExecutorBuilder.java +++ b/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/task/SimpleAsyncTaskExecutorBuilder.java @@ -41,6 +41,7 @@ import org.springframework.util.CollectionUtils; * @author Stephane Nicoll * @author Filip Hrisafov * @author Moritz Halbritter + * @author Yanming Zhou * @since 3.2.0 */ public class SimpleAsyncTaskExecutorBuilder { @@ -49,6 +50,8 @@ public class SimpleAsyncTaskExecutorBuilder { private final String threadNamePrefix; + private final boolean rejectTasksWhenLimitReached; + private final Integer concurrencyLimit; private final TaskDecorator taskDecorator; @@ -58,14 +61,15 @@ public class SimpleAsyncTaskExecutorBuilder { private final Duration taskTerminationTimeout; public SimpleAsyncTaskExecutorBuilder() { - this(null, null, null, null, null, null); + this(null, null, false, null, null, null, null); } - private SimpleAsyncTaskExecutorBuilder(Boolean virtualThreads, String threadNamePrefix, Integer concurrencyLimit, - TaskDecorator taskDecorator, Set customizers, - Duration taskTerminationTimeout) { + private SimpleAsyncTaskExecutorBuilder(Boolean virtualThreads, String threadNamePrefix, + boolean rejectTasksWhenLimitReached, Integer concurrencyLimit, TaskDecorator taskDecorator, + Set customizers, Duration taskTerminationTimeout) { this.virtualThreads = virtualThreads; this.threadNamePrefix = threadNamePrefix; + this.rejectTasksWhenLimitReached = rejectTasksWhenLimitReached; this.concurrencyLimit = concurrencyLimit; this.taskDecorator = taskDecorator; this.customizers = customizers; @@ -78,8 +82,9 @@ public class SimpleAsyncTaskExecutorBuilder { * @return a new builder instance */ public SimpleAsyncTaskExecutorBuilder threadNamePrefix(String threadNamePrefix) { - return new SimpleAsyncTaskExecutorBuilder(this.virtualThreads, threadNamePrefix, this.concurrencyLimit, - this.taskDecorator, this.customizers, this.taskTerminationTimeout); + return new SimpleAsyncTaskExecutorBuilder(this.virtualThreads, threadNamePrefix, + this.rejectTasksWhenLimitReached, this.concurrencyLimit, this.taskDecorator, this.customizers, + this.taskTerminationTimeout); } /** @@ -88,8 +93,24 @@ public class SimpleAsyncTaskExecutorBuilder { * @return a new builder instance */ public SimpleAsyncTaskExecutorBuilder virtualThreads(Boolean virtualThreads) { - return new SimpleAsyncTaskExecutorBuilder(virtualThreads, this.threadNamePrefix, this.concurrencyLimit, - this.taskDecorator, this.customizers, this.taskTerminationTimeout); + return new SimpleAsyncTaskExecutorBuilder(virtualThreads, this.threadNamePrefix, + this.rejectTasksWhenLimitReached, this.concurrencyLimit, this.taskDecorator, this.customizers, + this.taskTerminationTimeout); + } + + /** + * Set whether to reject tasks when the concurrency limit has been reached. By default + * {@code false} to block the caller until the submission can be accepted. Switch to + * {@code true} for immediate rejection instead. + * @param rejectTasksWhenLimitReached whether to reject tasks when the concurrency + * limit has been reached + * @return a new builder instance + * @since 3.5.0 + */ + public SimpleAsyncTaskExecutorBuilder rejectTasksWhenLimitReached(boolean rejectTasksWhenLimitReached) { + return new SimpleAsyncTaskExecutorBuilder(this.virtualThreads, this.threadNamePrefix, + rejectTasksWhenLimitReached, this.concurrencyLimit, this.taskDecorator, this.customizers, + this.taskTerminationTimeout); } /** @@ -98,8 +119,9 @@ public class SimpleAsyncTaskExecutorBuilder { * @return a new builder instance */ public SimpleAsyncTaskExecutorBuilder concurrencyLimit(Integer concurrencyLimit) { - return new SimpleAsyncTaskExecutorBuilder(this.virtualThreads, this.threadNamePrefix, concurrencyLimit, - this.taskDecorator, this.customizers, this.taskTerminationTimeout); + return new SimpleAsyncTaskExecutorBuilder(this.virtualThreads, this.threadNamePrefix, + this.rejectTasksWhenLimitReached, concurrencyLimit, this.taskDecorator, this.customizers, + this.taskTerminationTimeout); } /** @@ -108,8 +130,9 @@ public class SimpleAsyncTaskExecutorBuilder { * @return a new builder instance */ public SimpleAsyncTaskExecutorBuilder taskDecorator(TaskDecorator taskDecorator) { - return new SimpleAsyncTaskExecutorBuilder(this.virtualThreads, this.threadNamePrefix, this.concurrencyLimit, - taskDecorator, this.customizers, this.taskTerminationTimeout); + return new SimpleAsyncTaskExecutorBuilder(this.virtualThreads, this.threadNamePrefix, + this.rejectTasksWhenLimitReached, this.concurrencyLimit, taskDecorator, this.customizers, + this.taskTerminationTimeout); } /** @@ -119,8 +142,9 @@ public class SimpleAsyncTaskExecutorBuilder { * @since 3.2.1 */ public SimpleAsyncTaskExecutorBuilder taskTerminationTimeout(Duration taskTerminationTimeout) { - return new SimpleAsyncTaskExecutorBuilder(this.virtualThreads, this.threadNamePrefix, this.concurrencyLimit, - this.taskDecorator, this.customizers, taskTerminationTimeout); + return new SimpleAsyncTaskExecutorBuilder(this.virtualThreads, this.threadNamePrefix, + this.rejectTasksWhenLimitReached, this.concurrencyLimit, this.taskDecorator, this.customizers, + taskTerminationTimeout); } /** @@ -149,8 +173,9 @@ public class SimpleAsyncTaskExecutorBuilder { public SimpleAsyncTaskExecutorBuilder customizers( Iterable customizers) { Assert.notNull(customizers, "'customizers' must not be null"); - return new SimpleAsyncTaskExecutorBuilder(this.virtualThreads, this.threadNamePrefix, this.concurrencyLimit, - this.taskDecorator, append(null, customizers), this.taskTerminationTimeout); + return new SimpleAsyncTaskExecutorBuilder(this.virtualThreads, this.threadNamePrefix, + this.rejectTasksWhenLimitReached, this.concurrencyLimit, this.taskDecorator, append(null, customizers), + this.taskTerminationTimeout); } /** @@ -177,8 +202,9 @@ public class SimpleAsyncTaskExecutorBuilder { public SimpleAsyncTaskExecutorBuilder additionalCustomizers( Iterable customizers) { Assert.notNull(customizers, "'customizers' must not be null"); - return new SimpleAsyncTaskExecutorBuilder(this.virtualThreads, this.threadNamePrefix, this.concurrencyLimit, - this.taskDecorator, append(this.customizers, customizers), this.taskTerminationTimeout); + return new SimpleAsyncTaskExecutorBuilder(this.virtualThreads, this.threadNamePrefix, + this.rejectTasksWhenLimitReached, this.concurrencyLimit, this.taskDecorator, + append(this.customizers, customizers), this.taskTerminationTimeout); } /** @@ -217,6 +243,7 @@ public class SimpleAsyncTaskExecutorBuilder { PropertyMapper map = PropertyMapper.get().alwaysApplyingWhenNonNull(); map.from(this.virtualThreads).to(taskExecutor::setVirtualThreads); map.from(this.threadNamePrefix).whenHasText().to(taskExecutor::setThreadNamePrefix); + map.from(this.rejectTasksWhenLimitReached).to(taskExecutor::setRejectTasksWhenLimitReached); map.from(this.concurrencyLimit).to(taskExecutor::setConcurrencyLimit); map.from(this.taskDecorator).to(taskExecutor::setTaskDecorator); map.from(this.taskTerminationTimeout).as(Duration::toMillis).to(taskExecutor::setTaskTerminationTimeout); diff --git a/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/task/SimpleAsyncTaskExecutorBuilderTests.java b/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/task/SimpleAsyncTaskExecutorBuilderTests.java index a7f13deb804..eb09d5aa944 100644 --- a/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/task/SimpleAsyncTaskExecutorBuilderTests.java +++ b/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/task/SimpleAsyncTaskExecutorBuilderTests.java @@ -40,6 +40,7 @@ import static org.mockito.Mockito.spy; * @author Stephane Nicoll * @author Filip Hrisafov * @author Moritz Halbritter + * @author Yanming Zhou */ class SimpleAsyncTaskExecutorBuilderTests { @@ -58,6 +59,12 @@ class SimpleAsyncTaskExecutorBuilderTests { SimpleAsyncTaskExecutorAssert.assertThat(executor).usesVirtualThreads(); } + @Test + void rejectTasksWhenLimitReachedShouldApply() { + SimpleAsyncTaskExecutor executor = this.builder.rejectTasksWhenLimitReached(true).build(); + assertThat(executor).extracting("rejectTasksWhenLimitReached").isEqualTo(true); + } + @Test void concurrencyLimitShouldApply() { SimpleAsyncTaskExecutor executor = this.builder.concurrencyLimit(1).build();