javascheduled-tasksschedulerscheduling

Compensate system sleep for scheduled tasks?


I wrote a simple proof-of-concept for scheduling a runnable to execute at a given time. The code works exactly as expected and executes the runnable at the scheduled time:

public static void main(String[] args) throws InterruptedException, ExecutionException {
    DateTimeFormatter formatter = DateTimeFormatter.ofPattern("MM/dd/yyyy hh:mm:ss a");
    ScheduledExecutorService scheduler = Executors.newScheduledThreadPool(1);
    Runnable task = () -> System.out.println("Task Ran @ " + formatter.format(LocalDateTime.now()));

    LocalDateTime now = LocalDateTime.now();
    LocalDateTime runTime = ciel(ChronoUnit.MINUTES, now.plusMinutes(1));

    Duration duration = Duration.between(now, runTime);
    long initialDelay = duration.toMillis();

    System.out.println("Schedule @ " + formatter.format(now));
    System.out.println("Expected @ " + formatter.format(runTime));
    scheduler.schedule(task, initialDelay, TimeUnit.MILLISECONDS);

    scheduler.shutdown();
    scheduler.awaitTermination(2, TimeUnit.MINUTES);
}

private static LocalDateTime ciel(TemporalUnit precision, LocalDateTime time) {
    return time.truncatedTo(precision).plus(1, precision);
}

Output:

Schedule @ 12/18/2023 11:31:47 AM
Expected @ 12/18/2023 11:33:00 AM
Task Ran @ 12/18/2023 11:33:00 AM

However, if I put the system to sleep while the program is running and proceed to wake it later, the runnable will be executed outside of the scheduled time:

Schedule @ 12/18/2023 11:35:53 AM
Expected @ 12/18/2023 11:37:00 AM
Task Ran @ 12/18/2023 11:39:13 AM

Assuming the OS wakes before the scheduled time, how can I compensate for this so the runnable is still executed when expected?


Solution

  • As I noted in comments, the issue you observe with a system suspend / sleep delaying execution of tasks scheduled via a ScheduledExecutorService appears to be known issue JDK-8146527, originally filed at the beginning of 2016. There doesn't appear to be any workaround available from ScheduledExecutorService or its standard implementation ScheduledThreadPoolExecutor.

    However, you might be able to work around it via a different scheduling mechanism, such as java.util.Timer. Moreover, that particular class offers a built-in method for scheduling execution at a particular point in time, so that you should not need to compute a delay yourself if you don't already have one. If you choose to use a Timer then you should be sure to read and understand the caveats expressed in its docs. That might look like so:

    import java.util.Timer;  // Do not confuse this with javax.swing.Timer
    // ...
    
    public static void main(String[] args) {
        DateTimeFormatter formatter = DateTimeFormatter.ofPattern("MM/dd/yyyy hh:mm:ss a");
        Timer timer = new Timer();
        TimerTask task = new TimerTask() {
            public void run() {
                System.out.println("Task Ran @ " + formatter.format(ZonedDateTime.now()));
                timer.cancel();
            }
        };
        ZonedDateTime now = ZonedDateTime.now();
        ZonedDateTime runTime = now.plusMinutes(1);
    
        System.out.println("Schedule @ " + formatter.format(now));
        System.out.println("Expected @ " + formatter.format(runTime));
        timer.schedule(task, Date.from(runTime.toInstant()));
    }
    

    That method works for scheduling a task at a specified time, but I have no good means to test it in the specific case of a system suspend. If it doesn't solve that issue then you probably need to find a third-party scheduler that can handle it, or else write your own.