Skip to content

Commit

Permalink
Merge pull request #38957 from manovotn/issue38928
Browse files Browse the repository at this point in the history
Quartz - fix @dependent job creation/destruction when there is a re-fire
  • Loading branch information
mkouba authored Feb 22, 2024
2 parents 2ae5b45 + 01a10a2 commit a458734
Show file tree
Hide file tree
Showing 3 changed files with 82 additions and 9 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ public class DependentBeanJobTest {
@RegisterExtension
static final QuarkusUnitTest test = new QuarkusUnitTest()
.withApplicationRoot((jar) -> jar
.addClasses(Service.class, MyJob.class)
.addClasses(Service.class, MyJob.class, RefiringJob.class)
.addAsResource(new StringAsset("quarkus.quartz.start-mode=forced"),
"application.properties"));

Expand Down Expand Up @@ -75,7 +75,49 @@ public void testDependentBeanJobDestroyed() throws SchedulerException, Interrupt
.withSchedule(
SimpleScheduleBuilder.simpleSchedule()
.withIntervalInMilliseconds(333)
.withRepeatCount(3))
.withRepeatCount(2))
.build();
quartz.scheduleJob(job, trigger);

assertTrue(execLatch.await(2, TimeUnit.SECONDS), "Latch count: " + execLatch.getCount());
assertTrue(constructLatch.await(2, TimeUnit.SECONDS), "Latch count: " + constructLatch.getCount());
assertTrue(destroyedLatch.await(2, TimeUnit.SECONDS), "Latch count: " + destroyedLatch.getCount());
}

@Test
public void testDependentBeanJobWithRefire() throws SchedulerException, InterruptedException {
// 5 one-off jobs should trigger construction/execution/destruction 10 times in total
CountDownLatch execLatch = service.initExecuteLatch(10);
CountDownLatch constructLatch = service.initConstructLatch(10);
CountDownLatch destroyedLatch = service.initDestroyedLatch(10);
for (int i = 0; i < 5; i++) {
Trigger trigger = TriggerBuilder.newTrigger()
.withIdentity("myTrigger" + i, "myRefiringGroup")
.startNow()
.build();
JobDetail job = JobBuilder.newJob(RefiringJob.class)
.withIdentity("myJob" + i, "myRefiringGroup")
.build();
quartz.scheduleJob(job, trigger);
}
assertTrue(execLatch.await(2, TimeUnit.SECONDS), "Latch count: " + execLatch.getCount());
assertTrue(constructLatch.await(2, TimeUnit.SECONDS), "Latch count: " + constructLatch.getCount());
assertTrue(destroyedLatch.await(2, TimeUnit.SECONDS), "Latch count: " + destroyedLatch.getCount());

// repeating job triggering three times; we expect six beans to exist for that due to refires
execLatch = service.initExecuteLatch(6);
constructLatch = service.initConstructLatch(6);
destroyedLatch = service.initDestroyedLatch(6);
JobDetail job = JobBuilder.newJob(RefiringJob.class)
.withIdentity("myRepeatingJob", "myRefiringGroup")
.build();
Trigger trigger = TriggerBuilder.newTrigger()
.withIdentity("myRepeatingTrigger", "myRefiringGroup")
.startNow()
.withSchedule(
SimpleScheduleBuilder.simpleSchedule()
.withIntervalInMilliseconds(333)
.withRepeatCount(2))
.build();
quartz.scheduleJob(job, trigger);

Expand Down Expand Up @@ -141,4 +183,34 @@ public void execute(JobExecutionContext context) throws JobExecutionException {
service.execute();
}
}

@Dependent
static class RefiringJob implements Job {

@Inject
Service service;

@PostConstruct
void postConstruct() {
service.constructedLatch();
}

@PreDestroy
void preDestroy() {
service.destroyedLatch();
}

@Override
public void execute(JobExecutionContext context) throws JobExecutionException {
if (context.getRefireCount() == 0) {
service.execute();
// request re-fire; we expect a new dependent bean to be used for that
throw new JobExecutionException("Refiring job", true);
} else {
service.execute();
// no re-fire the second time
throw new JobExecutionException("Job was re-fired successfully", false);
}
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -17,19 +17,20 @@
*/
class CdiAwareJob implements Job {

private final Instance.Handle<? extends Job> jobInstanceHandle;
private final Instance<? extends Job> jobInstance;

public CdiAwareJob(Instance.Handle<? extends Job> jobInstanceHandle) {
this.jobInstanceHandle = jobInstanceHandle;
public CdiAwareJob(Instance<? extends Job> jobInstance) {
this.jobInstance = jobInstance;
}

@Override
public void execute(JobExecutionContext context) throws JobExecutionException {
Instance.Handle<? extends Job> handle = jobInstance.getHandle();
try {
jobInstanceHandle.get().execute(context);
handle.get().execute(context);
} finally {
if (jobInstanceHandle.getBean().getScope().equals(Dependent.class)) {
jobInstanceHandle.destroy();
if (handle.getBean().getScope().equals(Dependent.class)) {
handle.destroy();
}
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -1246,7 +1246,7 @@ public Job newJob(TriggerFiredBundle bundle, org.quartz.Scheduler Scheduler) thr
Instance<? extends Job> instance = jobs.select(jobClass);
if (instance.isResolvable()) {
// This is a job backed by a CDI bean
return jobWithSpanWrapper(new CdiAwareJob(instance.getHandle()));
return jobWithSpanWrapper(new CdiAwareJob(instance));
}
// Instantiate a plain job class
return jobWithSpanWrapper(super.newJob(bundle, Scheduler));
Expand Down

0 comments on commit a458734

Please sign in to comment.