quartz: preventing concurrent instances of a job in jobs.xml
This should be really easy. I'm using Quartz running under Apache Tomcat 6.0.18, and I have a jobs.xml file which sets up my scheduled job that runs every minute.
What I would like to do, is if the job is still running when the next trigger time rolls around, I don't want to start a new job, so I can let the old instance complete.
Is there a way to specify this in jobs.xml (prevent concurrent instances)?
If not, is there a way I can share access to an in-memory singleton within my application's Job implementation (is this through the JobExecutionContext?) so I can handle the concurrency myself? (and detect if a previous instance is running)
update: After floundering around in the docs, here's a couple of approaches I am considering, but either don't know how to get them to work, or there are problems.
Use StatefulJob. This prevents concurrent access... but I'm not sure what other side-effects would occur if I use it, also I want to avoid the following situation:
Suppose trigger times would be every minute, i.e. trigger#0 = at time 0, trigger #1 = 60000msec, #2 = 120000, #3 = 180000, etc. and the trigger#0 at time 0 fires my job which takes 130000msec. With a plain Job, this would execute triggers #1 and #2 while job trigger #0 is still running. With a StatefulJob, this would execute triggers #1 and #2 in order, immediately after #0 finishes at 130000. I don't want that, I want #1 and #2 not to run and the next trigger that runs a job should take place at #3 (180000msec). So I still have to do something else with StatefulJob to get it to work the way I want, so I don't see much of an advantage to using it.
Use a TriggerListener to return true from vetoJobExecution().
Although implementing the interface seems straightforward, I have to figure out how to setup one instance of a TriggerListener declaratively. Can't find the docs for the xml file.
Use a
static
shared thread-safe object (e.g. a semaphore or whatever) owned by my class that implements Job.I don't like the idea of using singletons via the
static
keyword under Tomcat/Quartz, not sure if there are side effects. Also I really don't want them to be true singletons, just something that is associated with a particular job definition.Implement my own Trigger which extends SimpleTrigger and contains shared state that could run its own TriggerListener.
Again, I don't know how to setup the XML开发者_运维技巧 file to use this trigger rather than the standard
<trigger><simple>...</simple></trigger>
.
There is another simpler solution. The job can be given an annotation of DisallowConcurrentExecution which prevents multiple concurrent instances running. See docs here.
The link keeps breaking so here is the relevant sample.
@DisallowConcurrentExecution
public class ColorJob implements Job {
dimitrisli's answer is not complete so here's mine.
When Quartz Job wakes up it returns its JobExecutionContext to you. I assume you want to skip jobs with the same trigger.
List<JobExecutionContext> jobs = jobExecutionContext.getScheduler().getCurrentlyExecutingJobs();
for (JobExecutionContext job : jobs) {
if (job.getTrigger().equals(jobExecutionContext.getTrigger()) && !job.getJobInstance().equals(this)) {
logger.info("There's another instance running, so leaving" + this);
return;
}
}
We get currently job contexts and check if there's a previous job instance with the same trigger. If this is the case we just skip with return.
when your Quartz job wakes up you can do:
JobDetail existingJobDetail = sched.getJobDetail(jobName, jobGroup);
if (existingJobDetail != null) {
List<JobExecutionContext> currentlyExecutingJobs = (List<JobExecutionContext>) sched.getCurrentlyExecutingJobs();
for (JobExecutionContext jec : currentlyExecutingJobs) {
if (existingJobDetail.equals(jec.getJobDetail())) {
// String message = jobName + " is already running.";
// log.info(message);
// throw new JobExecutionException(message,false);
}
}
// sched.deleteJob(jobName, jobGroup); if you want to delete the scheduled but not-currently-running job
}
I accomplished something similar making my job classes implement StatefulJob, which ensures that no other jobs starts before the current running job finishes.
Hope that helps ;)
PD: I implemented it using JBoss... but I don't think that makes any difference.
could you set the job as a StatefulJob, and for each trigger you create set the MisfireInstruction for the job to not fire if it is missed? Not sure what type of job you are using but you'll have to do some research into the misfireInstructions that are available for your trigger type.
Thanks, D
If you are using org.springframework.scheduling.quartz.QuartzJobBean
:
protected void executeInternal(JobExecutionContext context) throws JobExecutionException {
try {
Scheduler scheduler = context.getScheduler();
List<JobExecutionContext> jobs = scheduler.getCurrentlyExecutingJobs();
for (JobExecutionContext job : jobs) {
if (job.getTrigger().equals(context.getTrigger()) && job.getJobDetail() != context.getJobDetail()) {
LOG.warn("Ignored!");
return;
}
}
...
} catch (SchedulerException e) {
LOG.error("What a luck! :'(", e);
}
...
}
Slight variation to scaramouche's solution.
List<JobExecutionContext> jobs = jobExecutionContext.getScheduler().getCurrentlyExecutingJobs();
for (JobExecutionContext job : jobs) {
if (job.getTrigger().equals(jobExecutionContext.getTrigger()) && !job.getFireInstanceId().equals(jobExecutionContext.getFireInstanceId()) {
logger.info("There's another instance running, so leaving" + this);
return;
}
}
scaramouche's solution fails when there is a single instance used for all JobExecutions( returning the singleton using a custom JobFactory class, rather than calling newInstance() for each execution)
精彩评论