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?
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.