How to prevent spring batch to start new instance of job? - spring-batch

Idea is not to start job if already same job is running.
JobExplorer is simple injected in class where is scheduled method for running
public class JobClass {
private final Job job;
private final JobExplorer jobExplorer;
private final JobLauncher jobLauncher;
public JobMain(Job job,
JobLauncher jobLauncher,
JobExplorer jobExplorer) {
this.job = job;
this.jobLauncher = jobLauncher;
this.jobExplorer = jobExplorer;
}
and then it is executed
#Scheduled("0 */5 * ? * *")
public void startJob() {
JobParameters jobParameters = new JobParametersBuilder()
.addString("jobName", String.valueOf(instant.toEpochMilli()))
.toJobParameters();
jobLauncher.run(job, jobParameters);
}
This is not solution because if JVM stopped while job is running this will be same as current job running:
jobExplorer.findRunningJobExecutions("jobName")
It will find all jobs with exitCode ExitStatus.UNKNOWN.
There is 3 solutions as I see it:
Solution 1:
stop previous running not finished jobs and run new job
PROS: everything is clean, just one property
CONT: loosing current execution of current job
#Scheduled("0 */5 * ? * *")
public void startJob() {
(JobExecution jobExecution: jobExplorer.findRunningJobExecutions("jobName")) jobExecution.stop();
...
}
Solution 2
Calculate time between latest running job like it is here described and if it is any do not start new job:
https://stackoverflow.com/a/23218986/1182625
PROS: everything is clean
CONT: have to have doubled property (5 *60*1000 and "0 */5 * ? * *")
Set<JobExecution> jobExecutions = jobExplorer.findRunningJobExecutions("jobName");
if(jobExecutions.size()>1){
Long currentTime = (new Date()).getTime();
for(JobExecution execution : jobExecutions) {
if((currentTime - execution.getStartTime().getTime()) < 5*60*1000) {
return;
} else {
execution.stop();
}
}
}
Solution 3
Idea is simple to add static (to share between instances of class) volatile (to share between threads) flag which will indicate is any job currently running
PROS: just one property
CONT: needs 2 listeners, and volatile static variable which i don't know how reacted in multi-nodes environment
private static volatile boolean FINISHED = true;
and then simple add listener and FINISHED modify method:
// reset FINISHED after job is done
#AfterJob
public void afterJob() {
FINISHED = true;
}
public void setFinished() {
this.FINISHED = true;
}
And simple add:
#Scheduled("0 */5 * ? * *")
public void startJob() {
if(!FINISHED) return;
FINISHED = false;
...
}
And finally add StepListener
public MyStepListener() {
...
#AfterStep
public ExitStatus afterStep(StepExecution stepExecution) {
if(stepExecution.getExitStatus().getExitCode().equalsIgnoreCase(ExitStatus.FAILED.getExitCode())) (new JobMain()).setFinished();
return null;
}

Ok, I think I go to far with something could be KISS.
Keep It Simple & Stupid.
So, to achieve this is simple to put fixedDelay or fixedStringDelay in #Scheduled annotation if you want to use value from properties file.
#Scheduled(initialDelay = 3*60*1000, fixedDelayString ="${job.fixed_delay}")
With this I achieve that I don't have more than 1 instance of same job at same time.
I only lose that job start at exactly time (like ad midnight or...)

Idea is not to start job if already same job is running.
By design, Spring Batch will prevent that. If you try to start the same job instance while it has a running job execution, you will get a JobExecutionAlreadyRunningException.

Related

How to stop or cancel a running scheduled task?

I have a Task Scheduler running in my Micronaut Application as described here. What I want is an option to shutdown this task if I receive some kind of request from the user. How can I do that?
In order to cancel a scheduled task you need to work directly with the TaskScheduler.
The following example explains cancelling of a job.
#Singleton
public class SomeBeanThatDoesScheduling {
private final TaskScheduler taskScheduler;
private ScheduledFuture<?> scheduledFuture;
public SomeBeanThatDoesScheduling(#Named(TaskExecutors.SCHEDULED) TaskScheduler taskScheduler) {
this.taskScheduler = taskScheduler;
}
// on application startup you can register your scheduled task
#EventListener
public void onStartup(StartupEvent startupEvent) {
scheduledFuture = taskScheduler.scheduleWithFixedDelay(initialDelay, interval, this::execute);
}
public void execute() {
System.out.println("The task has been executed");
}
// use this method to cancel the job
public void cancelTheJob() {
if (this.scheduledFuture != null) {
this.scheduledFuture.cancel(false);
}
}
}

trigger is firing only once for duplicate cron expressions

When I try to schedule two different job with the same cron expression(0 * * * * ?) which is every minute, only one trigger is being fired. I almost tried everything but nothing worked. I also passed the parameter spring.quartz.properties.org.quartz.scheduler.batchTriggerAcquisitionMaxCount=10 but it didn't worked. At least one trigger is firing in a minute and sometimes two, but never two triggers fired in a minute continuously. Am I doing somethig wrong?
edit: I checked on db that Next_Fire_Time is being updated but not being triggered even not misfired or vetoed as I can see from my Trigger Listener.
public class GlobalTriggerListener implements TriggerListener {
private static final String TRIGGER_LISTENER_NAME = "GlobalTriggerListener";
#Override
public String getName() {
return TRIGGER_LISTENER_NAME;
}
#Override
public void triggerFired(Trigger trigger, JobExecutionContext context) {
String triggerName = context.getJobDetail().getKey().toString();
System.out.println("triggerFired");
System.out.println("trigger : " + triggerName + " is fired");
}
#Override
public boolean vetoJobExecution(Trigger trigger, JobExecutionContext context) {
boolean veto = false;
System.out.println("Veto Job Excecution trigger: " + veto);
return veto;
}
#Override
public void triggerMisfired(Trigger trigger) {
System.out.println(getName() + " trigger: " + trigger.getKey() + " misfired at " + trigger.getStartTime());
}
#Override
public void triggerComplete(Trigger trigger, JobExecutionContext context, Trigger.CompletedExecutionInstruction triggerInstructionCode) {
System.out.println(getName() + " trigger: " + trigger.getKey() + " completed at " + trigger.getStartTime());
}
}
private JobDetail buildJobDetail(ScheduledProcess sProcess) {
JobDataMap jobDataMap = new JobDataMap();
jobDataMap.put(JOB_DATA_KEY, sProcess.getSpid());
return JobBuilder.newJob(ProcessStarterJob.class)
.withIdentity(UUID.randomUUID().toString(), "scheduled-processes")
.withDescription("Start Process Job")
.usingJobData(jobDataMap)
.storeDurably()
.build();
}
private Trigger buildJobTrigger(JobDetail jobDetail, ScheduledProcess sProcess) throws ParseException {
SimpleDateFormat formatter = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
Date startDate = formatter.parse(sProcess.getStartDate());
Date endDate = formatter.parse(sProcess.getEndDate());
return TriggerBuilder.newTrigger()
.withIdentity(jobDetail.getKey().getName(), "scheduled-processes-triggers")
.withDescription("Start Process Trigger")
.startAt(startDate)
.endAt(endDate)
.withSchedule(CronScheduleBuilder.cronSchedule(sProcess.getQuartzDef()).withMisfireHandlingInstructionDoNothing())
.build();
}
Funny but true. I think this the third time I am the victim of Eclipse. Although I couldn't see any instance running on eclipse there was always a stale running instance of my quartz app which I saw it on Task Manager by chance. However I even debugged the quartz source code :)
So if you face with the same problem I have, first of all be sure that there is no running any other instance of your app anywhere, this can also be another quartz app that may be using your quartz db. When I start to be suspicious about if there is any other running instance was the moment I saw that Trigger NEXT_FIRE_TIME was being updated as it has to be but I wasn't seeing any fire on my Trigger Listener.
Hope these help you to identify you problems.

Scheduling multiple jobs with dynamic parameter using Quartz 1.6.2 (simple trigger) + Spring 3.0

I am trying to create a sample scheduler program using quartz simple trigger. My objective is run a single method with different parameters passed dynamically by user (considered as new Job)and schedule it for user defined time which is also received dynamically.
eg:
public void printMe (String name, int Age, Date dob) {
system.out.println("Name:"+name+" Age:"+age+" DOB:"+dob);
}
How to schedule and execute this method based on dynamically received parameters and start date and time using quartz 1.6.2. I am new to quartz scheduler, I have no idea how to do this. Do anyone help me with the sample programs you have?
Here is example to use quartz job via simple trigger
Simple Trigger 1 minutes with 1 second delay for every run to execute:
public static void main(String[] args) throws Exception{
JobDetail job = new JobDetail();
job.setName("dummyJobName");
job.setJobClass(HelloJob.class);
//configure the scheduler time
SimpleTrigger trigger = new SimpleTrigger();
trigger.setStartTime(new Date(System.currentTimeMillis() + 1000));
trigger.setRepeatCount(SimpleTrigger.REPEAT_INDEFINITELY);
trigger.setRepeatInterval(100000);// set time run again is 1 minutes
//schedule it
Scheduler scheduler = new StdSchedulerFactory().getScheduler();
scheduler.start();
scheduler.scheduleJob(job, trigger);
}
You create class for execute class job
import org.quartz.Job;
import org.quartz.JobExecutionContext;
import org.quartz.JobExecutionException;
public class HelloJob implements Job
{
public void execute(JobExecutionContext context)
throws JobExecutionException {
printmMe("YourName",20,"12/15/2013");
}
public void printMe (String name, int Age, Date dob) {
system.out.println("Name:"+name+" Age:"+age+" DOB:"+dob);
}
}
Above, that's program use quartz schedule to execute job HellloClass

How to persist JobDataMap changes from remote client

I'm working on a basic web client for Quartz.NET that among other things supports the modification of a job's JobDataMap at runtime.
My job is decorated with the following attributes which I believe is all that is necessary to make the job stateful:
[PersistJobDataAfterExecution]
[DisallowConcurrentExecution]
public class SampleTenantJob : IJob { ... }
At runtime, I execute the following code but the JobDataMap is not persisted:
public void UpdateJobProperties(string jobName, string groupName, IDictionary<string, object> properties)
{
IJobDetail job;
if (!TryGetJob(jobName, groupName, out job))
return;
foreach (var property in properties)
{
if (job.JobDataMap.ContainsKey(property.Key) && property.Value != null)
{
job.JobDataMap.Put(property.Key, property.Value);
}
}
}
I thought initially this was because I was using the XMLSchedulingDataProcessorPlugin for jobs but I've tried both the in memory (RAMStore) and AdoJobStore and still can not persist JobDataMap changes made by the remote client.
PersistJobDataAfterExecution (as the name implies) only applies when the job has finished executing, so the following job will track the number of times it is executed
[PersistJobDataAfterExecution]
public class HelloJob : IJob
{
public void Execute(IJobExecutionContext context)
{
int count = (int?) context.MergedJobDataMap["Count"] ?? 1;
Console.WriteLine("hello " + count.ToString() );
context.JobDetail.JobDataMap.Put("Count" , ++count);
}
}
Without the PersistJobDataAfterExecution attributes, count is always the same.
Since you aren't running the job, this doesn't help you, and I think you have to delete and re-create the job with the new JobDataMap.
Of course, you aren't forced to use JobDataMap and can always to read and store information for your job from somewhere else.

Is it possible to kill a current running Quartz Job?

I remember that we cannot kill the current running Quartz Job but we can interrupt and have a boolean check wherever is necessary whether we need to proceed further with the subsequent operations or not.
Even when we implement the InterruptableJob and call the scheduler.interrupt to interrupt the Job, the current executed job will be still running in the server.
Ex:
A named SQL query has been triggered by the job via Hibernate which takes a long time
A call has been made to a third party server where the third party server takes a long time to respond
http://neopatel.blogspot.in/2011/05/quartz-stop-job.html
http://forums.terracotta.org/forums/posts/list/3191.page
Could someone corrects my understanding and explain me how we can kill or stop the "currently" executing Job ?
you can create new abstract class called JobBase for example that implements IJob interface and insert abstract method:
public abstract void ExecuteJob(IJobExecutionContext context);
On JobBase you can implements method Execute like this
public abstract class JobBase : IJob,IInterruptableJob
{
private Thread currentThread;
private ILog logger;
public JobBase(ILog logger)
{
this.logger=logger;
}
public void Execute(IJobExecutionContext context)
{
var thread = new Thread(()=>
{
try
{
this.ExecuteJob(context);
}
catch(Exception ex)
{
this.logger.ErrorFormat("Unhandled exception {0}",ex.ToString());
}
});
thread.Start();
this.currentThread = thread;
this.currentThread.Join();
}
public abstract void ExecuteJob(IJobExecutionContext context);
public void Interrupt()
{
currentThread.Abort();
}
}
Each Job will implements JobExecute method.
public class TestJob :JobBase
{
private ILog logger;
public TeJob(ILog logger):base(logger)
{
}
public override ExecuteJob(IJobExecutionContext context)
{
}
}
Assumes that use some factory for creating a Job
For Stopping a Job you will call method scheduler.Interrupt(new JobKey(jobName));
As you told, there is no way to interrupt "brutally" a job in quartz, neither in JAVA.
You can encapsulate your job's logic in a separate Thread and run it with the ExecutorService.
Take a look to this example: https://stackoverflow.com/a/2275596/1517816
Assume your QuartzJob is the Test class and move your business logic in the Task class.
Hope it helps
I don't know why nobody mentioned this, or maybe this was not available at the time the question was asked.
There is a method called shutdown for a Scheduler instance.
SchedulerFactory factory = new StdSchedulerFactor();
Scheduler scheduler = factory.getScheduler();
The above is used to start a job like
scheduler.start();
Use a flag or something to know when to stop the job from running. Then use
scheduler.shutdown();
How I implemented my requirement:
if(flag==true)
{
scheduler.start();
scheduler.scheduleJob(jobDetail, simpleTrigger);
}
else if(flag==false)
{
scheduler.shutdown();
}
Where jobDetail and simpleTrigger are self explanatory.
Hope it helps. :)