Skip to content
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

Scheduler: inheritance of metadata #38791

Merged
merged 1 commit into from
Feb 15, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
44 changes: 37 additions & 7 deletions docs/src/main/asciidoc/scheduler-reference.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -34,12 +34,16 @@

TIP: The annotation is repeatable so a single method could be scheduled multiple times.

[WARNING]
====
Subclasses never inherit the metadata of a `@Scheduled` method declared on a superclass. In the following example, the `everySecond()` method is only invoked upon the instance of `Jobs`.
=== Inheritance of metadata

A subclass never inherits the metadata of a `@Scheduled` method declared on a superclass.
For example, suppose the class `org.amce.Foo` is extended by the class `org.amce.Bar`.
If `Foo` declares a non-static method annotated with `@Scheduled` then `Bar` does not inherit the metadata of the scheduled method.
In the following example, the `everySecond()` method is only invoked upon the instance of `Foo`.

[source,java]
----
class Jobs {
class Foo {

@Scheduled(every = "1s")
void everySecond() {
Expand All @@ -48,12 +52,38 @@
}

@Singleton
class MyJobs extends Jobs {
class Bar extends Foo {
}
----
====

A CDI event of type `io.quarkus.scheduler.SuccessfulExecution` is fired synchronously and asynchronously when an execution of a scheduled method is successful. A CDI event of type `io.quarkus.scheduler.FailedExecution` is fired synchronously and asynchronously when an execution of a scheduled method throws an exception.
=== CDI events

Check warning on line 59 in docs/src/main/asciidoc/scheduler-reference.adoc

View workflow job for this annotation

GitHub Actions / Linting with Vale

[vale] reported by reviewdog 🐶 [Quarkus.Headings] Use sentence-style capitalization in 'CDI events'. Raw Output: {"message": "[Quarkus.Headings] Use sentence-style capitalization in 'CDI events'.", "location": {"path": "docs/src/main/asciidoc/scheduler-reference.adoc", "range": {"start": {"line": 59, "column": 5}}}, "severity": "INFO"}

Some CDI events are fired synchronously and asynchronously when specific events occur.

|===
|Type |Event description

|`io.quarkus.scheduler.SuccessfulExecution`
|An execution of a scheduled job completed successfuly.

|`io.quarkus.scheduler.FailedExecution`
|An execution of a scheduled job completed with an exception.

|`io.quarkus.scheduler.SkippedExecution`
|An execution of a scheduled job was skipped.

|`io.quarkus.scheduler.SchedulerPaused`
|The scheduler was paused.

|`io.quarkus.scheduler.SchedulerResumed`
|The scheduler was resumed.

|`io.quarkus.scheduler.ScheduledJobPaused`
|A scheduled job was paused.

|`io.quarkus.scheduler.ScheduledJobResumed`
|A scheduled job was resumed.
|===

=== Triggers

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,11 @@
* {@code java.util.concurrent.CompletionStage<Void>} or {@code io.smallrye.mutiny.Uni<Void>}, or is annotated with
* {@link io.smallrye.common.annotation.NonBlocking} is executed on the event loop.
*
* <h2>Inheritance of metadata</h2>
* A subclass never inherits the metadata of a {@link Scheduled} method declared on a superclass. For example, suppose the class
* {@code org.amce.Foo} is extended by the class {@code org.amce.Bar}. If {@code Foo} declares a non-static method annotated
* with {@link Scheduled} then {@code Bar} does not inherit the metadata of the scheduled method.
*
* @see ScheduledExecution
*/
@Target(METHOD)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -132,14 +132,21 @@ void collectScheduledMethods(BeanArchiveIndexBuildItem beanArchives, BeanDiscove
}
for (AnnotationInstance annotationInstance : schedules) {
if (annotationInstance.target().kind() != METHOD) {
continue;
continue; // This should never happen as the annotation has @Target(METHOD)
}
MethodInfo method = annotationInstance.target().asMethod();
ClassInfo declaringClass = method.declaringClass();
if (!Modifier.isStatic(method.flags())
&& (Modifier.isAbstract(declaringClass.flags()) || declaringClass.isInterface())) {
throw new IllegalStateException(String.format(
"Non-static @Scheduled methods may not be declared on abstract classes and interfaces: %s() declared on %s",
method.name(), declaringClass.name()));
}
if (Modifier.isStatic(method.flags()) && !KotlinUtil.isSuspendMethod(method)) {
scheduledBusinessMethods.produce(new ScheduledBusinessMethodItem(null, method, schedules,
transformedAnnotations.hasAnnotation(method, SchedulerDotNames.NON_BLOCKING),
transformedAnnotations.hasAnnotation(method, SchedulerDotNames.RUN_ON_VIRTUAL_THREAD)));
LOGGER.debugf("Found scheduled static method %s declared on %s", method, method.declaringClass().name());
LOGGER.debugf("Found scheduled static method %s declared on %s", method, declaringClass.name());
}
}

Expand Down Expand Up @@ -425,14 +432,26 @@ private String generateInvoker(ScheduledBusinessMethodItem scheduledMethod, Clas
String returnTypeStr = DescriptorUtils.typeToString(method.returnType());
ResultHandle res;
if (isStatic) {
if (method.parameterTypes().isEmpty()) {
res = tryBlock.invokeStaticMethod(
MethodDescriptor.ofMethod(implClazz.name().toString(), method.name(), returnTypeStr));
if (implClazz.isInterface()) {
if (method.parameterTypes().isEmpty()) {
res = tryBlock.invokeStaticInterfaceMethod(
MethodDescriptor.ofMethod(implClazz.name().toString(), method.name(), returnTypeStr));
} else {
res = tryBlock.invokeStaticInterfaceMethod(
MethodDescriptor.ofMethod(implClazz.name().toString(), method.name(), returnTypeStr,
ScheduledExecution.class),
tryBlock.getMethodParam(0));
}
} else {
res = tryBlock.invokeStaticMethod(
MethodDescriptor.ofMethod(implClazz.name().toString(), method.name(), returnTypeStr,
ScheduledExecution.class),
tryBlock.getMethodParam(0));
if (method.parameterTypes().isEmpty()) {
res = tryBlock.invokeStaticMethod(
MethodDescriptor.ofMethod(implClazz.name().toString(), method.name(), returnTypeStr));
} else {
res = tryBlock.invokeStaticMethod(
MethodDescriptor.ofMethod(implClazz.name().toString(), method.name(), returnTypeStr,
ScheduledExecution.class),
tryBlock.getMethodParam(0));
}
}
} else {
// InjectableBean<Foo> bean = Arc.container().bean("foo1");
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
package io.quarkus.scheduler.test;

import static org.junit.jupiter.api.Assertions.fail;

import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.RegisterExtension;

import io.quarkus.scheduler.Scheduled;
import io.quarkus.test.QuarkusUnitTest;

public class NonStaticScheduledAbstractClassTest {

@RegisterExtension
static final QuarkusUnitTest test = new QuarkusUnitTest()
.setExpectedException(IllegalStateException.class)
.withApplicationRoot(root -> root
.addClasses(AbstractClassWitchScheduledMethod.class));

@Test
public void test() {
fail();
}

static abstract class AbstractClassWitchScheduledMethod {

@Scheduled(every = "1s")
void everySecond() {
}

}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
package io.quarkus.scheduler.test;

import static org.junit.jupiter.api.Assertions.fail;

import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.RegisterExtension;

import io.quarkus.scheduler.Scheduled;
import io.quarkus.test.QuarkusUnitTest;

public class NonStaticScheduledInterfaceTest {

@RegisterExtension
static final QuarkusUnitTest test = new QuarkusUnitTest()
.setExpectedException(IllegalStateException.class)
.withApplicationRoot(root -> root
.addClasses(InterfaceWitchScheduledMethod.class));

@Test
public void test() {
fail();
}

interface InterfaceWitchScheduledMethod {

@Scheduled(every = "1s")
void everySecond();

}

}
Original file line number Diff line number Diff line change
Expand Up @@ -16,11 +16,13 @@ public class ScheduledStaticMethodTest {
@RegisterExtension
static final QuarkusUnitTest test = new QuarkusUnitTest()
.withApplicationRoot((jar) -> jar
.addClasses(Jobs.class));
.addClasses(Jobs.class, AbstractJobs.class, InterfaceJobs.class));

@Test
public void testSimpleScheduledJobs() throws InterruptedException {
assertTrue(Jobs.LATCH.await(5, TimeUnit.SECONDS));
assertTrue(AbstractJobs.LATCH.await(5, TimeUnit.SECONDS));
assertTrue(InterfaceJobs.LATCH.await(5, TimeUnit.SECONDS));
}

static class Jobs {
Expand All @@ -31,6 +33,29 @@ static class Jobs {
static void everySecond() {
LATCH.countDown();
}

}

static abstract class AbstractJobs {

static final CountDownLatch LATCH = new CountDownLatch(1);

@Scheduled(every = "1s")
static void everySecond() {
LATCH.countDown();
}

}

interface InterfaceJobs {

CountDownLatch LATCH = new CountDownLatch(1);

@Scheduled(every = "1s")
static void everySecond() {
LATCH.countDown();
}

}

}
Loading