diff --git a/spring-context/src/main/java/org/springframework/scheduling/concurrent/ExecutorConfigurationSupport.java b/spring-context/src/main/java/org/springframework/scheduling/concurrent/ExecutorConfigurationSupport.java index 5d69b55d653..d6838f5beb2 100644 --- a/spring-context/src/main/java/org/springframework/scheduling/concurrent/ExecutorConfigurationSupport.java +++ b/spring-context/src/main/java/org/springframework/scheduling/concurrent/ExecutorConfigurationSupport.java @@ -86,6 +86,8 @@ public abstract class ExecutorConfigurationSupport extends CustomizableThreadFac @Nullable private ExecutorLifecycleDelegate lifecycleDelegate; + private volatile boolean lateShutdown; + /** * Set the ThreadFactory to use for the ExecutorService's thread pool. @@ -127,8 +129,9 @@ public abstract class ExecutorConfigurationSupport extends CustomizableThreadFac *

Default is {@code false} as of 6.1, triggering an early soft shutdown of * the executor and therefore rejecting any further task submissions. Switch this * to {@code true} in order to let other components submit tasks even during their - * own destruction callbacks, at the expense of a longer shutdown phase. - * This will usually go along with + * own stop and destruction callbacks, at the expense of a longer shutdown phase. + * The executor will not go through a coordinated lifecycle stop phase then + * but rather only stop tasks on its own shutdown. This usually goes along with * {@link #setWaitForTasksToCompleteOnShutdown "waitForTasksToCompleteOnShutdown"}. *

This flag will only have effect when the executor is running in a Spring * application context and able to receive the {@link ContextClosedEvent}. @@ -144,9 +147,13 @@ public abstract class ExecutorConfigurationSupport extends CustomizableThreadFac /** * Set whether to wait for scheduled tasks to complete on shutdown, * not interrupting running tasks and executing all tasks in the queue. - *

Default is {@code false}, shutting down immediately through interrupting - * ongoing tasks and clearing the queue. Switch this flag to {@code true} if - * you prefer fully completed tasks at the expense of a longer shutdown phase. + *

Default is {@code false}, with a coordinated lifecycle stop first (unless + * {@link #setAcceptTasksAfterContextClose "acceptTasksAfterContextClose"} + * has been set) and then an immediate shutdown through interrupting ongoing + * tasks and clearing the queue. Switch this flag to {@code true} if you + * prefer fully completed tasks at the expense of a longer shutdown phase. + * The executor will not go through a coordinated lifecycle stop phase then + * but rather only stop and wait for task completion on its own shutdown. *

Note that Spring's container shutdown continues while ongoing tasks * are being completed. If you want this executor to block and wait for the * termination of tasks before the rest of the container continues to shut @@ -374,7 +381,7 @@ public abstract class ExecutorConfigurationSupport extends CustomizableThreadFac */ @Override public void stop() { - if (this.lifecycleDelegate != null) { + if (this.lifecycleDelegate != null && !this.lateShutdown) { this.lifecycleDelegate.stop(); } } @@ -386,9 +393,12 @@ public abstract class ExecutorConfigurationSupport extends CustomizableThreadFac */ @Override public void stop(Runnable callback) { - if (this.lifecycleDelegate != null) { + if (this.lifecycleDelegate != null && !this.lateShutdown) { this.lifecycleDelegate.stop(callback); } + else { + callback.run(); + } } /** @@ -439,10 +449,16 @@ public abstract class ExecutorConfigurationSupport extends CustomizableThreadFac */ @Override public void onApplicationEvent(ContextClosedEvent event) { - if (event.getApplicationContext() == this.applicationContext && !this.acceptTasksAfterContextClose) { - // Early shutdown signal: accept no further tasks, let existing tasks complete - // before hitting the actual destruction step in the shutdown() method above. - initiateShutdown(); + if (event.getApplicationContext() == this.applicationContext) { + if (this.acceptTasksAfterContextClose || this.waitForTasksToCompleteOnShutdown) { + // Late shutdown without early stop lifecycle. + this.lateShutdown = true; + } + else { + // Early shutdown signal: accept no further tasks, let existing tasks complete + // before hitting the actual destruction step in the shutdown() method above. + initiateShutdown(); + } } } diff --git a/spring-context/src/main/java/org/springframework/scheduling/concurrent/ExecutorLifecycleDelegate.java b/spring-context/src/main/java/org/springframework/scheduling/concurrent/ExecutorLifecycleDelegate.java index 3f449f12bdc..5405a71ba4e 100644 --- a/spring-context/src/main/java/org/springframework/scheduling/concurrent/ExecutorLifecycleDelegate.java +++ b/spring-context/src/main/java/org/springframework/scheduling/concurrent/ExecutorLifecycleDelegate.java @@ -97,7 +97,7 @@ final class ExecutorLifecycleDelegate implements SmartLifecycle { @Override public boolean isRunning() { - return (!this.executor.isShutdown() & !this.paused); + return (!this.paused && !this.executor.isTerminated()); } void beforeExecute(Thread thread) { diff --git a/spring-context/src/test/java/org/springframework/scheduling/annotation/EnableSchedulingTests.java b/spring-context/src/test/java/org/springframework/scheduling/annotation/EnableSchedulingTests.java index d459204aa7a..fc7f20de8b6 100644 --- a/spring-context/src/test/java/org/springframework/scheduling/annotation/EnableSchedulingTests.java +++ b/spring-context/src/test/java/org/springframework/scheduling/annotation/EnableSchedulingTests.java @@ -344,9 +344,10 @@ public class EnableSchedulingTests { @Override public TaskScheduler myTaskScheduler() { ThreadPoolTaskScheduler scheduler = new ThreadPoolTaskScheduler(); - scheduler.setThreadNamePrefix("explicitScheduler-"); - scheduler.setAwaitTerminationMillis(1000); scheduler.setPoolSize(2); + scheduler.setThreadNamePrefix("explicitScheduler-"); + scheduler.setAcceptTasksAfterContextClose(true); + scheduler.setAwaitTerminationMillis(1000); return scheduler; } }