Question

I have a ScheduledExecutorService, which invokes a Runnable periodically via scheduleWithFixedDelay() (could have used scheduleAtFixedRate() instead).

Am now considering what to do if an error occurs. If it's something that can't easily be recovered from(*) I'd like the option of stopping all further invocations but not sure of the best way of doing this.

Apparently checked exceptions can't be thrown from a Runnable so would appreciate any guidance on how to choose from the following:

scheduledFuture.cancel(false);
...or...
scheduledFuture.cancel(true);
...or...
scheduledExecutorService.shutdown();
...or...
scheduledExecutorService.shutdownNow();
...or...
Throw a custom RuntimeException myself?
...or...
Something else?

(*) Would like to know the general case but in case anyone's interested, the checked exception I'm currently looking at is a ParserConfigurationException thrown from DocumentBuilderFactory.newDocumentBuilder(). If this is thrown, it indicates a serious problem so I'd basically like the scheduling to completely stop rather than potentially repeating the error every time.

Was it helpful?

Solution 2

Based on a few of the helpful comments above here's the gist of my current code - a few q's remain within so would welcome any further comments:

public class ScheduledTask implements Runnable {
    // Configurable values
    private static final int CORE_THREAD_POOL_SIZE = 1;
    private static final int INITIAL_DELAY_MS = 0;
    private static final int INTERVAL_MS = 1000;

    private final ScheduledExecutorService scheduledExecutorService = 
        Executors.newScheduledThreadPool(ScheduledTask.CORE_THREAD_POOL_SIZE);

    private ScheduledFuture<?> scheduledFuture;

    public void run() {
        try {
            try {
                // Do stuff
            } catch RecoverableCheckedException rce { // E.g. SAXException
                // Log and handle appropriately
            }
        } catch UnrecoverableCheckedException uce { // E.g. ParserConfigurationException
            // Not 100% happy with this. It means the caller would need to call
            // getCause() to get the real Exception in this case. But other 
            // RuntimeExceptions wouldn't be wrapped. Could consider catching
            // and wrapping all RuntimeExceptions but I like that even less!
            throw new RuntimeException(uce);
        }
    }

    public boolean isScheduling() {
        return (this.scheduledFuture != null)
               && (!this.scheduledFuture.isDone());
    }

    // May not be needed but provided in case this class is shared.
    public boolean isShutdown() {
        return scheduledExecutorService.isShutdown();
    }

    public void start() {
        // If the Executor Service has already been shutdown, would expect
        // a RejectedExecutionException to be thrown here(?) Not sure what
        // would happen if this method were called when isScheduling() is
        // true?
        this.scheduledFuture = 
            this.scheduledExecutorService.scheduleWithFixedDelay(
                this,
                ScheduledTask.INITIAL_DELAY_MS,
                ScheduledTask.INTERVAL_MS,
                TimeUnit.MILLISECONDS);
    }

    // To be called once at the very end - e.g. on program termination.
    public void shutdown() {
        this.scheduledExecutorService.shutdown();
    }
}

OTHER TIPS

You can use a Callable along with the Future perhaps. This will let you throw a checked exception from within an asynchronous task, yet still catch and handle as needed per task.

If you use that approach, then allowing the task itself to decide how to handle the exception probably makes the most sense. See this answer:

However, if you want to handle the exception outside of the task itself, then I think you will need another thread for each task. Here's one possible option:

    ScheduledExecutorService scheduleExecutor;
    scheduleExecutor = = Executors.newScheduledThreadPool(10); // or whatever

    ExecutorService workerExecutor;
    workerExecutor = Executors.newSingleThreadExecutor(); // or whatever

    public void schedule(final long fixedDelay) {

      scheduleExecutor.scheduleWithFixedDelay(new Runnable() {

        @Override
        public void run() {

            Future<Void> future = workerExecutor.submit(new Callable<Void>() {

                @Override
                public Void call() throws Exception {

                    // Do work here. Throw appropiate exception as needed.

                    return null;
                }
            });

            // Now you can catch and handle the exception in whatever
            // way you need to. You can cancel just this task (which is likely
            // redundant by this point), or you can choose to shutdown
            // all other scheduled tasks (which I doubt is what you want).

            try {
                future.get();
            } catch (Exception e) {
                future.cancel(true);
            }

        }
    }, 0, fixedDelay, TimeUnit.MILLISECONDS);

}
Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top