4

I use ScheduledExecutorService to schedule some tasks which need to run periodically. I want to know whether this code works to recover the schedule when an exception happens.

ScheduledExecutorService service = Executors.newScheduledThreadPool(1);
this.startMemoryUpdateSchedule(service);//See below method

//Recursive method to handle exception when run schedule task
private void startMemoryUpdateSchedule(ScheduledExecutorService service) {
    ScheduledFuture<?> future = service.scheduleWithFixedDelay(new MemoryUpdateThread(), 1, UPDATE_MEMORY_SCHEDULE, TimeUnit.MINUTES);
    try {
        future.get();
    } catch (ExecutionException e) {
        e.printStackTrace();
        logger.error("Exception thrown for thread",e);
        future.cancel(true);
        this.startMemoryUpdateSchedule(service);
    } catch(Exception e) {
        logger.error("Other exception ",e);
    }
}
assylias
  • 321,522
  • 82
  • 660
  • 783
Simon Wang
  • 2,235
  • 7
  • 36
  • 48
  • hey when you call this.startMemoryUpdateSchedule(service) from main thread. The main thread will have to wait for process to finish but your function will be running foreover.the main thread will go in sleep?? – Prannoy Mittal Oct 13 '12 at 12:38
  • @ Parannoy, i will create a new thread for this function from main thread. – Simon Wang Oct 15 '12 at 01:59

4 Answers4

2

You should probably enclose the try block in a while(true) loop because if the first run does not throw an exception, you will exit your method and if the second call throws one, you won't catch it.

I would also run the recursive call in its own thread to avoid the risk of a StackOverFlow error if things go bad.

So it would look like this:

private void startMemoryUpdateSchedule(final ScheduledExecutorService service) {
    final ScheduledFuture<?> future = service.scheduleWithFixedDelay(new MemoryUpdateThread(), 1, UPDATE_MEMORY_SCHEDULE, TimeUnit.MINUTES);
    Runnable watchdog = new Runnable() {

        @Override
        public void run() {
            while (true) {
                try {
                    future.get();
                } catch (ExecutionException e) {
                    //handle it
                    startMemoryUpdateSchedule(service);
                    return;
                } catch (InterruptedException e) {
                    //handle it
                    return;
                }
            }
        }
    };
    new Thread(watchdog).start();
}
assylias
  • 321,522
  • 82
  • 660
  • 783
  • @ assylias, i call startMemoryUpdateSchedule() method in init() of one Servlet, it seems it will block even if no exception is thrown? – Simon Wang May 31 '12 at 01:54
  • 1
    @Grace Not sure I understand your question. The code I proposed will return immediately because the blocking call (`future.get()`) is called from a separate thread. I think in the end you should write some tests with a simple runnable that sends an exception every 5 calls for example, and see if the behaviour is as you expect. – assylias May 31 '12 at 08:12
  • @ assylias, thanks for your explanation, i think it's clear to me now~ – Simon Wang Jun 04 '12 at 01:47
1

ScheduledExecutorService.scheduleWithFixedDelay(Runnable, long, long, TimeUnit) throws RejectedExecutionException (a child of RuntimeException) ==> We can catch it & retry submission once more.

Now as future.get() is supposed to return the result of one execution, we need to invoke it in a loop.

Also, the failure of one execution does not affect the next scheduled execution, which differentiates the ScheduledExecutorService from the TimerTask which executes the scheduled tasks in the same thread => failure in one execution would abort the schedule in case of TimerTask (http://stackoverflow.com/questions/409932/java-timer-vs-executorservice) We just need to catch all the three exceptions thrown by Future.get(), but we can not rethrow them, then we won't be able to get the result of the subsequent executions.

The code could be:

public void startMemoryUpdateSchedule(final ScheduledExecutorService service) {
final ScheduledFuture<?> future;
    try {
        future = service.scheduleWithFixedDelay(new MemoryUpdateThread(),
                1, UPDATE_MEMORY_SCHEDULE, TimeUnit.SECONDS);
    } catch (RejectedExecutionException ree) {
        startMemoryUpdateSchedule(service);
        return;
    }
    while (true) {
        try {
            future.get();
        } catch (InterruptedException ie) {
            Thread.currentThread().interrupt();
        } catch (ExecutionException ee) {
            Throwable cause = ee.getCause();
            // take action, log etc.
        } catch (CancellationException e) {
          // safety measure if task was cancelled by some external agent.
        }
    }
}
lab bhattacharjee
  • 1,617
  • 2
  • 17
  • 33
  • I'm not sure about the "failure of one execution does not affect the next scheduled execution" for scheduleWithFixedDelay. From the [Javadoc:](http://docs.oracle.com/javase/6/docs/api/java/util/concurrent/ScheduledExecutorService.html#scheduleWithFixedDelay) _If any execution of the task encounters an exception, subsequent executions are suppressed. Otherwise, the task will only terminate via cancellation or termination of the executor._ – alph486 Oct 11 '13 at 15:59
1

Try to use VerboseRunnable class from jcabi-log, which is designed exactly for this purpose:

import com.jcabi.log.VerboseRunnable;
Runnable runnable = new VerboseRunnable(
  Runnable() {
    public void run() { 
      // do business logic, may Exception occurs
    }
  },
  true // it means that all exceptions will be swallowed and logged
);

Now, when anybody calls runnable.run() no exceptions are thrown. Instead, they are swallowed and logged (to SLF4J).

yegor256
  • 102,010
  • 123
  • 446
  • 597
0

I've added the loop as discussed.

public void startMemoryUpdateSchedule(final ScheduledExecutorService service) {

    boolean retry = false;

    do {

        ScheduledFuture<?> future = null;
        try {
            retry = false;
            future = service.scheduleWithFixedDelay(new MemoryUpdateThread(), 1, UPDATE_MEMORY_SCHEDULE, TimeUnit.SECONDS);
            future.get();
        } catch (ExecutionException e) {
            // handle
            future.cancel(true);
            retry = true;
        } catch(Exception e) {
            // handle
        }           

    } while (retry);

}
Kalyan Sarkar
  • 228
  • 2
  • 7
  • If an exception is thrown by the second execution, you won't catch the exception because you would have exited the method after the first (successful) run. – assylias May 30 '12 at 14:47