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

Make sure that disabled jobs are paused in Quartz #25408

Merged
merged 1 commit into from May 11, 2022

Conversation

machi1990
Copy link
Member

Fixes #25214

Disabled methods whose jobs details and triggers were already in the
database were still getting triggered because Quartz invokes all the
jobs in the database that are not in the paused State.
This is to make sure that the disabled methods are effectively not
triggered by Quartz in any way.

Furthermore, I do not believe the full scheduled method implementing the Application custom logic
was called because of the checks in the extensions which meant the
App business logic trigger was not full invoked but pausing the triggers
via Quartz we are sure that the trigger won't be invoked when it is
disabled.

Previously this was done via checks in the extension which mean that
only the Application logic of the scheduled method was not called.

which meant that the trigger was not stored in known business triggers
at

scheduledTasks.put(identity, new QuartzTrigger(trigger, invoker,
SchedulerUtils.parseOverdueGracePeriod(scheduled, defaultOverdueGracePeriod)));

meaning that upon the execution invocation of the method in

public void execute(JobExecutionContext jobExecutionContext) throws JobExecutionException {

the business logic wasn't triggered but a NPE (same as #24931) could have been raised
with the code in main branch

@@ -166,6 +166,7 @@ public QuartzScheduler(SchedulerContext context, QuartzSupport quartzSupport, Sc
String cron = SchedulerUtils.lookUpPropertyValue(scheduled.cron());
if (!cron.isEmpty()) {
if (SchedulerUtils.isOff(cron)) {
this.pause(identity);
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Let's pause the job disabled job to effectively let Quartz know that this should not be invoked in any way.
When the job is enabled/turned on, it'll be rescheduled and executed again.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This results in a org.quartz.Scheduler.pauseJob(JobKey) invocation, right? What happens if the job does not exist yet? Is it a no-op?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If the job does not exists, it is a no-op.
I'll add a test case in the integration test project to make sure we won't have a regression here.

@@ -214,6 +215,7 @@ public QuartzScheduler(SchedulerContext context, QuartzSupport quartzSupport, Sc
} else if (!scheduled.every().isEmpty()) {
OptionalLong everyMillis = SchedulerUtils.parseEveryAsMillis(scheduled);
if (!everyMillis.isPresent()) {
this.pause(identity);
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@machi1990
Copy link
Member Author

@ruiarodrigues this should fix the issue reported in #25214. Can you have a look?

@ruiarodrigues
Copy link

@machi1990 What am I suppose to do/check? 😄

@machi1990
Copy link
Member Author

@machi1990 What am I suppose to do/check? smile

:-)
I've tested that the patch effectively fixes the issue. I'll like a second eye on it as well, so it is possible, can you verify that this PR fixes the issue for you?

You can clone my fork: https://github.com/machi1990/quarkus and checkout to the fix/25214 branch.
Once that is done you can follow the instructions in https://github.com/quarkusio/quarkus/blob/main/CONTRIBUTING.md#build (except the cloning the repo part) to build a local snapshot which you can use in the reproducer.
here is how you could use the snapshot: https://github.com/quarkusio/quarkus/blob/main/CONTRIBUTING.md#usage

@ruiarodrigues
Copy link

I cloned it but I'm having some problems with the download of the dependencies. Is there any snapshot available of your fork?
I checked the PR and I have one question: the if(!everyMillis.isPresent()) statement already discard the job and doesn't add it to the scheduler or reschedule it. So why do you need to pause it here? Can it be read elsewhere and added to the scheduler?

@machi1990
Copy link
Member Author

I cloned it but I'm having some problems with the download of the dependencies. Is there any snapshot available of your fork?

No, I do not have a snapshot. What dependencies issue are you encountering?

I checked the PR and I have one question: the if(!everyMillis.isPresent()) statement already discard the job and doesn't add it to the scheduler or reschedule it. So why do you need to pause it here? Can it be read elsewhere and added to the scheduler?

This is explained in the PR description. The job is discarded and this is fine for in memory jobs but for persisted jobs quartz will try to launch them (

public void execute(JobExecutionContext jobExecutionContext) throws JobExecutionException {
) - load them from the db. The way we wired the jobs in the current main codebase (it might be different from the version of quarkus the reproducer is running), the trigger method annotated with @Scheduled won't be executed - previously this would have cause a NPE (issue #24931, fixed by #25407) . With just that, the @scheduled trigger will be skipped all together.
Now the change in this PR really make sure that Quartz does not try to fire the trigger for disabled jobs. They'll be resumed automatically when the job is enabled / turned on again.

@ruiarodrigues
Copy link

From you explanation, the fix seems fine. I was able to compile finally. I let you know.

@ruiarodrigues
Copy link

I'm not able to run the reproducer with the 999-SNAPSHOT. I think you can go ahead and I will test it again when the final release is available

@machi1990
Copy link
Member Author

I'm not able to run the reproducer with the 999-SNAPSHOT. I think you can go ahead and I will test it again when the final release is available

Thanks for trying anyway. I wonder what's the issue you are facing?
In either way, @mkouba would you be able to re-review and approve/merge. I think once this is in, it can still be tested using main SNAPSHOT - https://github.com/quarkusio/quarkus/blob/main/CONTRIBUTING.md#checking-an-issue-is-fixed-in-main

@Path("cron")
@Produces(MediaType.TEXT_PLAIN)
public String getCronCount() {
return disabledScheduledMethods.valueSetByCronScheduledMethod;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You shouldn't access a static variable like this...

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I didn't give this much thought since it is a variable that's there to only show that it is not initialized.


@ApplicationScoped
public class DisabledScheduledMethods {
static String valueSetByCronScheduledMethod = "";
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think that we should make these variables volatile...

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Same reasoning as #25408 (comment)

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

While the read access above is more a matter of style the fact that this field is not thread-safe is risky and may cause intermittent test failures or false positives.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I see your point. I've added the suggestion and added some comments to the methods

Copy link
Contributor

@mkouba mkouba left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks good. I've added two minor comments about the test though...

Fixes quarkusio#25214

Disabled methods whose jobs details and triggers were already in the
database were still getting triggered because Quartz invokes all the
jobs in the database that are not in the paused State.
This is to make sure that the disabled methods are effectively not
triggered by Quartz in any way.

Furthermore, I do not believe the full scheduled method implementing the Application custom logic
was called because of the checks in the extensions which meant the
App business logic trigger was not full invoked but pausing the triggers
via Quartz we are sure that the trigger won't be invoked when it is
disabled.

Previously this was done via checks in the extension which mean that
only the Application logic of the scheduled method was not called.

- https://github.com/quarkusio/quarkus/blob/4df39b60a0b3d132713e6d60087a1054588d4a5f/extensions/quartz/runtime/src/main/java/io/quarkus/quartz/runtime/QuartzScheduler.java#L215-L218
- https://github.com/quarkusio/quarkus/blob/4df39b60a0b3d132713e6d60087a1054588d4a5f/extensions/quartz/runtime/src/main/java/io/quarkus/quartz/runtime/QuartzScheduler.java#L168-L170

which meant that the trigger was not stored in known business triggers
at https://github.com/quarkusio/quarkus/blob/4df39b60a0b3d132713e6d60087a1054588d4a5f/extensions/quartz/runtime/src/main/java/io/quarkus/quartz/runtime/QuartzScheduler.java#L302-L303

meaning that upon the execution invocation of the method in
https://github.com/quarkusio/quarkus/blob/4df39b60a0b3d132713e6d60087a1054588d4a5f/extensions/quartz/runtime/src/main/java/io/quarkus/quartz/runtime/QuartzScheduler.java#L560
the business logic wasn't triggered but a NPE (same as quarkusio#24931) could have been raised
with the code in main branch
@machi1990
Copy link
Member Author

Looks good. I've added two minor comments about the test though...

Thanks. Can you have a look?

@machi1990 machi1990 requested a review from mkouba May 11, 2022 13:45
@machi1990 machi1990 merged commit 70a6197 into quarkusio:main May 11, 2022
@quarkus-bot quarkus-bot bot added this to the 2.10 - main milestone May 11, 2022
@machi1990
Copy link
Member Author

Thanks @mkouba

@machi1990 machi1990 deleted the fix/25214 branch May 11, 2022 14:47
@gsmet gsmet modified the milestones: 2.10 - main, 2.9.1.Final May 12, 2022
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Disabled schedulers are not disabled
4 participants