Coordinated stop before destroy when ExecutorService not terminated yet

Closes gh-31549
This commit is contained in:
Juergen Hoeller 2023-11-09 10:22:27 +01:00
parent 38724a1205
commit 9414c2ddba
3 changed files with 31 additions and 14 deletions

View File

@ -86,6 +86,8 @@ public abstract class ExecutorConfigurationSupport extends CustomizableThreadFac
@Nullable @Nullable
private ExecutorLifecycleDelegate lifecycleDelegate; private ExecutorLifecycleDelegate lifecycleDelegate;
private volatile boolean lateShutdown;
/** /**
* Set the ThreadFactory to use for the ExecutorService's thread pool. * Set the ThreadFactory to use for the ExecutorService's thread pool.
@ -127,8 +129,9 @@ public abstract class ExecutorConfigurationSupport extends CustomizableThreadFac
* <p>Default is {@code false} as of 6.1, triggering an early soft shutdown of * <p>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 * 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 * 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. * own stop and destruction callbacks, at the expense of a longer shutdown phase.
* This will usually go along with * 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"}. * {@link #setWaitForTasksToCompleteOnShutdown "waitForTasksToCompleteOnShutdown"}.
* <p>This flag will only have effect when the executor is running in a Spring * <p>This flag will only have effect when the executor is running in a Spring
* application context and able to receive the {@link ContextClosedEvent}. * 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, * Set whether to wait for scheduled tasks to complete on shutdown,
* not interrupting running tasks and executing all tasks in the queue. * not interrupting running tasks and executing all tasks in the queue.
* <p>Default is {@code false}, shutting down immediately through interrupting * <p>Default is {@code false}, with a coordinated lifecycle stop first (unless
* ongoing tasks and clearing the queue. Switch this flag to {@code true} if * {@link #setAcceptTasksAfterContextClose "acceptTasksAfterContextClose"}
* you prefer fully completed tasks at the expense of a longer shutdown phase. * 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.
* <p>Note that Spring's container shutdown continues while ongoing tasks * <p>Note that Spring's container shutdown continues while ongoing tasks
* are being completed. If you want this executor to block and wait for the * 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 * termination of tasks before the rest of the container continues to shut
@ -374,7 +381,7 @@ public abstract class ExecutorConfigurationSupport extends CustomizableThreadFac
*/ */
@Override @Override
public void stop() { public void stop() {
if (this.lifecycleDelegate != null) { if (this.lifecycleDelegate != null && !this.lateShutdown) {
this.lifecycleDelegate.stop(); this.lifecycleDelegate.stop();
} }
} }
@ -386,9 +393,12 @@ public abstract class ExecutorConfigurationSupport extends CustomizableThreadFac
*/ */
@Override @Override
public void stop(Runnable callback) { public void stop(Runnable callback) {
if (this.lifecycleDelegate != null) { if (this.lifecycleDelegate != null && !this.lateShutdown) {
this.lifecycleDelegate.stop(callback); this.lifecycleDelegate.stop(callback);
} }
else {
callback.run();
}
} }
/** /**
@ -439,10 +449,16 @@ public abstract class ExecutorConfigurationSupport extends CustomizableThreadFac
*/ */
@Override @Override
public void onApplicationEvent(ContextClosedEvent event) { public void onApplicationEvent(ContextClosedEvent event) {
if (event.getApplicationContext() == this.applicationContext && !this.acceptTasksAfterContextClose) { if (event.getApplicationContext() == this.applicationContext) {
// Early shutdown signal: accept no further tasks, let existing tasks complete if (this.acceptTasksAfterContextClose || this.waitForTasksToCompleteOnShutdown) {
// before hitting the actual destruction step in the shutdown() method above. // Late shutdown without early stop lifecycle.
initiateShutdown(); 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();
}
} }
} }

View File

@ -97,7 +97,7 @@ final class ExecutorLifecycleDelegate implements SmartLifecycle {
@Override @Override
public boolean isRunning() { public boolean isRunning() {
return (!this.executor.isShutdown() & !this.paused); return (!this.paused && !this.executor.isTerminated());
} }
void beforeExecute(Thread thread) { void beforeExecute(Thread thread) {

View File

@ -344,9 +344,10 @@ public class EnableSchedulingTests {
@Override @Override
public TaskScheduler myTaskScheduler() { public TaskScheduler myTaskScheduler() {
ThreadPoolTaskScheduler scheduler = new ThreadPoolTaskScheduler(); ThreadPoolTaskScheduler scheduler = new ThreadPoolTaskScheduler();
scheduler.setThreadNamePrefix("explicitScheduler-");
scheduler.setAwaitTerminationMillis(1000);
scheduler.setPoolSize(2); scheduler.setPoolSize(2);
scheduler.setThreadNamePrefix("explicitScheduler-");
scheduler.setAcceptTasksAfterContextClose(true);
scheduler.setAwaitTerminationMillis(1000);
return scheduler; return scheduler;
} }
} }