-
Notifications
You must be signed in to change notification settings - Fork 38.3k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Cron scheduled methods may be executed a fraction of a second too early #29735
Comments
Hi @thuri, I've tried to debug and find the root cause of this issue but without success so far. I've also tried to run a test that uses I've suspected at some point that the part of Just had 1 error with the following logs and the original cron expression from your
What we see above are a debugger log from the So: the runnable does compute a delay of 59975ms, which should result in an execution time at So all we're left with is the |
Hi @simonbasle , thank you for having a look into this.
But what stands against that is, that the code works when downgraded to spring boot 2.7.6 and run with the same setup (see README in example project). So I'm not sure whether this could be a Java 17 issue. |
I ran the example app on another system and had the same results. Ubuntu 20.04.5 LTS I also switched to a Temerium JVM also with the same issue |
Clock drifting / rolling back is still the prime suspect in my opinion. One way to definitely validate this hypothesis is for you to simplify the code as much as possible, eliminating all moving pieces (including Spring) and try to emulate the code in Nevertheless, I would advise you to try and avoid recomputing your own "next time" in the production code. It seems you're replicating the job of (edit: clarify I'm asking the |
this is the test I came up with that reproduces the code ultimately executed in public static void main(String[] args) throws ExecutionException, InterruptedException {
CronTrigger trigger = new CronTrigger("*/5 * * * * * ");
ScheduledExecutorService executor = Executors.newSingleThreadScheduledExecutor();
SimpleTriggerContext triggerContext = new SimpleTriggerContext(Clock.systemUTC());
final List<Instant> instants = new ArrayList<>();
Callable<Instant> captureNow = () -> {
Instant now = triggerContext.getClock().instant();
instants.add(now);
return now;
};
for (int i = 0; i < 50; i++) {
Instant scheduledExecutionTime = trigger.nextExecution(triggerContext);
if (scheduledExecutionTime == null) {
throw new NullPointerException("stopped by null");
}
Instant delayBaseNow = triggerContext.getClock().instant();
Duration delayDuration = Duration.between(delayBaseNow, scheduledExecutionTime);
System.out.printf("#%d Scheduling cron job at %s\n", i, scheduledExecutionTime);
if (delayDuration.isNegative()) {
throw new IllegalStateException("between now and scheduledExecutionTime, duration is negative (" + delayDuration + ")\n");
}
else {
System.out.printf("#%d Expected to run job at %s (duration of %s)\n\n", i, delayBaseNow.plus(delayDuration), delayDuration.toMillis());
}
long initialDelay = delayDuration.toMillis();
var currentFuture = executor.schedule(captureNow, initialDelay, TimeUnit.MILLISECONDS);
Instant executionTime = currentFuture.get();
triggerContext.update(scheduledExecutionTime, executionTime, executionTime);
if (executionTime.isBefore(scheduledExecutionTime)) {
throw new IllegalStateException("Clock drift in round " + i + " at " + executionTime + " which was scheduled at " + scheduledExecutionTime);
}
}
System.exit(0);
} Again, on my machine, it doesn't fail that often. But I just had a failure which proves that the issue occurs at lower levels than Spring, ie. clock drift.
(note that I implemented the reproducer as a |
Hi, I have seen the same problem on my project with spring boot 3.0.1, so as thuri I went back to spring boot 2.7.6. |
thanks for this data point @sigfridobarb. In light of all of that I'm closing the issue. If anybody is able to dig into the root cause at the JDK or OS level and that knowledge brings an avenue of improvement to light, feel free to submit a PR. |
Thanks for investigating this thing. The closing is ok for me as I already have a workaround for my issue. Perhaps it would be a nice feature if metadata about the scheduling could be injected into the method. That metadata could perhaps contain the last and the next (planned) executionTime. |
@thuri I've looked a bit at this idea of injecting execution context, but the code is a bit too far removed for this too be practical. Also, only the |
Hi @simonbasle, I'm experiencing the same issues with Spring Boot 3 now. I have a task which gets triggered by a For me it looks like a Spring Boot 3/Spring Framework 6 issue, because when I downgrade to Spring Boot 2 again (also running with Java 17), everything is working fine. Cheers |
import java.time.Duration;
import java.time.LocalDateTime;
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;
public class Test {
private static ScheduledExecutorService executor = Executors.newSingleThreadScheduledExecutor();
public static void main(String[] args) {
schedule();
}
public static void schedule() {
LocalDateTime now = LocalDateTime.now();
LocalDateTime scheduled = now.plus(Duration.ofSeconds(1));
executor.schedule(() -> {
long delay = Duration.between(scheduled, LocalDateTime.now()).toMillis();
System.out.println("delta: " + delay + "ms");
if (delay < 0) {
System.exit(1);
}
schedule();
}, Duration.between(now, scheduled).toNanos(), TimeUnit.NANOSECONDS);
}
} test output like this:
3.2 GHz Intel Core i7 iMac 2019 OpenJDK Runtime Environment Temurin-17.0.5+8 (build 17.0.5+8) |
The testcode from @simonbasle throws an exception on every run for me. However, if I change it to use nanos instead of millis, it runs fine. So I'm not sure if this should be kicked up to JVM/OS level. I have a process with a cron on second 0, and that triggers early roughly 50% of the time. I'm running OpenJDK 19.0.2 on Ubuntu 22.04.02. |
We have same cron issue with Spring Boot 2.1.18. |
@Somnium7 it can't be the same issue since this is about Spring Boot 3.0+. You are using an unsupported Spring Boot version with unpatched CVEs, please upgrade to a supported Spring Boot version as soon as possible. |
@bclozel Well, cause of the issue may be different, I know nothing about Spring Boot internals, but cron jobs definitely still could be executed earlier than they should even on old versions. Thanks for version reminder though, |
Affects: 6.0.3
Hi Spring Team,
i was just updating a project of mine from spring-boot 2.7.6 to spring-boot 3.0.0 and may have found an issue with cron scheduled beans. I'm quite sure that this has something to do with spring-context and not Spring Boot itself, so i decided to create this issue here.
When the application ran with 2.7.6 the cron expression "0 */1 * * * *" executed the annotated method always some faction of a second AFTER second 0 of each minute.
With 3.0.1 the job may be executed "around" the second 0 of each minute. That means, that a job may be executed at second 59 + some fraction of a second.
See the following log output:
In most cases that may not be a problem but in my project i use the current time (java.time.Instant.now) to calculate the next iteration with
CronExpression#next
which will result in the timestamp of the next minute where the second is 0. This means that the result ofnext
is actually the timestamp on which the current execution should have been started.I did create a small demo project to show you what I mean and to reproduce the problem if possible: https://github.com/thuri/spring6-scheduled-cron
I had a look at CronExpression source code but didn't find a change that could be the cause of this. Maybe I'll have some time to have a deeper look into this.
Regards,
Michael
The text was updated successfully, but these errors were encountered: