2

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.

Steve Chambers
  • 37,270
  • 24
  • 156
  • 208
  • Just a note: throwing a RuntimeException from a task in an ExecutorService doesn't shut down the service. It just interrupts the one task. You'll probably want some other thread monitoring the health of your scheduled jobs an managing the executor appropriately. – Ryan Stewart Dec 21 '12 at 16:46
  • Are you sure? Haven't tested it myself but I'm a bit dubious about this - e.g. see here: http://stackoverflow.com/questions/3308977/should-scheduledexecutorservice-scheduleat-methods-re-schedule-tasks-if-the-tas – Steve Chambers Dec 21 '12 at 16:49
  • Well, the executor will suppress future runs of your task if any given run generates an unhandled exception. This is the default behavior. So what you are looking for is a way to override this in cases where you think the exception is 'easily recovered' from? – Perception Dec 21 '12 at 16:49
  • @Perception: Not exactly no. I'm fine with catching and handling those types of exceptions. What I'm looking for is the correct way to terminate the scheduling when a really bad error happens. We're talking about throwing a RuntimeException here but there are quite a few other ways. Just wondering which is most appropriate and why... – Steve Chambers Dec 21 '12 at 16:53
  • 1
    @SteveChambers: Based on your listed options, I assumed you wanted the entire executor service to shut down on an error, and there's a difference between one task not executing anymore and shutting down the service. A shutdown stops running all tasks in that service (not aggressively unless you shutdownNow()) and destroys the thread pool. If you never shut it down, you've effectively created a resource leak. – Ryan Stewart Dec 21 '12 at 16:57
  • Thanks @RyanStewart. This is getting closer. From what you've said I'll probably not choose to use shutdown or shutdownNow() since it probably shouldn't be stopping other services in case for some miracle they didn't have the exception. Am guessing this.scheduledFuture.cancel(false); may be the cleanest way to stop future invocations but allow current ones to complete... – Steve Chambers Dec 21 '12 at 17:02
  • 1
    with a scheduled task, the cleanest way to shut it down is to throw the exception out of your task. this is clearly documented to cancel the task. if the failure needs to be reported somewhere, then you should have a callback available to the task which handles said reporting. Future is not the way to handle failures from a scheduled task, only useful cancelling the task based on external criteria. – jtahlborn Dec 21 '12 at 17:52
  • This all sounds sensible. My only slight concern is I'd presumably have to do something like catch(Exception e) { throw new RuntimeException(e); } - converting into a RunTimeException seems a bit of a hack just to get it to throw. – Steve Chambers Dec 21 '12 at 22:27
  • @RyanStewart If a ScheduledExecutorService encounters an exception, it does indeed cause a work stoppage of both current and future tasks. [The doc](http://docs.oracle.com/javase/8/docs/api/java/util/concurrent/ScheduledExecutorService.html) for both `scheduleAtFixedRate` and `scheduleWithFixedDelay` says `If any execution of the task encounters an exception, subsequent executions are suppressed.` This [funny post](http://code.nomad-labs.com/2011/12/09/mother-fk-the-scheduledexecutorservice/) explains. – Basil Bourque Jul 23 '14 at 04:39
  • Link to the funny post mentioned above is broken. [Alternative copy of the text](http://www.aiuxian.com/article/p-486355.html) working at the time of this comment. – Steve Chambers May 16 '20 at 21:35

2 Answers2

1

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);

}
Community
  • 1
  • 1
kaliatech
  • 17,579
  • 5
  • 72
  • 84
  • Hmmm the first suggestion seemed OK up until the "do the scheduling yourself" bit. I'm using ScheduledExecutorService to do this with the benefits it provides so would prefer not to change this unless there is a compelling reason. As for the link, I'd seen that but wasn't sure if "a RuntimeException or Error of your choice" is the proper way to do this (even though it works). Am currently thinking scheduledFuture.cancel(false) may do the same thing more elegantly but am a relative novice in all this concurrency stuff so could be stand corrected! – Steve Chambers Dec 21 '12 at 17:07
  • It really depends on what you want/need. Using Callable allows you to throw/handle exceptions. Without that, any exception is going to stop the ScheduledExecutorService and you will not know about it (from outside the task itself) without polling. So the larger issue with ScheduledExecutorService is how you get access to the future _when_ an error occurs. You are correct though, that if you have a reference to the Future, then calling cancel will stop that particular task from continuing....whatever that means after it has already thrown an exception. – kaliatech Dec 21 '12 at 17:13
  • As far as doing the scheduling yourself, it's a relatively easy thing to do and more explicit (IMO) if you truly want to be able to arbitrarily handle error cases of a submitted task outside of the task itself. I'll modify my answer slightly to include the automatic scheduling. – kaliatech Dec 21 '12 at 17:14
  • This is helpful in getting a better understanding of all this. Thanks for your efforts with the sample code -as far as I can tell this look like it'd work but I'm concerned the increased complexity might bite and make it less easy to maintain (for both myself and others). Also not sure Callable is quite what I need as I'll be catching and logging the expected exceptions. If a RunTimeException occurs it will be logged before being rethrown and I think it's a good thing that it will stop the scheduling since anything that unexpected probably needs something to be done about it before continuing. – Steve Chambers Dec 21 '12 at 22:18
  • Just ran out of characters there but was going to say thanks again for you efforts - I'll relook at this at work on Xmas eve morning. – Steve Chambers Dec 21 '12 at 22:19
1

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();
    }
}
Steve Chambers
  • 37,270
  • 24
  • 156
  • 208
  • Hi Steve,I put the interval as 15 minutes. For the first two iterations it worked well but for the remaining,it is getting wrong time. – rajeesh May 20 '14 at 13:36
  • Not sure why that would be. Would suggest posting a new StackOverflow question as there are some very helpful people on here when it comes to Java threading issues. – Steve Chambers May 20 '14 at 21:36