Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

In Quartz.NET is there a way to set a property that will only allow one instance of a Job to run?

I have a service that will run every X minutes. If that job takes longer than X minutes for some unforeseen reason I want to make sure that the trigger doesn't kick off a second instance of this job.

Sample Scenario

  • I have Job X, picks up files and is triggered by Quartz every 1 minute.
  • Job X can typically process 100 files in 1 minute, anything over 100 files will take longer than 1 minute.
  • Since the last run time, 150 files happen to be out there so Job X kicks off and begins processing. When 1 minute is reached 100 files were processed, 50 files remain, and Job X continues to run.
  • However a second instance of Job X is kicked off because the trigger fires every 1 minute.
  • I now have 2 instances of Job X picking up the same 50 files.

Is there a way to wire up Quartz.NET to only allow 1 instance of a Job? I'm OK with the second trigger waiting for the first to complete or I'm also OK with an option for it to skip the second trigger since it will be triggered again in a minute.



I took a look at the Java version of Quartz API and found a property 'DisallowConcurrentExecution' but didn't find one similar in the .NET version.

My Code for the Quartz.NET Implementation

public IScheduler Scheduler { get; set; }
public IJobListener AutofacJobListener { get; set; }

public void Start()
{
var trigger = TriggerUtils.MakeMinutelyTrigger(1);
trigger.Name = @"Document Import Trigger";

Scheduler.ScheduleJob(new JobDetail("Document Import", null, typeof(DocumentImportJob)), trigger);
Scheduler.AddGlobalJobListener(AutofacJobListener);
Scheduler.Start();
}
like image 429
Mark Avatar asked May 12 '11 20:05

Mark


3 Answers

Use the DisallowConcurrentExecution attribute.

Declare your class as follows:

[DisallowConcurrentExecution]
public class SomeTask : IJob
{

} 

Misfires

"Misfire Instructions Another important property of a Trigger is its "misfire instruction". A misfire occurs if a persistent trigger "misses" its firing time because of the scheduler being shutdown, or because there are no available threads in Quartz.NET's thread pool for executing the job. The different trigger types have different misfire instructions available to them. By default they use a 'smart policy' instruction - which has dynamic behavior based on trigger type and configuration. When the scheduler starts, it searches for any persistent triggers that have misfired, and it then updates each of them based on their individually configured misfire instructions. When you start using Quartz.NET in your own projects, you should make yourself familiar with the misfire instructions that are defined on the given trigger types, and explained in their API documentation. More specific information about misfire instructions will be given within the tutorial lessons specific to each trigger type."

Check out the "trigger misfire instructions" information at the bottom of these pages:

Lesson 5: SimpleTrigger

Lesson 6: CronTrigger

Old Quartz.NET API answer:

http://quartznet.sourceforge.net/apidoc/topic142.html:

IStatefulJob instances follow slightly different rules from regular IJob instances. The key difference is that their associated JobDataMap is re-persisted after every execution of the job, thus preserving state for the next execution. The other difference is that stateful jobs are not allowed to Execute concurrently, which means new triggers that occur before the completion of the IJob.Execute method will be delayed.

So, declare your 'Job' class as follows:

class DocumentImportJob : IStatefulJob
{
   ......
} 

To avoid delayed tasks re-firing immediately after job completes (when the job takes more than 1 minute and causes a trigger 'misfire'), do the following when creating your trigger(s) (adjust depending on the trigger type used):

myJobTrigger.MisfireInstruction = MisfireInstruction.CronTrigger.DoNothing;  

https://www.quartz-scheduler.net/documentation/quartz-2.x/tutorial/more-about-triggers.html

like image 73
Keith Blows Avatar answered Nov 01 '22 08:11

Keith Blows


As an update to this answer, in newer versions of Quartz.Net this is now done via an attribute "DisallowConcurrentExecution" you apply to your job implementation:

[DisallowConcurrentExecution]
public class MyJob : IJob  
{
    ..
}

And for the misfire instruction, here is how to do that:

var trigger = TriggerBuilder.Create()
    .WithSimpleSchedule(ssb => ssb.WithIntervalInMinutes(interval)
        .RepeatForever()
        .WithMisfireHandlingInstructionIgnoreMisfires()
    )
    .Build();
like image 46
Bittercoder Avatar answered Nov 01 '22 08:11

Bittercoder


Well, one simple way to do it would be to just store a flag value in a variable somewhere, and check the variable upon entrance to the job method.

That way you would just let the job "start" a second time, it would just exit immediately without doing any real work.

Here's an example:

private volatile bool _IsRunning;

...

if (Interlocked.Exchange(ref _IsRunning, true))
    return;
try
{
    // job code
}
finally
{
    _IsRunning = false;
}
like image 4
Lasse V. Karlsen Avatar answered Nov 01 '22 08:11

Lasse V. Karlsen