javaconcurrencycompletable-future

How to interrupt underlying execution of CompletableFuture


I know that CompletableFuture design does not control its execution with interruptions, but I suppose some of you might have this problem. CompletableFutures are very good way to compose async execution, but given the case when you want the underlying execution to be interrupted or stopped when future is canceled, how do we do that? Or we must just accept that any canceled or manually completed CompletableFuture will not impact the thread working out there to complete it?

That is, in my opinion, obviously a useless work that takes time of executor worker. I wonder what approach or design might help in this case?

UPDATE

Here is a simple test for this

public class SimpleTest {

  @Test
  public void testCompletableFuture() throws Exception {
    CompletableFuture<Void> cf = CompletableFuture.runAsync(()->longOperation());

    bearSleep(1);

    //cf.cancel(true);
    cf.complete(null);

    System.out.println("it should die now already");
    bearSleep(7);
  }

  public static void longOperation(){
    System.out.println("started");
    bearSleep(5);
    System.out.println("completed");
  }

  private static void bearSleep(long seconds){
    try {
      TimeUnit.SECONDS.sleep(seconds);
    } catch (InterruptedException e) {
      System.out.println("OMG!!! Interrupt!!!");
    }
  }
}

Solution

  • A CompletableFuture is not related to the asynchronous action that may eventually complete it.

    Since (unlike FutureTask) this class has no direct control over the computation that causes it to be completed, cancellation is treated as just another form of exceptional completion. Method cancel has the same effect as completeExceptionally(new CancellationException()).

    There may not even be a separate thread working on completing it (there may even be many threads working on it). Even if there is, there's no link from a CompletableFuture to any thread that has a reference to it.

    As such, there's nothing you can do through CompletableFuture to interrupt any thread that may be running some task that will complete it. You'll have to write your own logic which tracks any Thread instances which acquire a reference to the CompletableFuture with the intention to complete it.


    Here's an example of the type of execution I think you could get away with.

    public static void main(String[] args) throws Exception {
        ExecutorService service = Executors.newFixedThreadPool(1);
        CompletableFuture<String> completable = new CompletableFuture<>();
        Future<?> future = service.submit(new Runnable() {
            @Override
            public void run() {
                for (int i = 0; i < 10; i++) {
                    if (Thread.interrupted()) {
                        return; // remains uncompleted
                    }
                    try {
                        Thread.sleep(1000);
                    } catch (InterruptedException e) {
                        return; // remains uncompleted
                    }
                }
                completable.complete("done");
            }
        });
    
        Thread.sleep(2000);
    
        // not atomic across the two
        boolean cancelled = future.cancel(true);
        if (cancelled)
            completable.cancel(true); // may not have been cancelled if execution has already completed
        if (completable.isCancelled()) {
            System.out.println("cancelled");
        } else if (completable.isCompletedExceptionally()) {
            System.out.println("exception");
        } else {
            System.out.println("success");
        }
        service.shutdown();
    }
    

    This assumes that the task being executed is setup to handle interruptions correctly.