From 129b98b3828967264642a61bf69ff7eba7dd63bc Mon Sep 17 00:00:00 2001 From: Martin Kouba Date: Fri, 27 Sep 2024 14:27:56 +0200 Subject: [PATCH] Scheduler: document `@Scheduled#executionMaxDelay()` - also add test for JobDefinition#setExecutionMaxDelay() --- .../main/asciidoc/scheduler-reference.adoc | 28 ++++++++++++++--- .../DelayedExecutionTest.java | 30 ++++++++++++++----- 2 files changed, 47 insertions(+), 11 deletions(-) diff --git a/docs/src/main/asciidoc/scheduler-reference.adoc b/docs/src/main/asciidoc/scheduler-reference.adoc index 8039175bb7a533..8255fdcb965cf3 100644 --- a/docs/src/main/asciidoc/scheduler-reference.adoc +++ b/docs/src/main/asciidoc/scheduler-reference.adoc @@ -220,7 +220,8 @@ Property Expressions. (Note that `"{property.path}"` style expressions are still void myMethod() { } ---- -=== Delayed Execution +[[delayed_start]] +=== Delayed Start of a Trigger `@Scheduled` provides two ways to delay the time a trigger should start firing at. @@ -249,9 +250,8 @@ void everyTwoSeconds() { } NOTE: If `@Scheduled#delay()` is set to a value greater than zero the value of `@Scheduled#delayed()` is ignored. The main advantage over `@Scheduled#delay()` is that the value is configurable. -The `delay` attribute supports <> including default values and nested -Property Expressions. (Note that `"{property.path}"` style expressions are still supported but don't offer the full functionality of Property Expressions.) - +The `delay` attribute supports <> including default values and nested Property Expressions. +(Note that `"{property.path}"` style expressions are still supported but don't offer the full functionality of Property Expressions.) [source,java] ---- @@ -260,6 +260,26 @@ void everyTwoSeconds() { } ---- <1> The config property `myMethod.delay.expr` is used to set the delay. +[[delayed_execution]] +=== Delayed Execution + +`@Scheduled#executionMaxDelay()` can be set to delay each execution of a scheduled method. +The value represents the maximum delay between the activation of the trigger and the execution of the scheduled method. +The actual delay varies randomly over time but it never exceeds the maximum value. + +The value is parsed with `DurationConverter#parseDuration(String)`. +It can be a property expression. +In that case, the scheduler attempts to use the configured value instead: `@Scheduled(executionMaxDelay = "${myJob.maxDelay}")`. +Additionally, the property expression can specify a default value: +`@Scheduled(executionMaxDelay = "${myJob.maxDelay}:500ms}")`. + +[source,java] +---- +@Scheduled(every = "2s", executionMaxDelay = "500ms") <1> +void everyTwoSeconds() { } +---- +<1> The delay will be a value between 0 and 500 milliseconds. As a result, the period between to `everyTwoSeconds()` executions will be roughly between two and two and a half seconds. + [[concurrent_execution]] === Concurrent Execution diff --git a/extensions/scheduler/deployment/src/test/java/io/quarkus/scheduler/test/delayedexecution/DelayedExecutionTest.java b/extensions/scheduler/deployment/src/test/java/io/quarkus/scheduler/test/delayedexecution/DelayedExecutionTest.java index 8cbabeab9540da..b5e07fc95669c6 100644 --- a/extensions/scheduler/deployment/src/test/java/io/quarkus/scheduler/test/delayedexecution/DelayedExecutionTest.java +++ b/extensions/scheduler/deployment/src/test/java/io/quarkus/scheduler/test/delayedexecution/DelayedExecutionTest.java @@ -1,6 +1,5 @@ package io.quarkus.scheduler.test.delayedexecution; -import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertTrue; import java.util.concurrent.CountDownLatch; @@ -11,8 +10,10 @@ import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.RegisterExtension; +import io.quarkus.runtime.StartupEvent; import io.quarkus.scheduler.DelayedExecution; import io.quarkus.scheduler.Scheduled; +import io.quarkus.scheduler.Scheduler; import io.quarkus.test.QuarkusUnitTest; public class DelayedExecutionTest { @@ -24,23 +25,38 @@ public class DelayedExecutionTest { @Test public void testSimpleScheduledJobs() throws InterruptedException { assertTrue(Jobs.EVENT_LATCH.await(5, TimeUnit.SECONDS)); - assertTrue(Jobs.LATCH.await(5, TimeUnit.SECONDS)); + assertTrue(Jobs.LATCH1.await(5, TimeUnit.SECONDS)); + assertTrue(Jobs.LATCH2.await(5, TimeUnit.SECONDS)); } static class Jobs { - static final CountDownLatch LATCH = new CountDownLatch(1); - static final CountDownLatch EVENT_LATCH = new CountDownLatch(1); + static final CountDownLatch LATCH1 = new CountDownLatch(1); + static final CountDownLatch LATCH2 = new CountDownLatch(1); + static final CountDownLatch EVENT_LATCH = new CountDownLatch(2); @Scheduled(identity = "foo", every = "1s", executionMaxDelay = "500ms") static void everySecond() { - LATCH.countDown(); + LATCH1.countDown(); + } + + void start(@Observes StartupEvent event, Scheduler scheduler) { + scheduler.newJob("bar") + .setInterval("1s") + .setExecutionMaxDelay("500ms") + .setTask(se -> { + LATCH2.countDown(); + }).schedule(); } void onDelay(@Observes DelayedExecution delayedExecution) { assertTrue(delayedExecution.getDelay() < 500); - assertEquals("foo", delayedExecution.getExecution().getTrigger().getId()); - EVENT_LATCH.countDown(); + String id = delayedExecution.getExecution().getTrigger().getId(); + if ("foo".equals(id) || "bar".equals(id)) { + EVENT_LATCH.countDown(); + } else { + throw new IllegalStateException("Invalid job identity: " + id); + } } }