Interrupt all threads if an exception occurs in any

Viewed 499

I have a method name someTask that I have to invoke 100 times and I am using asynchronous coding as below.

for (int i = 0; i < 100; i++) { 
    futures.add(CompletableFuture.supplyAsync(  
       () -> { someTask(); }, 
       myexecutor
    ));
}

If an exception occurs on any thread while executing someTask() I want to interrupt all the current threads and also stop future threads from executing. What is the best way to handle this?

Update: I am using ThreadPoolTaskExecutor.

2 Answers

Since you don't seem to be using the CompletableFuture specific methods, you could use an ExecutorService directly:

for (int i = 0; i < 100; i++) {
  executor.submit(() -> {
      try {
        someTask();
      } catch (InterruptedException e) {
        //interrupted, just exit
        Thread.currentThread().interrupt();
      } catch (Exception e) {
        //some other exception: cancel everything
        executorService.shutdownNow();
      }
    });
}

shutdownNow will interrupt all the already submitted tasks and the executor will refuse new task submission with a RejectedExecutionException.

The main drawback with this approach is that the ExecutorService cannot be reused, although you can of course create a new one. If that's a problem you will need another way.

Note that for this to work efficiently someTask() needs to regularly check the interrupted status of the thread and exit on interruption, or use interruptible methods. For example if someTask is a loop running some calculations and you never check the Thread interrupted status, the loop will run entirely and will not be stopped.

There are many ways to do that and all depend on the exact use case. But all ways (I'd say) have one thing in common: you'll have to end the tasks in a controlled manner. You can't just "instant-terminate" the execution of a task because this could lead to inconsitent data and other problems. This is usually done by checking some flag, e.g. Thread.isInterrupted(), and ending the execution manually if it's signaling that the execution shall cancel. You can use a custom flag as well, which I'll show:

import java.util.HashSet;
import java.util.Set;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.atomic.AtomicBoolean;

class Main  {
    
    static final ExecutorService executor = Executors.newCachedThreadPool();
    
    
    // just dummy stuff to simulate calculation
    static void someStuff(AtomicBoolean cancel) {
        if (cancel.get()) {
            System.out.println("interrupted");
            return;
        }
        System.out.println("doing stuff");
        for (int i = 0; i < 100_000_000; i++) {
            Math.sqrt(Math.log(i));
        }
    }
    
    
    static void someTask(AtomicBoolean cancel) {
        someStuff(cancel);
        someStuff(cancel);
        someStuff(cancel);
    }
    
    
    public static void main(String[] args) {
        final Set<CompletableFuture<?>> futures = new HashSet<>();
        final AtomicBoolean cancel = new AtomicBoolean();
        
        for (int i = 0; i < 10; i++) { 
            futures.add(CompletableFuture.supplyAsync(  
               () -> {someTask(cancel); return null;}, executor
            ));
        }
        futures.add(CompletableFuture.supplyAsync(() -> {
            try {
                throw new RuntimeException("dummy exception");
            }
            catch (RuntimeException re) {
                cancel.set(true);
            }
            return null;
        }));
        
        futures.forEach(cf -> cf.join());
        executor.shutdownNow();
    }
}

Notice that the final task is throwing an exception and handles it by setting the flag cancel to true. All the other tasks will see that by checking it periodically. They will cancel their execution if the flag signals it. If you comment out the exception-throwing task, all tasks will just finish normally.

Note that this approach is independent of the executor used.

Related