From d01e490b5243902cd650f61ad716ffb304b8153a Mon Sep 17 00:00:00 2001 From: junhyeong9812 Date: Thu, 25 Jun 2026 07:54:03 +0900 Subject: [PATCH] Make immediate-cancel task termination test deterministic The taskTerminationTimeoutWithImmediateCancel test submitted a task and immediately closed the executor, then asserted that the future was cancelled. The cancellation flag is set by close() on the calling thread, while it is checked at the start of the task on a separate worker thread. With no ordering guarantee between the two, a quickly scheduled worker could pass the cancellation check before close() set the flag, complete the trivial task normally, and leave the future uncancelled, making the test fail intermittently under load. Override doExecute to capture the task-tracking wrapper instead of running it on a background thread, then run it on the test thread after close() has set the cancellation flag. This exercises the same cancellation path deterministically, with no reliance on thread scheduling. Signed-off-by: junhyeong9812 --- .../task/SimpleAsyncTaskExecutorTests.java | 18 ++++++++++-------- 1 file changed, 10 insertions(+), 8 deletions(-) diff --git a/spring-core/src/test/java/org/springframework/core/task/SimpleAsyncTaskExecutorTests.java b/spring-core/src/test/java/org/springframework/core/task/SimpleAsyncTaskExecutorTests.java index a1f4e91f8e2b..fbf1b377fddc 100644 --- a/spring-core/src/test/java/org/springframework/core/task/SimpleAsyncTaskExecutorTests.java +++ b/spring-core/src/test/java/org/springframework/core/task/SimpleAsyncTaskExecutorTests.java @@ -21,6 +21,7 @@ import java.util.concurrent.Future; import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicBoolean; +import java.util.concurrent.atomic.AtomicReference; import org.junit.jupiter.api.Test; @@ -153,17 +154,18 @@ void taskTerminationTimeout() throws InterruptedException{ @Test void taskTerminationTimeoutWithImmediateCancel() { - AtomicBoolean finished = new AtomicBoolean(); + AtomicReference captured = new AtomicReference<>(); Future future; - try (SimpleAsyncTaskExecutor executor = new SimpleAsyncTaskExecutor()) { + try (SimpleAsyncTaskExecutor executor = new SimpleAsyncTaskExecutor() { + @Override + protected void doExecute(Runnable task) { + captured.set(task); + } + }) { executor.setTaskTerminationTimeout(100); - future = executor.submit(() -> { - if (finished.get()) { - throw new IllegalStateException(); - } - }); + future = executor.submit(() -> {}); } - finished.set(true); + assertThatExceptionOfType(CancellationException.class).isThrownBy(captured.get()::run); assertThatExceptionOfType(CancellationException.class).isThrownBy(future::get); }