From 3b47ba21a86701f6f96dcb5676c1873021e628c3 Mon Sep 17 00:00:00 2001 From: Filip Hrisafov Date: Thu, 14 Feb 2019 09:44:14 +0100 Subject: [PATCH 1/3] Add support for task executor shutdown related properties See gh-15951 --- .../task/TaskExecutionAutoConfiguration.java | 3 + .../task/TaskExecutionProperties.java | 34 +++++++++ .../TaskExecutionAutoConfigurationTests.java | 20 +++-- .../boot/task/TaskExecutorBuilder.java | 73 +++++++++++++++++-- .../boot/task/TaskExecutorBuilderTests.java | 21 +++++- 5 files changed, 136 insertions(+), 15 deletions(-) diff --git a/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/task/TaskExecutionAutoConfiguration.java b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/task/TaskExecutionAutoConfiguration.java index cbf2392922f..c48c2ec375c 100644 --- a/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/task/TaskExecutionAutoConfiguration.java +++ b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/task/TaskExecutionAutoConfiguration.java @@ -75,6 +75,9 @@ public class TaskExecutionAutoConfiguration { builder = builder.allowCoreThreadTimeOut(pool.isAllowCoreThreadTimeout()); builder = builder.keepAlive(pool.getKeepAlive()); builder = builder.threadNamePrefix(this.properties.getThreadNamePrefix()); + builder = builder.awaitTermination(this.properties.getAwaitTermination()); + builder = builder.waitForTasksToCompleteOnShutdown( + this.properties.isWaitForTasksToCompleteOnShutdown()); builder = builder.customizers(this.taskExecutorCustomizers); builder = builder.taskDecorator(this.taskDecorator.getIfUnique()); return builder; 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 437509c540a..99569251af3 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 @@ -17,13 +17,16 @@ package org.springframework.boot.autoconfigure.task; import java.time.Duration; +import java.time.temporal.ChronoUnit; import org.springframework.boot.context.properties.ConfigurationProperties; +import org.springframework.boot.convert.DurationUnit; /** * Configuration properties for task execution. * * @author Stephane Nicoll + * @author Filip Hrisafov * @since 2.1.0 */ @ConfigurationProperties("spring.task.execution") @@ -36,6 +39,20 @@ public class TaskExecutionProperties { */ private String threadNamePrefix = "task-"; + /** + * Maximum number of time that the executor is supposed to block on shutdown waiting + * for remaining tasks to complete. This is particularly useful if your remaining + * tasks are likely to need access to other resources that are also managed by the + * container. If a duration suffix is not specified, seconds will be used. + */ + @DurationUnit(ChronoUnit.SECONDS) + private Duration awaitTermination; + + /** + * Whether the executor should wait for scheduled tasks to complete on shutdown. + */ + private boolean waitForTasksToCompleteOnShutdown = false; + public Pool getPool() { return this.pool; } @@ -48,6 +65,23 @@ public class TaskExecutionProperties { this.threadNamePrefix = threadNamePrefix; } + public Duration getAwaitTermination() { + return this.awaitTermination; + } + + public void setAwaitTermination(Duration awaitTermination) { + this.awaitTermination = awaitTermination; + } + + public boolean isWaitForTasksToCompleteOnShutdown() { + return this.waitForTasksToCompleteOnShutdown; + } + + public void setWaitForTasksToCompleteOnShutdown( + boolean waitForTasksToCompleteOnShutdown) { + this.waitForTasksToCompleteOnShutdown = waitForTasksToCompleteOnShutdown; + } + public static class Pool { /** 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 bdb38406cc5..5669a94009a 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 @@ -63,13 +63,15 @@ public class TaskExecutionAutoConfigurationTests { @Test public void taskExecutorBuilderShouldApplyCustomSettings() { - 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.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.thread-name-prefix=mytest-", + "spring.task.execution.await-termination=30s", + "spring.task.execution.wait-for-tasks-to-complete-on-shutdown=true") .run(assertTaskExecutor((taskExecutor) -> { assertThat(taskExecutor).hasFieldOrPropertyWithValue("queueCapacity", 10); @@ -79,6 +81,10 @@ public class TaskExecutionAutoConfigurationTests { .hasFieldOrPropertyWithValue("allowCoreThreadTimeOut", true); assertThat(taskExecutor.getKeepAliveSeconds()).isEqualTo(5); assertThat(taskExecutor.getThreadNamePrefix()).isEqualTo("mytest-"); + assertThat(taskExecutor) + .hasFieldOrPropertyWithValue("awaitTerminationSeconds", 30); + assertThat(taskExecutor).hasFieldOrPropertyWithValue( + "waitForTasksToCompleteOnShutdown", true); })); } diff --git a/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/task/TaskExecutorBuilder.java b/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/task/TaskExecutorBuilder.java index eced7b6f95c..666a99ef9a7 100644 --- a/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/task/TaskExecutorBuilder.java +++ b/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/task/TaskExecutorBuilder.java @@ -40,6 +40,7 @@ import org.springframework.util.CollectionUtils; * bean and can be injected whenever a {@link TaskExecutor} is needed. * * @author Stephane Nicoll + * @author Filip Hrisafov * @since 2.1.0 */ public class TaskExecutorBuilder { @@ -56,6 +57,10 @@ public class TaskExecutorBuilder { private final String threadNamePrefix; + private final Duration awaitTermination; + + private final Boolean waitForTasksToCompleteOnShutdown; + private final TaskDecorator taskDecorator; private final Set customizers; @@ -67,13 +72,16 @@ public class TaskExecutorBuilder { this.allowCoreThreadTimeOut = null; this.keepAlive = null; this.threadNamePrefix = null; + this.awaitTermination = null; + this.waitForTasksToCompleteOnShutdown = null; this.taskDecorator = null; this.customizers = null; } private TaskExecutorBuilder(Integer queueCapacity, Integer corePoolSize, Integer maxPoolSize, Boolean allowCoreThreadTimeOut, Duration keepAlive, - String threadNamePrefix, TaskDecorator taskDecorator, + String threadNamePrefix, Duration awaitTermination, + Boolean waitForTasksToCompleteOnShutdown, TaskDecorator taskDecorator, Set customizers) { this.queueCapacity = queueCapacity; this.corePoolSize = corePoolSize; @@ -81,6 +89,8 @@ public class TaskExecutorBuilder { this.allowCoreThreadTimeOut = allowCoreThreadTimeOut; this.keepAlive = keepAlive; this.threadNamePrefix = threadNamePrefix; + this.awaitTermination = awaitTermination; + this.waitForTasksToCompleteOnShutdown = waitForTasksToCompleteOnShutdown; this.taskDecorator = taskDecorator; this.customizers = customizers; } @@ -94,6 +104,7 @@ public class TaskExecutorBuilder { public TaskExecutorBuilder queueCapacity(int queueCapacity) { return new TaskExecutorBuilder(queueCapacity, this.corePoolSize, this.maxPoolSize, this.allowCoreThreadTimeOut, this.keepAlive, this.threadNamePrefix, + this.awaitTermination, this.waitForTasksToCompleteOnShutdown, this.taskDecorator, this.customizers); } @@ -109,6 +120,7 @@ public class TaskExecutorBuilder { public TaskExecutorBuilder corePoolSize(int corePoolSize) { return new TaskExecutorBuilder(this.queueCapacity, corePoolSize, this.maxPoolSize, this.allowCoreThreadTimeOut, this.keepAlive, this.threadNamePrefix, + this.awaitTermination, this.waitForTasksToCompleteOnShutdown, this.taskDecorator, this.customizers); } @@ -124,6 +136,7 @@ public class TaskExecutorBuilder { public TaskExecutorBuilder maxPoolSize(int maxPoolSize) { return new TaskExecutorBuilder(this.queueCapacity, this.corePoolSize, maxPoolSize, this.allowCoreThreadTimeOut, this.keepAlive, this.threadNamePrefix, + this.awaitTermination, this.waitForTasksToCompleteOnShutdown, this.taskDecorator, this.customizers); } @@ -136,7 +149,9 @@ public class TaskExecutorBuilder { public TaskExecutorBuilder allowCoreThreadTimeOut(boolean allowCoreThreadTimeOut) { return new TaskExecutorBuilder(this.queueCapacity, this.corePoolSize, this.maxPoolSize, allowCoreThreadTimeOut, this.keepAlive, - this.threadNamePrefix, this.taskDecorator, this.customizers); + this.threadNamePrefix, this.awaitTermination, + this.waitForTasksToCompleteOnShutdown, this.taskDecorator, + this.customizers); } /** @@ -147,7 +162,9 @@ public class TaskExecutorBuilder { public TaskExecutorBuilder keepAlive(Duration keepAlive) { return new TaskExecutorBuilder(this.queueCapacity, this.corePoolSize, this.maxPoolSize, this.allowCoreThreadTimeOut, keepAlive, - this.threadNamePrefix, this.taskDecorator, this.customizers); + this.threadNamePrefix, this.awaitTermination, + this.waitForTasksToCompleteOnShutdown, this.taskDecorator, + this.customizers); } /** @@ -158,7 +175,41 @@ public class TaskExecutorBuilder { public TaskExecutorBuilder threadNamePrefix(String threadNamePrefix) { return new TaskExecutorBuilder(this.queueCapacity, this.corePoolSize, this.maxPoolSize, this.allowCoreThreadTimeOut, this.keepAlive, - threadNamePrefix, this.taskDecorator, this.customizers); + threadNamePrefix, this.awaitTermination, + this.waitForTasksToCompleteOnShutdown, this.taskDecorator, + this.customizers); + } + + /** + * Set the maximum number of time that the executor is supposed to block on shutdown + * in order to wait for remaining tasks to complete their execution before the rest of + * the container continues to shut down. This is particularly useful if your remaining + * tasks are likely to need access to other resources that are also managed by the + * container. + * @param awaitTermination the await termination to set + * @return a new builder instance + */ + public TaskExecutorBuilder awaitTermination(Duration awaitTermination) { + return new TaskExecutorBuilder(this.queueCapacity, this.corePoolSize, + this.maxPoolSize, this.allowCoreThreadTimeOut, this.keepAlive, + this.threadNamePrefix, awaitTermination, + this.waitForTasksToCompleteOnShutdown, this.taskDecorator, + this.customizers); + } + + /** + * Set whether the executor should wait for scheduled tasks to complete on shutdown, + * not interrupting running tasks and executing all tasks in the queue. + * @param waitForTasksToCompleteOnShutdown if executor needs to wait for the tasks to + * complete on shutdown + * @return a new builder instance + */ + public TaskExecutorBuilder waitForTasksToCompleteOnShutdown( + boolean waitForTasksToCompleteOnShutdown) { + return new TaskExecutorBuilder(this.queueCapacity, this.corePoolSize, + this.maxPoolSize, this.allowCoreThreadTimeOut, this.keepAlive, + this.threadNamePrefix, this.awaitTermination, + waitForTasksToCompleteOnShutdown, this.taskDecorator, this.customizers); } /** @@ -169,7 +220,8 @@ public class TaskExecutorBuilder { public TaskExecutorBuilder taskDecorator(TaskDecorator taskDecorator) { return new TaskExecutorBuilder(this.queueCapacity, this.corePoolSize, this.maxPoolSize, this.allowCoreThreadTimeOut, this.keepAlive, - this.threadNamePrefix, taskDecorator, this.customizers); + this.threadNamePrefix, this.awaitTermination, + this.waitForTasksToCompleteOnShutdown, taskDecorator, this.customizers); } /** @@ -199,7 +251,9 @@ public class TaskExecutorBuilder { Assert.notNull(customizers, "Customizers must not be null"); return new TaskExecutorBuilder(this.queueCapacity, this.corePoolSize, this.maxPoolSize, this.allowCoreThreadTimeOut, this.keepAlive, - this.threadNamePrefix, this.taskDecorator, append(null, customizers)); + this.threadNamePrefix, this.awaitTermination, + this.waitForTasksToCompleteOnShutdown, this.taskDecorator, + append(null, customizers)); } /** @@ -229,7 +283,8 @@ public class TaskExecutorBuilder { Assert.notNull(customizers, "Customizers must not be null"); return new TaskExecutorBuilder(this.queueCapacity, this.corePoolSize, this.maxPoolSize, this.allowCoreThreadTimeOut, this.keepAlive, - this.threadNamePrefix, this.taskDecorator, + this.threadNamePrefix, this.awaitTermination, + this.waitForTasksToCompleteOnShutdown, this.taskDecorator, append(this.customizers, customizers)); } @@ -275,6 +330,10 @@ public class TaskExecutorBuilder { map.from(this.allowCoreThreadTimeOut).to(taskExecutor::setAllowCoreThreadTimeOut); map.from(this.threadNamePrefix).whenHasText() .to(taskExecutor::setThreadNamePrefix); + map.from(this.awaitTermination).asInt(Duration::getSeconds) + .to(taskExecutor::setAwaitTerminationSeconds); + map.from(this.waitForTasksToCompleteOnShutdown) + .to(taskExecutor::setWaitForTasksToCompleteOnShutdown); map.from(this.taskDecorator).to(taskExecutor::setTaskDecorator); if (!CollectionUtils.isEmpty(this.customizers)) { this.customizers.forEach((customizer) -> customizer.customize(taskExecutor)); diff --git a/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/task/TaskExecutorBuilderTests.java b/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/task/TaskExecutorBuilderTests.java index f955694ea06..31581225344 100644 --- a/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/task/TaskExecutorBuilderTests.java +++ b/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/task/TaskExecutorBuilderTests.java @@ -37,6 +37,7 @@ import static org.mockito.Mockito.verifyZeroInteractions; * Tests for {@link TaskExecutorBuilder}. * * @author Stephane Nicoll + * @author Filip Hrisafov */ public class TaskExecutorBuilderTests { @@ -60,6 +61,21 @@ public class TaskExecutorBuilderTests { assertThat(executor.getThreadNamePrefix()).isEqualTo("test-"); } + @Test + public void awaitTerminationShouldApply() { + ThreadPoolTaskExecutor executor = this.builder + .awaitTermination(Duration.ofMinutes(1)).build(); + assertThat(executor).hasFieldOrPropertyWithValue("awaitTerminationSeconds", 60); + } + + @Test + public void waitForTasksToCompleteOnShutdownShouldApply() { + ThreadPoolTaskExecutor executor = this.builder + .waitForTasksToCompleteOnShutdown(true).build(); + assertThat(executor) + .hasFieldOrPropertyWithValue("waitForTasksToCompleteOnShutdown", true); + } + @Test public void taskDecoratorShouldApply() { TaskDecorator taskDecorator = mock(TaskDecorator.class); @@ -97,7 +113,8 @@ public class TaskExecutorBuilderTests { ThreadPoolTaskExecutor executor = spy(new ThreadPoolTaskExecutor()); this.builder.queueCapacity(10).corePoolSize(4).maxPoolSize(8) .allowCoreThreadTimeOut(true).keepAlive(Duration.ofMinutes(1)) - .threadNamePrefix("test-").taskDecorator(taskDecorator) + .threadNamePrefix("test-").awaitTermination(Duration.ofSeconds(30)) + .waitForTasksToCompleteOnShutdown(true).taskDecorator(taskDecorator) .additionalCustomizers((taskExecutor) -> { verify(taskExecutor).setQueueCapacity(10); verify(taskExecutor).setCorePoolSize(4); @@ -105,6 +122,8 @@ public class TaskExecutorBuilderTests { verify(taskExecutor).setAllowCoreThreadTimeOut(true); verify(taskExecutor).setKeepAliveSeconds(60); verify(taskExecutor).setThreadNamePrefix("test-"); + verify(taskExecutor).setAwaitTerminationSeconds(30); + verify(taskExecutor).setWaitForTasksToCompleteOnShutdown(true); verify(taskExecutor).setTaskDecorator(taskDecorator); }); this.builder.configure(executor); From d2cbf08f092345bf2a5cfb1893d089cbd403dc1d Mon Sep 17 00:00:00 2001 From: Stephane Nicoll Date: Mon, 18 Feb 2019 18:50:10 +0100 Subject: [PATCH 2/3] Polish "Add support for task executor shutdown related properties" Closes gh-15951 --- .../task/TaskExecutionAutoConfiguration.java | 7 +- .../task/TaskExecutionProperties.java | 71 ++++++----- .../TaskExecutionAutoConfigurationTests.java | 24 ++-- .../boot/task/TaskExecutorBuilder.java | 116 +++++++++--------- .../boot/task/TaskExecutorBuilderTests.java | 29 +++-- 5 files changed, 122 insertions(+), 125 deletions(-) diff --git a/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/task/TaskExecutionAutoConfiguration.java b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/task/TaskExecutionAutoConfiguration.java index c48c2ec375c..034957ee52b 100644 --- a/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/task/TaskExecutionAutoConfiguration.java +++ b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/task/TaskExecutionAutoConfiguration.java @@ -22,6 +22,7 @@ import org.springframework.beans.factory.ObjectProvider; import org.springframework.boot.autoconfigure.EnableAutoConfiguration; import org.springframework.boot.autoconfigure.condition.ConditionalOnClass; import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; +import org.springframework.boot.autoconfigure.task.TaskExecutionProperties.Shutdown; import org.springframework.boot.context.properties.EnableConfigurationProperties; import org.springframework.boot.task.TaskExecutorBuilder; import org.springframework.boot.task.TaskExecutorCustomizer; @@ -74,10 +75,10 @@ public class TaskExecutionAutoConfiguration { builder = builder.maxPoolSize(pool.getMaxSize()); builder = builder.allowCoreThreadTimeOut(pool.isAllowCoreThreadTimeout()); builder = builder.keepAlive(pool.getKeepAlive()); + Shutdown shutdown = this.properties.getShutdown(); + builder = builder.awaitTermination(shutdown.isAwaitTermination()); + builder = builder.awaitTerminationPeriod(shutdown.getAwaitTerminationPeriod()); builder = builder.threadNamePrefix(this.properties.getThreadNamePrefix()); - builder = builder.awaitTermination(this.properties.getAwaitTermination()); - builder = builder.waitForTasksToCompleteOnShutdown( - this.properties.isWaitForTasksToCompleteOnShutdown()); builder = builder.customizers(this.taskExecutorCustomizers); builder = builder.taskDecorator(this.taskDecorator.getIfUnique()); return builder; 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 99569251af3..e9c1d0bd511 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-2018 the original author or authors. + * Copyright 2012-2019 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. @@ -17,10 +17,8 @@ package org.springframework.boot.autoconfigure.task; import java.time.Duration; -import java.time.temporal.ChronoUnit; import org.springframework.boot.context.properties.ConfigurationProperties; -import org.springframework.boot.convert.DurationUnit; /** * Configuration properties for task execution. @@ -34,29 +32,21 @@ public class TaskExecutionProperties { private final Pool pool = new Pool(); + private final Shutdown shutdown = new Shutdown(); + /** * Prefix to use for the names of newly created threads. */ private String threadNamePrefix = "task-"; - /** - * Maximum number of time that the executor is supposed to block on shutdown waiting - * for remaining tasks to complete. This is particularly useful if your remaining - * tasks are likely to need access to other resources that are also managed by the - * container. If a duration suffix is not specified, seconds will be used. - */ - @DurationUnit(ChronoUnit.SECONDS) - private Duration awaitTermination; - - /** - * Whether the executor should wait for scheduled tasks to complete on shutdown. - */ - private boolean waitForTasksToCompleteOnShutdown = false; - public Pool getPool() { return this.pool; } + public Shutdown getShutdown() { + return this.shutdown; + } + public String getThreadNamePrefix() { return this.threadNamePrefix; } @@ -65,23 +55,6 @@ public class TaskExecutionProperties { this.threadNamePrefix = threadNamePrefix; } - public Duration getAwaitTermination() { - return this.awaitTermination; - } - - public void setAwaitTermination(Duration awaitTermination) { - this.awaitTermination = awaitTermination; - } - - public boolean isWaitForTasksToCompleteOnShutdown() { - return this.waitForTasksToCompleteOnShutdown; - } - - public void setWaitForTasksToCompleteOnShutdown( - boolean waitForTasksToCompleteOnShutdown) { - this.waitForTasksToCompleteOnShutdown = waitForTasksToCompleteOnShutdown; - } - public static class Pool { /** @@ -155,4 +128,34 @@ public class TaskExecutionProperties { } + public static class Shutdown { + + /** + * Whether the executor should wait for scheduled tasks to complete on shutdown. + */ + private boolean awaitTermination; + + /** + * Maximum time the executor should wait for remaining tasks to complete. + */ + private Duration awaitTerminationPeriod; + + public boolean isAwaitTermination() { + return this.awaitTermination; + } + + public void setAwaitTermination(boolean awaitTermination) { + this.awaitTermination = awaitTermination; + } + + public Duration getAwaitTerminationPeriod() { + return this.awaitTerminationPeriod; + } + + public void setAwaitTerminationPeriod(Duration awaitTerminationPeriod) { + this.awaitTerminationPeriod = awaitTerminationPeriod; + } + + } + } 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 5669a94009a..7e5256401c3 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 @@ -63,15 +63,15 @@ public class TaskExecutionAutoConfigurationTests { @Test public void taskExecutorBuilderShouldApplyCustomSettings() { - 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.thread-name-prefix=mytest-", - "spring.task.execution.await-termination=30s", - "spring.task.execution.wait-for-tasks-to-complete-on-shutdown=true") + 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-") .run(assertTaskExecutor((taskExecutor) -> { assertThat(taskExecutor).hasFieldOrPropertyWithValue("queueCapacity", 10); @@ -80,11 +80,11 @@ public class TaskExecutionAutoConfigurationTests { assertThat(taskExecutor) .hasFieldOrPropertyWithValue("allowCoreThreadTimeOut", true); assertThat(taskExecutor.getKeepAliveSeconds()).isEqualTo(5); - assertThat(taskExecutor.getThreadNamePrefix()).isEqualTo("mytest-"); - assertThat(taskExecutor) - .hasFieldOrPropertyWithValue("awaitTerminationSeconds", 30); assertThat(taskExecutor).hasFieldOrPropertyWithValue( "waitForTasksToCompleteOnShutdown", true); + assertThat(taskExecutor) + .hasFieldOrPropertyWithValue("awaitTerminationSeconds", 30); + assertThat(taskExecutor.getThreadNamePrefix()).isEqualTo("mytest-"); })); } diff --git a/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/task/TaskExecutorBuilder.java b/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/task/TaskExecutorBuilder.java index 666a99ef9a7..13b5919833b 100644 --- a/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/task/TaskExecutorBuilder.java +++ b/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/task/TaskExecutorBuilder.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2018 the original author or authors. + * Copyright 2012-2019 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. @@ -55,11 +55,11 @@ public class TaskExecutorBuilder { private final Duration keepAlive; - private final String threadNamePrefix; + private final Boolean awaitTermination; - private final Duration awaitTermination; + private final Duration awaitTerminationPeriod; - private final Boolean waitForTasksToCompleteOnShutdown; + private final String threadNamePrefix; private final TaskDecorator taskDecorator; @@ -71,26 +71,26 @@ public class TaskExecutorBuilder { this.maxPoolSize = null; this.allowCoreThreadTimeOut = null; this.keepAlive = null; - this.threadNamePrefix = null; this.awaitTermination = null; - this.waitForTasksToCompleteOnShutdown = null; + this.awaitTerminationPeriod = null; + this.threadNamePrefix = null; this.taskDecorator = null; this.customizers = null; } private TaskExecutorBuilder(Integer queueCapacity, Integer corePoolSize, Integer maxPoolSize, Boolean allowCoreThreadTimeOut, Duration keepAlive, - String threadNamePrefix, Duration awaitTermination, - Boolean waitForTasksToCompleteOnShutdown, TaskDecorator taskDecorator, + 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.threadNamePrefix = threadNamePrefix; this.awaitTermination = awaitTermination; - this.waitForTasksToCompleteOnShutdown = waitForTasksToCompleteOnShutdown; + this.awaitTerminationPeriod = awaitTerminationPeriod; + this.threadNamePrefix = threadNamePrefix; this.taskDecorator = taskDecorator; this.customizers = customizers; } @@ -103,9 +103,9 @@ public class TaskExecutorBuilder { */ public TaskExecutorBuilder queueCapacity(int queueCapacity) { return new TaskExecutorBuilder(queueCapacity, this.corePoolSize, this.maxPoolSize, - this.allowCoreThreadTimeOut, this.keepAlive, this.threadNamePrefix, - this.awaitTermination, this.waitForTasksToCompleteOnShutdown, - this.taskDecorator, this.customizers); + this.allowCoreThreadTimeOut, this.keepAlive, this.awaitTermination, + this.awaitTerminationPeriod, this.threadNamePrefix, this.taskDecorator, + this.customizers); } /** @@ -119,9 +119,9 @@ public class TaskExecutorBuilder { */ public TaskExecutorBuilder corePoolSize(int corePoolSize) { return new TaskExecutorBuilder(this.queueCapacity, corePoolSize, this.maxPoolSize, - this.allowCoreThreadTimeOut, this.keepAlive, this.threadNamePrefix, - this.awaitTermination, this.waitForTasksToCompleteOnShutdown, - this.taskDecorator, this.customizers); + this.allowCoreThreadTimeOut, this.keepAlive, this.awaitTermination, + this.awaitTerminationPeriod, this.threadNamePrefix, this.taskDecorator, + this.customizers); } /** @@ -135,9 +135,9 @@ public class TaskExecutorBuilder { */ public TaskExecutorBuilder maxPoolSize(int maxPoolSize) { return new TaskExecutorBuilder(this.queueCapacity, this.corePoolSize, maxPoolSize, - this.allowCoreThreadTimeOut, this.keepAlive, this.threadNamePrefix, - this.awaitTermination, this.waitForTasksToCompleteOnShutdown, - this.taskDecorator, this.customizers); + this.allowCoreThreadTimeOut, this.keepAlive, this.awaitTermination, + this.awaitTerminationPeriod, this.threadNamePrefix, this.taskDecorator, + this.customizers); } /** @@ -149,9 +149,8 @@ public class TaskExecutorBuilder { public TaskExecutorBuilder allowCoreThreadTimeOut(boolean allowCoreThreadTimeOut) { return new TaskExecutorBuilder(this.queueCapacity, this.corePoolSize, this.maxPoolSize, allowCoreThreadTimeOut, this.keepAlive, - this.threadNamePrefix, this.awaitTermination, - this.waitForTasksToCompleteOnShutdown, this.taskDecorator, - this.customizers); + this.awaitTermination, this.awaitTerminationPeriod, this.threadNamePrefix, + this.taskDecorator, this.customizers); } /** @@ -162,54 +161,51 @@ public class TaskExecutorBuilder { public TaskExecutorBuilder keepAlive(Duration keepAlive) { return new TaskExecutorBuilder(this.queueCapacity, this.corePoolSize, this.maxPoolSize, this.allowCoreThreadTimeOut, keepAlive, - this.threadNamePrefix, this.awaitTermination, - this.waitForTasksToCompleteOnShutdown, this.taskDecorator, - this.customizers); + this.awaitTermination, this.awaitTerminationPeriod, this.threadNamePrefix, + this.taskDecorator, this.customizers); } /** - * Set the prefix to use for the names of newly created threads. - * @param threadNamePrefix the thread name prefix to set + * Set whether the executor should wait for scheduled tasks to complete on shutdown, + * not interrupting running tasks and executing all tasks in the queue. + * @param awaitTermination whether the executor needs to wait for the tasks to + * complete on shutdown * @return a new builder instance + * @see #awaitTerminationPeriod(Duration) */ - public TaskExecutorBuilder threadNamePrefix(String threadNamePrefix) { + public TaskExecutorBuilder awaitTermination(boolean awaitTermination) { return new TaskExecutorBuilder(this.queueCapacity, this.corePoolSize, this.maxPoolSize, this.allowCoreThreadTimeOut, this.keepAlive, - threadNamePrefix, this.awaitTermination, - this.waitForTasksToCompleteOnShutdown, this.taskDecorator, - this.customizers); + awaitTermination, this.awaitTerminationPeriod, this.threadNamePrefix, + this.taskDecorator, this.customizers); } /** - * Set the maximum number of time that the executor is supposed to block on shutdown - * in order to wait for remaining tasks to complete their execution before the rest of - * the container continues to shut down. This is particularly useful if your remaining - * tasks are likely to need access to other resources that are also managed by the - * container. - * @param awaitTermination the await termination to set + * Set the maximum time the executor is supposed to block on shutdown. When set, the + * executor blocks on shutdown in order to wait for remaining tasks to complete their + * execution before the rest of the container continues to shut down. This is + * particularly useful if your remaining tasks are likely to need access to other + * resources that are also managed by the container. + * @param awaitTerminationPeriod the await termination period to set * @return a new builder instance */ - public TaskExecutorBuilder awaitTermination(Duration awaitTermination) { + public TaskExecutorBuilder awaitTerminationPeriod(Duration awaitTerminationPeriod) { return new TaskExecutorBuilder(this.queueCapacity, this.corePoolSize, this.maxPoolSize, this.allowCoreThreadTimeOut, this.keepAlive, - this.threadNamePrefix, awaitTermination, - this.waitForTasksToCompleteOnShutdown, this.taskDecorator, - this.customizers); + this.awaitTermination, awaitTerminationPeriod, this.threadNamePrefix, + this.taskDecorator, this.customizers); } /** - * Set whether the executor should wait for scheduled tasks to complete on shutdown, - * not interrupting running tasks and executing all tasks in the queue. - * @param waitForTasksToCompleteOnShutdown if executor needs to wait for the tasks to - * complete on shutdown + * Set the prefix to use for the names of newly created threads. + * @param threadNamePrefix the thread name prefix to set * @return a new builder instance */ - public TaskExecutorBuilder waitForTasksToCompleteOnShutdown( - boolean waitForTasksToCompleteOnShutdown) { + public TaskExecutorBuilder threadNamePrefix(String threadNamePrefix) { return new TaskExecutorBuilder(this.queueCapacity, this.corePoolSize, this.maxPoolSize, this.allowCoreThreadTimeOut, this.keepAlive, - this.threadNamePrefix, this.awaitTermination, - waitForTasksToCompleteOnShutdown, this.taskDecorator, this.customizers); + this.awaitTermination, this.awaitTerminationPeriod, threadNamePrefix, + this.taskDecorator, this.customizers); } /** @@ -220,8 +216,8 @@ public class TaskExecutorBuilder { public TaskExecutorBuilder taskDecorator(TaskDecorator taskDecorator) { return new TaskExecutorBuilder(this.queueCapacity, this.corePoolSize, this.maxPoolSize, this.allowCoreThreadTimeOut, this.keepAlive, - this.threadNamePrefix, this.awaitTermination, - this.waitForTasksToCompleteOnShutdown, taskDecorator, this.customizers); + this.awaitTermination, this.awaitTerminationPeriod, this.threadNamePrefix, + taskDecorator, this.customizers); } /** @@ -251,9 +247,8 @@ public class TaskExecutorBuilder { Assert.notNull(customizers, "Customizers must not be null"); return new TaskExecutorBuilder(this.queueCapacity, this.corePoolSize, this.maxPoolSize, this.allowCoreThreadTimeOut, this.keepAlive, - this.threadNamePrefix, this.awaitTermination, - this.waitForTasksToCompleteOnShutdown, this.taskDecorator, - append(null, customizers)); + this.awaitTermination, this.awaitTerminationPeriod, this.threadNamePrefix, + this.taskDecorator, append(null, customizers)); } /** @@ -283,9 +278,8 @@ public class TaskExecutorBuilder { Assert.notNull(customizers, "Customizers must not be null"); return new TaskExecutorBuilder(this.queueCapacity, this.corePoolSize, this.maxPoolSize, this.allowCoreThreadTimeOut, this.keepAlive, - this.threadNamePrefix, this.awaitTermination, - this.waitForTasksToCompleteOnShutdown, this.taskDecorator, - append(this.customizers, customizers)); + this.awaitTermination, this.awaitTerminationPeriod, this.threadNamePrefix, + this.taskDecorator, append(this.customizers, customizers)); } /** @@ -328,12 +322,12 @@ public class TaskExecutorBuilder { map.from(this.keepAlive).asInt(Duration::getSeconds) .to(taskExecutor::setKeepAliveSeconds); map.from(this.allowCoreThreadTimeOut).to(taskExecutor::setAllowCoreThreadTimeOut); + map.from(this.awaitTermination) + .to(taskExecutor::setWaitForTasksToCompleteOnShutdown); + map.from(this.awaitTerminationPeriod).asInt(Duration::getSeconds) + .to(taskExecutor::setAwaitTerminationSeconds); map.from(this.threadNamePrefix).whenHasText() .to(taskExecutor::setThreadNamePrefix); - map.from(this.awaitTermination).asInt(Duration::getSeconds) - .to(taskExecutor::setAwaitTerminationSeconds); - map.from(this.waitForTasksToCompleteOnShutdown) - .to(taskExecutor::setWaitForTasksToCompleteOnShutdown); map.from(this.taskDecorator).to(taskExecutor::setTaskDecorator); if (!CollectionUtils.isEmpty(this.customizers)) { this.customizers.forEach((customizer) -> customizer.customize(taskExecutor)); diff --git a/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/task/TaskExecutorBuilderTests.java b/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/task/TaskExecutorBuilderTests.java index 31581225344..e8fbe947b04 100644 --- a/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/task/TaskExecutorBuilderTests.java +++ b/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/task/TaskExecutorBuilderTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2018 the original author or authors. + * Copyright 2012-2019 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. @@ -56,24 +56,23 @@ public class TaskExecutorBuilderTests { } @Test - public void threadNamePrefixShouldApply() { - ThreadPoolTaskExecutor executor = this.builder.threadNamePrefix("test-").build(); - assertThat(executor.getThreadNamePrefix()).isEqualTo("test-"); + public void awaitTerminationShouldApply() { + ThreadPoolTaskExecutor executor = this.builder.awaitTermination(true).build(); + assertThat(executor) + .hasFieldOrPropertyWithValue("waitForTasksToCompleteOnShutdown", true); } @Test - public void awaitTerminationShouldApply() { + public void awaitTerminationPeriodShouldApply() { ThreadPoolTaskExecutor executor = this.builder - .awaitTermination(Duration.ofMinutes(1)).build(); + .awaitTerminationPeriod(Duration.ofMinutes(1)).build(); assertThat(executor).hasFieldOrPropertyWithValue("awaitTerminationSeconds", 60); } @Test - public void waitForTasksToCompleteOnShutdownShouldApply() { - ThreadPoolTaskExecutor executor = this.builder - .waitForTasksToCompleteOnShutdown(true).build(); - assertThat(executor) - .hasFieldOrPropertyWithValue("waitForTasksToCompleteOnShutdown", true); + public void threadNamePrefixShouldApply() { + ThreadPoolTaskExecutor executor = this.builder.threadNamePrefix("test-").build(); + assertThat(executor.getThreadNamePrefix()).isEqualTo("test-"); } @Test @@ -113,17 +112,17 @@ public class TaskExecutorBuilderTests { ThreadPoolTaskExecutor executor = spy(new ThreadPoolTaskExecutor()); this.builder.queueCapacity(10).corePoolSize(4).maxPoolSize(8) .allowCoreThreadTimeOut(true).keepAlive(Duration.ofMinutes(1)) - .threadNamePrefix("test-").awaitTermination(Duration.ofSeconds(30)) - .waitForTasksToCompleteOnShutdown(true).taskDecorator(taskDecorator) + .awaitTermination(true).awaitTerminationPeriod(Duration.ofSeconds(30)) + .threadNamePrefix("test-").taskDecorator(taskDecorator) .additionalCustomizers((taskExecutor) -> { verify(taskExecutor).setQueueCapacity(10); verify(taskExecutor).setCorePoolSize(4); verify(taskExecutor).setMaxPoolSize(8); verify(taskExecutor).setAllowCoreThreadTimeOut(true); verify(taskExecutor).setKeepAliveSeconds(60); - verify(taskExecutor).setThreadNamePrefix("test-"); - verify(taskExecutor).setAwaitTerminationSeconds(30); verify(taskExecutor).setWaitForTasksToCompleteOnShutdown(true); + verify(taskExecutor).setAwaitTerminationSeconds(30); + verify(taskExecutor).setThreadNamePrefix("test-"); verify(taskExecutor).setTaskDecorator(taskDecorator); }); this.builder.configure(executor); From fa49dfcaafdeb1956ebb90a55a046c0948110694 Mon Sep 17 00:00:00 2001 From: Stephane Nicoll Date: Mon, 18 Feb 2019 21:08:08 +0100 Subject: [PATCH 3/3] Add support for task scheduling shutdown related properties See gh-15951 --- .../task/TaskSchedulingAutoConfiguration.java | 6 +- .../task/TaskSchedulingProperties.java | 40 ++++++++++++- .../TaskSchedulingAutoConfigurationTests.java | 9 ++- .../boot/task/TaskSchedulerBuilder.java | 59 ++++++++++++++++--- .../boot/task/TaskSchedulerBuilderTests.java | 17 +++++- 5 files changed, 119 insertions(+), 12 deletions(-) diff --git a/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/task/TaskSchedulingAutoConfiguration.java b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/task/TaskSchedulingAutoConfiguration.java index f87ace5d99d..a006316a3f9 100644 --- a/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/task/TaskSchedulingAutoConfiguration.java +++ b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/task/TaskSchedulingAutoConfiguration.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2018 the original author or authors. + * Copyright 2012-2019 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. @@ -23,6 +23,7 @@ import org.springframework.boot.autoconfigure.EnableAutoConfiguration; import org.springframework.boot.autoconfigure.condition.ConditionalOnBean; import org.springframework.boot.autoconfigure.condition.ConditionalOnClass; import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; +import org.springframework.boot.autoconfigure.task.TaskSchedulingProperties.Shutdown; import org.springframework.boot.context.properties.EnableConfigurationProperties; import org.springframework.boot.task.TaskSchedulerBuilder; import org.springframework.boot.task.TaskSchedulerCustomizer; @@ -58,6 +59,9 @@ public class TaskSchedulingAutoConfiguration { ObjectProvider taskSchedulerCustomizers) { TaskSchedulerBuilder builder = new TaskSchedulerBuilder(); builder = builder.poolSize(properties.getPool().getSize()); + Shutdown shutdown = properties.getShutdown(); + builder = builder.awaitTermination(shutdown.isAwaitTermination()); + builder = builder.awaitTerminationPeriod(shutdown.getAwaitTerminationPeriod()); builder = builder.threadNamePrefix(properties.getThreadNamePrefix()); builder = builder.customizers(taskSchedulerCustomizers); return builder; diff --git a/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/task/TaskSchedulingProperties.java b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/task/TaskSchedulingProperties.java index 1edb46bb691..a9bd90a85ad 100644 --- a/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/task/TaskSchedulingProperties.java +++ b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/task/TaskSchedulingProperties.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2018 the original author or authors. + * Copyright 2012-2019 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. @@ -16,6 +16,8 @@ package org.springframework.boot.autoconfigure.task; +import java.time.Duration; + import org.springframework.boot.context.properties.ConfigurationProperties; /** @@ -29,6 +31,8 @@ public class TaskSchedulingProperties { private final Pool pool = new Pool(); + private final Shutdown shutdown = new Shutdown(); + /** * Prefix to use for the names of newly created threads. */ @@ -38,6 +42,10 @@ public class TaskSchedulingProperties { return this.pool; } + public Shutdown getShutdown() { + return this.shutdown; + } + public String getThreadNamePrefix() { return this.threadNamePrefix; } @@ -63,4 +71,34 @@ public class TaskSchedulingProperties { } + public static class Shutdown { + + /** + * Whether the executor should wait for scheduled tasks to complete on shutdown. + */ + private boolean awaitTermination; + + /** + * Maximum time the executor should wait for remaining tasks to complete. + */ + private Duration awaitTerminationPeriod; + + public boolean isAwaitTermination() { + return this.awaitTermination; + } + + public void setAwaitTermination(boolean awaitTermination) { + this.awaitTermination = awaitTermination; + } + + public Duration getAwaitTerminationPeriod() { + return this.awaitTerminationPeriod; + } + + public void setAwaitTerminationPeriod(Duration awaitTerminationPeriod) { + this.awaitTerminationPeriod = awaitTerminationPeriod; + } + + } + } diff --git a/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/task/TaskSchedulingAutoConfigurationTests.java b/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/task/TaskSchedulingAutoConfigurationTests.java index 456ebb7d101..04c3dc9d313 100644 --- a/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/task/TaskSchedulingAutoConfigurationTests.java +++ b/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/task/TaskSchedulingAutoConfigurationTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2018 the original author or authors. + * Copyright 2012-2019 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. @@ -59,11 +59,18 @@ public class TaskSchedulingAutoConfigurationTests { public void enableSchedulingWithNoTaskExecutorAutoConfiguresOne() { this.contextRunner .withPropertyValues( + "spring.task.scheduling.shutdown.await-termination=true", + "spring.task.scheduling.shutdown.await-termination-period=30s", "spring.task.scheduling.thread-name-prefix=scheduling-test-") .withUserConfiguration(SchedulingConfiguration.class).run((context) -> { assertThat(context).hasSingleBean(TaskExecutor.class); + TaskExecutor taskExecutor = context.getBean(TaskExecutor.class); TestBean bean = context.getBean(TestBean.class); Thread.sleep(15); + assertThat(taskExecutor).hasFieldOrPropertyWithValue( + "waitForTasksToCompleteOnShutdown", true); + assertThat(taskExecutor) + .hasFieldOrPropertyWithValue("awaitTerminationSeconds", 30); assertThat(bean.threadNames) .allMatch((name) -> name.contains("scheduling-test-")); }); diff --git a/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/task/TaskSchedulerBuilder.java b/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/task/TaskSchedulerBuilder.java index ca8b74613ce..360b7852d0e 100644 --- a/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/task/TaskSchedulerBuilder.java +++ b/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/task/TaskSchedulerBuilder.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2018 the original author or authors. + * Copyright 2012-2019 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. @@ -16,6 +16,7 @@ package org.springframework.boot.task; +import java.time.Duration; import java.util.Arrays; import java.util.Collections; import java.util.LinkedHashSet; @@ -42,19 +43,28 @@ public class TaskSchedulerBuilder { private final Integer poolSize; + private final Boolean awaitTermination; + + private final Duration awaitTerminationPeriod; + private final String threadNamePrefix; private final Set customizers; public TaskSchedulerBuilder() { this.poolSize = null; + this.awaitTermination = null; + this.awaitTerminationPeriod = null; this.threadNamePrefix = null; this.customizers = null; } - public TaskSchedulerBuilder(Integer poolSize, String threadNamePrefix, + public TaskSchedulerBuilder(Integer poolSize, Boolean awaitTermination, + Duration awaitTerminationPeriod, String threadNamePrefix, Set taskSchedulerCustomizers) { this.poolSize = poolSize; + this.awaitTermination = awaitTermination; + this.awaitTerminationPeriod = awaitTerminationPeriod; this.threadNamePrefix = threadNamePrefix; this.customizers = taskSchedulerCustomizers; } @@ -65,8 +75,35 @@ public class TaskSchedulerBuilder { * @return a new builder instance */ public TaskSchedulerBuilder poolSize(int poolSize) { - return new TaskSchedulerBuilder(poolSize, this.threadNamePrefix, - this.customizers); + return new TaskSchedulerBuilder(poolSize, this.awaitTermination, + this.awaitTerminationPeriod, this.threadNamePrefix, this.customizers); + } + + /** + * Set whether the executor should wait for scheduled tasks to complete on shutdown, + * not interrupting running tasks and executing all tasks in the queue. + * @param awaitTermination whether the executor needs to wait for the tasks to + * complete on shutdown + * @return a new builder instance + * @see #awaitTerminationPeriod(Duration) + */ + public TaskSchedulerBuilder awaitTermination(boolean awaitTermination) { + return new TaskSchedulerBuilder(this.poolSize, awaitTermination, + this.awaitTerminationPeriod, this.threadNamePrefix, this.customizers); + } + + /** + * Set the maximum time the executor is supposed to block on shutdown. When set, the + * executor blocks on shutdown in order to wait for remaining tasks to complete their + * execution before the rest of the container continues to shut down. This is + * particularly useful if your remaining tasks are likely to need access to other + * resources that are also managed by the container. + * @param awaitTerminationPeriod the await termination period to set + * @return a new builder instance + */ + public TaskSchedulerBuilder awaitTerminationPeriod(Duration awaitTerminationPeriod) { + return new TaskSchedulerBuilder(this.poolSize, this.awaitTermination, + awaitTerminationPeriod, this.threadNamePrefix, this.customizers); } /** @@ -75,8 +112,8 @@ public class TaskSchedulerBuilder { * @return a new builder instance */ public TaskSchedulerBuilder threadNamePrefix(String threadNamePrefix) { - return new TaskSchedulerBuilder(this.poolSize, threadNamePrefix, - this.customizers); + return new TaskSchedulerBuilder(this.poolSize, this.awaitTermination, + this.awaitTerminationPeriod, threadNamePrefix, this.customizers); } /** @@ -105,7 +142,8 @@ public class TaskSchedulerBuilder { public TaskSchedulerBuilder customizers( Iterable customizers) { Assert.notNull(customizers, "Customizers must not be null"); - return new TaskSchedulerBuilder(this.poolSize, this.threadNamePrefix, + return new TaskSchedulerBuilder(this.poolSize, this.awaitTermination, + this.awaitTerminationPeriod, this.threadNamePrefix, append(null, customizers)); } @@ -134,7 +172,8 @@ public class TaskSchedulerBuilder { public TaskSchedulerBuilder additionalCustomizers( Iterable customizers) { Assert.notNull(customizers, "Customizers must not be null"); - return new TaskSchedulerBuilder(this.poolSize, this.threadNamePrefix, + return new TaskSchedulerBuilder(this.poolSize, this.awaitTermination, + this.awaitTerminationPeriod, this.threadNamePrefix, append(this.customizers, customizers)); } @@ -158,6 +197,10 @@ public class TaskSchedulerBuilder { public T configure(T taskScheduler) { PropertyMapper map = PropertyMapper.get().alwaysApplyingWhenNonNull(); map.from(this.poolSize).to(taskScheduler::setPoolSize); + map.from(this.awaitTermination) + .to(taskScheduler::setWaitForTasksToCompleteOnShutdown); + map.from(this.awaitTerminationPeriod).asInt(Duration::getSeconds) + .to(taskScheduler::setAwaitTerminationSeconds); map.from(this.threadNamePrefix).to(taskScheduler::setThreadNamePrefix); if (!CollectionUtils.isEmpty(this.customizers)) { this.customizers.forEach((customizer) -> customizer.customize(taskScheduler)); diff --git a/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/task/TaskSchedulerBuilderTests.java b/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/task/TaskSchedulerBuilderTests.java index 68db1188e03..f2e8091f3d5 100644 --- a/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/task/TaskSchedulerBuilderTests.java +++ b/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/task/TaskSchedulerBuilderTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2018 the original author or authors. + * Copyright 2012-2019 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. @@ -16,6 +16,7 @@ package org.springframework.boot.task; +import java.time.Duration; import java.util.Collections; import java.util.Set; @@ -45,6 +46,20 @@ public class TaskSchedulerBuilderTests { assertThat(scheduler.getPoolSize()).isEqualTo(4); } + @Test + public void awaitTerminationShouldApply() { + ThreadPoolTaskScheduler executor = this.builder.awaitTermination(true).build(); + assertThat(executor) + .hasFieldOrPropertyWithValue("waitForTasksToCompleteOnShutdown", true); + } + + @Test + public void awaitTerminationPeriodShouldApply() { + ThreadPoolTaskScheduler executor = this.builder + .awaitTerminationPeriod(Duration.ofMinutes(1)).build(); + assertThat(executor).hasFieldOrPropertyWithValue("awaitTerminationSeconds", 60); + } + @Test public void threadNamePrefixShouldApply() { ThreadPoolTaskScheduler scheduler = this.builder.threadNamePrefix("test-")