public static void sendEmailForNewTrigger(String jobName, String triggerName, Date sendDate) {
try {
Scheduler sched = QuartzSchedulerManager.getInstanceScheduler();
JobKey jobKey = JobKey.jobKey(jobName, "group_email");
JobDetail job = sched.getJobDetail(jobKey);
SimpleTrigger trigger = (SimpleTrigger) newTrigger().withIdentity(triggerName)
.startAt(sendDate)
.build();
if (job == null) {
job = newJob(SendEmailJob.class).withIdentity(jobName, "group_email").build();
}
sched.scheduleJob(job, trigger);
log.info(jobName + " will run at: " + sendDate);
} catch (SchedulerException e) {
log.error(e.toString());
throw new RuntimeException(e.getMessage());
}
}
in my code ,it can't work, always tell me 'Unable to store Job : 'group_email.send_1', because one already exists with this identification.' i don't know how to do ,just want to add two or more trigger to one job.
Running only on job instance at a time this ok : Spring batch restrict single instance of job only
public class jobMailListener implements JobExecutionListener {
// active JobExecution, used as a lock.
private JobExecution _active;
public void beforeJob(JobExecution jobExecution) {
// create a lock
synchronized (jobExecution) {
if (_active != null && _active.isRunning()) {
//***************************//
// Here create/storage in queue it up ?
//****************************//
jobExecution.stop();
} else {
_active = jobExecution;
}
}
}
public void afterJob(JobExecution jobExecution) {
// release the lock
synchronized (jobExecution) {
if (jobExecution == _active) {
_active = null;
}
}
}
}
<batch:job id="envoiMail" restartable="true">
<batch:listeners><batch:listener ref="jobMailListener"/>
<batch:step id="prepareData">...
I would not stop the jobs but create a queue.
Can be used spring integration ?
I thought http://incomplete-code.blogspot.fr/2013/03/spring-batch-running-only-one-job.html#comment-form
But it is not functional.
I'm trying to use Quartz in order to schedule jobs in a web app running on Glassfish. I'm using RAMJobStore. The problem is that sometimes, the job that was scheduled isn't being executed, even if it was scheduled in the past or the future. The amount of jobs are extremely low, a total of under 20 jobs scheduled at all times on the scheduler and a guaranteed maximum of 1 job running at the same time, so I presume the thread count is not an issue, I could set it to threadCount 1 and it would still work. The scheduler is also not being shut down before the servlet is being destroyed. So what can be the cause for some jobs not being run ?
StartupServlet
public void init()
{
try
{
scheduler = StdSchedulerFactory.getDefaultScheduler();
scheduler.start();
loadJobs();
}
catch (SchedulerException se)
{
se.printStackTrace();
}
}
#Override
public void destroy()
{
try
{
scheduler.shutdown();
}
catch (SchedulerException se)
{
se.printStackTrace();
}
}
Scheduling a job
JobDetail job = JobBuilder.newJob(ScheduledTransactionJob.class)
.withIdentity(transaction.getId())
.build();
Trigger trigger = TriggerBuilder.newTrigger()
.withIdentity(transaction.getId())
.startAt(date)
.build();
try
{
scheduler.scheduleJob(job, trigger);
dateFormat = new SimpleDateFormat("dd MMM yyyy, HH:mm:ss");
String recurringTransactionTime = dateFormat.format(date);
logger.info("Scheduled job for " + recurringTransactionTime);
}
catch (SchedulerException se)
{
se.printStackTrace();
}
quartz.properties
#============================================================================
# Configure Main Scheduler Properties
#============================================================================
org.quartz.scheduler.skipUpdateCheck = true
org.quartz.scheduler.instanceName = AppScheduler
org.quartz.scheduler.instanceId = AUTO
#============================================================================
# Configure ThreadPool
#============================================================================
org.quartz.threadPool.class = org.quartz.simpl.SimpleThreadPool
org.quartz.threadPool.threadCount = 25
org.quartz.threadPool.threadPriority = 10
#============================================================================
# Configure JobStore
#============================================================================
org.quartz.jobStore.misfireThreshold = 60000
org.quartz.jobStore.class = org.quartz.simpl.RAMJobStore
Seems to be working now. Haven't ran into any more problems. Could've been a config issue, as I have moved the config file in /src/main/resources.
Also try turning logging on in order to help with the debug:
log4j.logger.com.gargoylesoftware.htmlunit=DEBUG
We also added a JobTriggerListener to help with the logs:
private static class JobTriggerListener implements TriggerListener
{
private String name;
public JobTriggerListener(String name)
{
this.name = name;
}
public String getName()
{
return name;
}
public void triggerComplete(Trigger trigger, JobExecutionContext context,
Trigger.CompletedExecutionInstruction triggerInstructionCode)
{
}
public void triggerFired(Trigger trigger, JobExecutionContext context)
{
}
public void triggerMisfired(Trigger trigger)
{
logger.warn("Trigger misfired for trigger: " + trigger.getKey());
try
{
logger.info("Available threads: " + scheduler.getCurrentlyExecutingJobs());
}
catch (SchedulerException ex)
{
logger.error("Could not get currently executing jobs.", ex);
}
}
public boolean vetoJobExecution(Trigger trigger, JobExecutionContext context)
{
return false;
}
}
I have a client server application and I'm using rxjava to do server requests from the client. The client should only do one request at a time so I intent to use a thread queue scheduler similar to the trampoline scheduler.
Now I try to implement a mechanism to watch changes on the server. Therefore I send a long living request that blocks until the server has some changes and sends back the result (long pull).
This long pull request should only run when the job queue is idle. I'm looking for a way to automatically stop the watch request when a regular request is scheduled and start it again when the queue becomes empty. I thought about modifying the trampoline scheduler to get this behavior but I have the feeling that this is a common problem and there might be an easier solution?
You can hold onto the Subscription returned by scheduling the long poll task, unsubscribe it if the queue becomes non-empty and re-schedule if the queue becomes empty.
Edit: here is an example with the basic ExecutorScheduler:
import java.util.concurrent.*;
import java.util.concurrent.atomic.*;
public class IdleScheduling {
static final class TaskQueue {
final ExecutorService executor;
final AtomicReference<Future<?>> idleFuture;
final Runnable idleRunnable;
final AtomicInteger wip;
public TaskQueue(Runnable idleRunnable) {
this.executor = Executors.newFixedThreadPool(1);
this.idleRunnable = idleRunnable;
this.idleFuture = new AtomicReference<>();
this.wip = new AtomicInteger();
this.idleFuture.set(executor.submit(idleRunnable));
}
public void shutdownNow() {
executor.shutdownNow();
}
public Future<?> enqueue(Runnable task) {
if (wip.getAndIncrement() == 0) {
idleFuture.get().cancel(true);
}
return executor.submit(() -> {
task.run();
if (wip.decrementAndGet() == 0) {
startIdle();
}
});
}
void startIdle() {
idleFuture.set(executor.submit(idleRunnable));
}
}
public static void main(String[] args) throws Exception {
TaskQueue tq = new TaskQueue(() -> {
while (!Thread.currentThread().isInterrupted()) {
try {
Thread.sleep(1000);
} catch (InterruptedException ex) {
System.out.println("Idle interrupted...");
return;
}
System.out.println("Idle...");
}
});
try {
Thread.sleep(1500);
tq.enqueue(() -> System.out.println("Work 1"));
Thread.sleep(500);
tq.enqueue(() -> {
System.out.println("Work 2");
try {
Thread.sleep(500);
} catch (InterruptedException ex) {
}
});
tq.enqueue(() -> System.out.println("Work 3"));
Thread.sleep(1500);
} finally {
tq.shutdownNow();
}
}
}
using Quartz, I'd like few jobs (say about 10) to execute as a chain - i.e. NOT concurrently.
They should be executed after an "accounting day change" event occur but since they all access the same DB, I dont want them to start all together. I want them to be executed sequentially instead (order doesnt matter).
I have an idea to put them into a group - say "account_day_change_jobs" and configure Quartz somehow to do the rest for me :-) Means - run sequentially all jobs from the group. I tried the API doc (both 1.8 and 2.1), tried google but didnt find anything.
Is it possible? Is it even reasonable? Other ideas how to achieve the behavior I want?
Thanks very much for any ideas :-)
Hans
The Trigger Listener class below should re-schedule any jobs that attempt to execute while another job that the listener has been configured for is running.
Ive only lightly tested it but for simple cases it should be suitable.
public class SequentialTriggerListener extends TriggerListenerSupport {
private JobKey activeJob;
private Scheduler activeScheduler;
private Queue<JobDetail> queuedJobs = new ConcurrentLinkedQueue<JobDetail>();
public String getName() {
return "SequentialTriggerListener";
}
public boolean vetoJobExecution(Trigger trigger, JobExecutionContext context) {
synchronized (this) {
if (activeJob != null) {
getLog().debug("Queueing Sequential Job - " + context.getJobDetail().getKey().getName());
JobDetail jd = context.getJobDetail();
activeScheduler = context.getScheduler();
jd = JobBuilder.newJob().usingJobData(jd.getJobDataMap()).withIdentity(getName() + ":" + jd.getKey().getName(), jd.getKey().getGroup())
.ofType(jd.getJobClass()).build();
queuedJobs.add(jd);
return true;
} else {
activeJob = trigger.getJobKey();
getLog().debug("Executing Job - " + activeJob.getName());
return false;
}
}
}
public void triggerMisfired(Trigger trigger) {
triggerFinalized(trigger);
}
public void triggerComplete(Trigger trigger, JobExecutionContext context, CompletedExecutionInstruction triggerInstructionCode) {
triggerFinalized(trigger);
}
protected void triggerFinalized(Trigger trigger) {
synchronized (this) {
try {
if (trigger.getJobKey().equals(activeJob)) {
getLog().debug("Finalized Sequential Job - " + activeJob.getName());
activeJob = null;
JobDetail jd = queuedJobs.poll();
if (jd != null) {
getLog().debug("Triggering Sequential Job - " + jd.getKey().getName());
activeScheduler.scheduleJob(jd,TriggerBuilder.newTrigger().forJob(jd).withIdentity("trigger:" + jd.getKey().getName(), jd.getKey().getGroup())
.startNow().withSchedule(SimpleScheduleBuilder.simpleSchedule().withRepeatCount(0).withIntervalInMilliseconds(1)).build());
}
} else {
// this should not occur as the trigger finalizing should be the one we are tracking.
getLog().warn("Sequential Trigger Listener execution order failer");
}
} catch (SchedulerException ex) {
getLog().warn("Sequential Trigger Listener failure", ex);
}
}
}
}
its has been a long time since I used quartz, however I would try two job listeners registered to listen to two different groups
the basic idea is to have one job fire from a group / list ("todayGroup"), the '("todayGroup") job listener detects the completion for good or bad. then kicks off the next job in the list. However, it saves the 'just finished' job back in the scheduler under the ("tomorrowGroup").
public class MyTodayGroupListener extends JobListenerSupport {
private String name;
private static String GROUP_NAME = "todayGroup";
public MyOtherJobListener(String name) {
this.name = name;
}
public String getName() {
return name;
}
#Override
public void jobWasExecuted(JobExecutionContext context,
JobExecutionException jobException) {
Scheduler sched = context.getScheduler();
// switch the job to the other group so we don't run it again today.
JobDetail current = context.getJobDetail();
JobDetail tomorrows = current.getJobBuilder().withIdentity(current.getKey().getName(), "tomorrow").build();
sched.addJob(tomorrows,true);
//see if there is anything left to run
Set<JobKey> jobKeys = sched.getJobKeys(groupEquals(GROUP_NAME ));
Iterator<JobKey> nextJob = null;
if(jobKeys != null && !jobKeys.isEmpty() ){
nextJob = jobKeys.iterator();
}
if(nextJob != null){
// Define a Trigger that will fire "now" and associate it with the first job from the list
Trigger trigger = newTrigger()
.withIdentity("trigger1", "group1")
.startNow()
.forJob(nextJob =.next())
.build();
// Schedule the trigger
sched.scheduleJob(trigger);
}
}
}
likewise, you'll need two 'group triggers' that will fire the first job from their respective groups at the given time you want.
public class TriggerGroupDisallowConcurrentExecutionTriggerListener : ITriggerListener
{
private IScheduler activeScheduler;
private readonly object locker = new object();
private ConcurrentDictionary<string, JobsQueueInfo> groupsDictionary = new ConcurrentDictionary<string, JobsQueueInfo>();
public string Name => "TriggerGroupDisallowConcurrentExecutionTriggerListener";
public Task TriggerComplete(ITrigger trigger, IJobExecutionContext context, SchedulerInstruction triggerInstructionCode, CancellationToken cancellationToken = default)
{
//JobKey key = context.JobDetail.Key;
//Console.WriteLine($"{DateTime.Now}: TriggerComplete. {key.Name} - {key.Group} - {trigger.Key.Name}");
TriggerFinished(trigger, cancellationToken);
return Task.CompletedTask;
}
public Task TriggerFired(ITrigger trigger, IJobExecutionContext context, CancellationToken cancellationToken = default)
{
//JobKey key = context.JobDetail.Key;
//Console.WriteLine($"{DateTime.Now}: TriggerFired. {key.Name} - {key.Group} - {trigger.Key.Name}");
return Task.CompletedTask;
}
public Task TriggerMisfired(ITrigger trigger, CancellationToken cancellationToken = default)
{
//JobKey key = trigger.JobKey;
//Console.WriteLine($"{DateTime.Now}: TriggerMisfired. {key.Name} - {key.Group} - {trigger.Key.Name}");
TriggerFinished(trigger, cancellationToken);
return Task.CompletedTask;
}
public Task<bool> VetoJobExecution(ITrigger trigger, IJobExecutionContext context, CancellationToken cancellationToken = default)
{
//JobKey key = context.JobDetail.Key;
//Console.WriteLine($"{DateTime.Now}: VetoJobExecution. {key.Name} - {key.Group} - {trigger.Key.Name}");
lock (locker)
{
//if (!groupsDictionary.ContainsKey(context.JobDetail.Key.Group))
//{
groupsDictionary.TryAdd(context.JobDetail.Key.Group, new JobsQueueInfo { QueuedJobs = new ConcurrentQueue<IJobDetail>(), ActiveJobKey = null });
var activeJobKey = groupsDictionary[context.JobDetail.Key.Group].ActiveJobKey;
//}
if (activeJobKey != null && activeJobKey != context.JobDetail.Key)
{
var queuedJobs = groupsDictionary[context.JobDetail.Key.Group].QueuedJobs;
if (queuedJobs.Any(jobDetail => jobDetail.Key.Name == context.JobDetail.Key.Name) == true)
{
//NOTE: Джоба уже есть в очереди, нет необходимости её добавлять повторно
return Task.FromResult(true);
}
else
{
//NOTE: Добавить джобу в очередь на выполнение, и не выполнять её сейчас, т.к. она будет выполнена как только подойдёт её очередь
activeScheduler = context.Scheduler;
var newJob = JobBuilder.Create(context.JobDetail.JobType).WithIdentity(context.JobDetail.Key).Build();
queuedJobs.Enqueue(newJob);
return Task.FromResult(true);
}
}
groupsDictionary[context.JobDetail.Key.Group].ActiveJobKey = trigger.JobKey;
return Task.FromResult(false);
}
}
protected void TriggerFinished(ITrigger trigger, CancellationToken cancellationToken = default)
{
lock (locker)
{
try
{
if (!groupsDictionary.ContainsKey(trigger.JobKey.Group))
{
return;
}
var queuedJobs = groupsDictionary[trigger.JobKey.Group].QueuedJobs;
if (queuedJobs.TryDequeue(out IJobDetail jobDetail))
{
//Console.WriteLine($"dequeue - {jobDetail.Key.Name}");
var task = activeScheduler.TriggerJob(jobDetail.Key, cancellationToken);
task.ConfigureAwait(false);
task.Wait(cancellationToken);
groupsDictionary[trigger.JobKey.Group].ActiveJobKey = jobDetail.Key;
}
else
{
groupsDictionary[trigger.JobKey.Group].ActiveJobKey = null;
}
}
catch (SchedulerException ex)
{
throw;
}
}
}
private class JobsQueueInfo
{
public ConcurrentQueue<IJobDetail> QueuedJobs { get; set; }
public JobKey ActiveJobKey { get; set; }
}
}