Vejto
Vejto

Reputation: 1314

Exception handling with multiple futures from ScheduledExecutorService

I am using a ScheduledExecutorService to which I add both submit Runnables as well as scheduled Runnables (using scheduleWithFixedDelay). The intention is to have very long running processes and thus non of my runnables have a defined lifetime. I basically want the main thread to only react to exceptions and interruptions. The scheduled tasks are critical, e.g. generating heatbeats, and thus if any thread throw a runtimeexception I want to log the exception, abort all other threads and close the program.

How should I handle the exceptions? The ScheduledExecutorService swallows all the exceptions unless I run Future.get().

Looping through the futures, like below, does not work. If the first item in the futures list doesn't return any errors that will block the thread from listening on the other threads that might return error.

for (Future<?> future : futures) {
    future.get();
}

One option would be looping through the futures asking if they are done, like below, but I don't really like this solution. I need to add a thread sleep and thus the response to an exception is delayed.

boolean allActive = true;
while (allActive) {
    for (Future<?> future : futures) {
        if (!future.isDone()) {
             allActive = false;
             break;
        }
    }
    Thread.sleep(50);
}

What other options do I have? Or am I approaching the problem wrong? Shouldn't I use a ScheduledExecutorService at all and implement the schedule myself in my own thread?

Example code, try change the order to the future list! I want the behavior you get if you add handle before handle2 but the order of the list shouldn't matter:

import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;

public class Test {

    private static int i = 0;

    public static void main(String[] args) throws Exception {

        ScheduledExecutorService scheduler = Executors.newSingleThreadScheduledExecutor();

        Future<?> handle = scheduler.scheduleWithFixedDelay(new Runnable() {
            public void run() {
                System.out.println("No exception!");
                if (i > 2) {
                    System.out.println("Exception!");
                    throw new RuntimeException("foo");
                }
                i++;
            }
        }, 0, 500, TimeUnit.MILLISECONDS);

        Future<?> handle2 = scheduler.scheduleWithFixedDelay(new Runnable() {
            public void run() {
                System.out.println("Running!");
            }
        }, 0, 500, TimeUnit.MILLISECONDS);


        List<Future<?>> futures = new ArrayList<>();
        futures.add(handle2);
        futures.add(handle);

        try {
            for (Future<?> future : futures) {
                future.get();
            }
        } catch (Exception e) {
            scheduler.shutdownNow();
            System.out.println(scheduler.awaitTermination(1, TimeUnit.SECONDS));
            System.out.println("Shuwdown complete");
            e.printStackTrace();
        }
    }
}

Upvotes: 1

Views: 1388

Answers (1)

Fildor
Fildor

Reputation: 16104

You can do it with a Listener or Observer-Like pattern:

interface IFutureListener{
    void onException( Throwable t );
}


final IFutureListener errHandler = new IFutureListener(){
     @override public void onException( Throwable t ){
          // shutdown Service here
     }
};
// ...
Future<?> handle = scheduler.scheduleWithFixedDelay(new Runnable() {
        final IFutureListener callback = errHandler;
        public void run() {
            try{
            System.out.println("No exception!");
            if (i > 2) {
                System.out.println("Exception!");
                throw new RuntimeException("foo");
            }
            i++;
            }
            catch( Exception ex ){
                callback.onException(ex);
            }
        }
    }, 0, 500, TimeUnit.MILLISECONDS);

You still might need some tweaks to this, but this is the gist of it.

Guava's ListenableFuture as @dimo414 writes in comment will give you something similar. But if you do not want / are not allowed to use 3rd parties, this is a way you can implement it yourself.

Thanks to @efekctive: I also suggest logging exceptions. They should almost never just be swallowed silently except you know exactly what you are doing.

Upvotes: 2

Related Questions