Skip to content

Commit f62519b

Browse files
committed
Add cancelRemainingTasksOnClose flag for enforcing early interruption
Closes gh-35372
1 parent 0e2af5d commit f62519b

File tree

1 file changed

+50
-9
lines changed

1 file changed

+50
-9
lines changed

spring-core/src/main/java/org/springframework/core/task/SimpleAsyncTaskExecutor.java

Lines changed: 50 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -92,6 +92,8 @@ public class SimpleAsyncTaskExecutor extends CustomizableThreadCreator
9292
@Nullable
9393
private Set<Thread> activeThreads;
9494

95+
private boolean cancelRemainingTasksOnClose = false;
96+
9597
private boolean rejectTasksWhenLimitReached = false;
9698

9799
private volatile boolean active = true;
@@ -184,12 +186,33 @@ public void setTaskDecorator(TaskDecorator taskDecorator) {
184186
* @param timeout the timeout in milliseconds
185187
* @since 6.1
186188
* @see #close()
189+
* @see #setCancelRemainingTasksOnClose
187190
* @see org.springframework.scheduling.concurrent.ExecutorConfigurationSupport#setAwaitTerminationMillis
188191
*/
189192
public void setTaskTerminationTimeout(long timeout) {
190193
Assert.isTrue(timeout >= 0, "Timeout value must be >=0");
191194
this.taskTerminationTimeout = timeout;
192-
this.activeThreads = (timeout > 0 ? ConcurrentHashMap.newKeySet() : null);
195+
trackActiveThreadsIfNecessary();
196+
}
197+
198+
/**
199+
* Specify whether to cancel remaining tasks on close: that is, whether to
200+
* interrupt any active threads at the time of the {@link #close()} call.
201+
* <p>The default is {@code false}, not tracking active threads at all or
202+
* just interrupting any remaining threads that still have not finished after
203+
* the specified {@link #setTaskTerminationTimeout taskTerminationTimeout}.
204+
* Switch this to {@code true} for immediate interruption on close, either in
205+
* combination with a subsequent termination timeout or without any waiting
206+
* at all, depending on whether a {@code taskTerminationTimeout} has been
207+
* specified as well.
208+
* @since 6.2.11
209+
* @see #close()
210+
* @see #setTaskTerminationTimeout
211+
* @see org.springframework.scheduling.concurrent.ExecutorConfigurationSupport#setWaitForTasksToCompleteOnShutdown
212+
*/
213+
public void setCancelRemainingTasksOnClose(boolean cancelRemainingTasksOnClose) {
214+
this.cancelRemainingTasksOnClose = cancelRemainingTasksOnClose;
215+
trackActiveThreadsIfNecessary();
193216
}
194217

195218
/**
@@ -249,6 +272,15 @@ public boolean isActive() {
249272
return this.active;
250273
}
251274

275+
/**
276+
* Track active threads only when a task termination timeout has been
277+
* specified or interruption of remaining threads has been requested.
278+
*/
279+
private void trackActiveThreadsIfNecessary() {
280+
this.activeThreads = (this.taskTerminationTimeout > 0 || this.cancelRemainingTasksOnClose ?
281+
ConcurrentHashMap.newKeySet() : null);
282+
}
283+
252284

253285
/**
254286
* Executes the given task, within a concurrency throttle
@@ -353,7 +385,7 @@ protected Thread newThread(Runnable task) {
353385
}
354386

355387
/**
356-
* This close methods tracks the termination of active threads if a concrete
388+
* This close method tracks the termination of active threads if a concrete
357389
* {@link #setTaskTerminationTimeout task termination timeout} has been set.
358390
* Otherwise, it is not necessary to close this executor.
359391
* @since 6.1
@@ -364,17 +396,26 @@ public void close() {
364396
this.active = false;
365397
Set<Thread> threads = this.activeThreads;
366398
if (threads != null) {
367-
synchronized (threads) {
368-
try {
369-
if (!threads.isEmpty()) {
370-
threads.wait(this.taskTerminationTimeout);
399+
if (this.cancelRemainingTasksOnClose) {
400+
// Early interrupt for remaining tasks on close
401+
threads.forEach(Thread::interrupt);
402+
}
403+
if (this.taskTerminationTimeout > 0) {
404+
synchronized (threads) {
405+
try {
406+
if (!threads.isEmpty()) {
407+
threads.wait(this.taskTerminationTimeout);
408+
}
409+
}
410+
catch (InterruptedException ex) {
411+
Thread.currentThread().interrupt();
371412
}
372413
}
373-
catch (InterruptedException ex) {
374-
Thread.currentThread().interrupt();
414+
if (!this.cancelRemainingTasksOnClose) {
415+
// Late interrupt for remaining tasks after timeout
416+
threads.forEach(Thread::interrupt);
375417
}
376418
}
377-
threads.forEach(Thread::interrupt);
378419
}
379420
}
380421
}

0 commit comments

Comments
 (0)