Cancellation does NOT work well with CompletableFutures

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.sandbox

import gt.sandbox.util.output.Out
import gt.sandbox.util.output.impl.OutSettings
import java.util.concurrent.CompletableFuture

val 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

Backlinks