Cancellation does NOT work well with CompletableFutures
2 min read
CompletableFuture.cancel
CompletableFuture.cancel(boolean mayInterruptIfRunning). mayInterruptIfRunning: does Nothing.
[Cancellation does NOT work well with CompletableFutures](thorg://notes/u7ox6ra9bc6qdn3zoh7plfm)
```java
/**
* If not already completed, completes this CompletableFuture with
* a {@link CancellationException}. Dependent CompletableFutures
* that have not already completed will also complete
* exceptionally, with a {@link CompletionException} caused by
* this {@code CancellationException}.
*
* @param mayInterruptIfRunning this value has no effect in this
* implementation because interrupts are not used to control
* processing.
*
* @return {@code true} if this task is now cancelled
*/
public boolean cancel(boolean mayInterruptIfRunning) {
```
After 'cancel' is called. Thread.sleep() in cancelled thread does NOT throw Interrupted exception.
Code
package com.glassthought.sandboximport gt.sandbox.util.output.Outimport gt.sandbox.util.output.impl.OutSettingsimport java.util.concurrent.CompletableFutureval out = Out.standard(outSettings = OutSettings(printColorPerThread = true))fun main() { // Create a CompletableFuture that simulates a long-running task val future = CompletableFuture.supplyAsync { out.println("supplyAsync: Task started") try { out.println("Starting 5 second sleep") Thread.sleep(5000) // Simulate a long-running task out.println("Slept for 5 seconds, will sleep for just 100ms more") Thread.sleep(100) out.println("supplyAsync: Task completed") "Task completed" } catch (e: InterruptedException) { out.println("Task was interrupted") throw RuntimeException("Task interrupted", e) } } Thread.sleep(100) // Simulate cancellation after 2 seconds Thread { out.println("Starting a new thread to cancel the future...") Thread.sleep(2000) // Wait 2 seconds before canceling out.println("Cancelling the future...") val cancelled = future.cancel(true) out.println("Future cancelled: $cancelled") }.start() // Attempt to retrieve the result (blocks until completed or cancelled) try { val result = future.get() out.println("Future result: $result") } catch (e: Exception) { out.println("Exception occurred: ${e.message}") } Thread.sleep(6000) out.println("Main thread ends")}
Command to reproduce:
gt.sandbox.checkout.commit e04a4d99c46dad07bed4 \&& cd "${GT_SANDBOX_REPO}" \&& cmd.run.announce "./gradlew run --quiet"
Recorded output of command:
[2024-11-22T16:45:52.824796Z][elapsed-since-start: 15ms][tname:ForkJoinPool.commonPool-worker-1/tid:20] supplyAsync: Task started[2024-11-22T16:45:52.852971Z][elapsed-since-start: 33ms][tname:ForkJoinPool.commonPool-worker-1/tid:20] Starting 5 second sleep[2024-11-22T16:45:52.928483Z][elapsed-since-start: 108ms][tname:Thread-0/tid:21] Starting a new thread to cancel the future...[2024-11-22T16:45:54.931070Z][elapsed-since-start: 2111ms][tname:Thread-0/tid:21] Cancelling the future...[2024-11-22T16:45:54.935170Z][elapsed-since-start: 2115ms][tname:main/tid:1] Exception occurred: null[2024-11-22T16:45:54.938928Z][elapsed-since-start: 2118ms][tname:Thread-0/tid:21] Future cancelled: true[2024-11-22T16:45:57.857226Z][elapsed-since-start: 5037ms][tname:ForkJoinPool.commonPool-worker-1/tid:20] Slept for 5 seconds, will sleep for just 100ms more[2024-11-22T16:45:57.958440Z][elapsed-since-start: 5138ms][tname:ForkJoinPool.commonPool-worker-1/tid:20] supplyAsync: Task completed[2024-11-22T16:46:00.940212Z][elapsed-since-start: 8120ms][tname:main/tid:1] Main thread ends