Understanding the DisallowConcurrentExecution Job Attribute

In this post I’ll go over what the DisallowConcurrentExecution job attribute does and how to use it.

Documentation

The Quartz.net documentation states that the DisallowConcurrentExecutionAttribute is

An attribute that marks a IJob class as one that must not have multiple instances executed concurrently (where instance is based-upon a IJobDetail definition - or in other words based upon a JobKey.
This can be used in lieu of implementing the StatefulJob marker interface that was used prior to Quartz 2.0.

I would like to highlight two particular items that in essence define what this attribute does and how to use it:

  1. It should be applied to the job (a class that implements IJob)
  2. It prevents multiple instances of a job WITH THE SAME KEY from running at the same time.

When to Use It

First let’s discuss when we would use this attribute. Since this attribute will prevent multiple instances of the same job (the actual job instance, not the job type) from running at the same time, you should apply this attribute to any job class for which you want to limit the number of concurrently running instances to 1.

How to Use It

In order to use this attribute, simply apply it to the job class as you would a class level attribute, like this:

[DisallowConcurrentExecutionAttribute]
class DisallowConcurrentJob : IJob
{
//Implementation goes here
}

I’ve already mentioned it a couple of times but I’ll mention it one more time, as it is the most common mistake people make when using this attribute: this attribute only prevents jobs with the same key (name AND group) from running more than one instance concurrently. If the job instances are of the same type (class) but have different keys, you will end up with at most one instance per unique key.

An Example

Let’s walk through an example of a job that uses the DisallowConcurrentExecution attribute. You can download the example source code from github.Once you open the solution file you’ll find a project called DisallowConcurrentExecutionAttributeExample. This is the project we’ll use to highlight how this attribute works.

First, let me explain what the setup for the example is. In this project you’ll find 3 jobs: BaseLongRunningJob, AllowConcurrentJob and DisallowConcurrentJob . The BaseLongRunningJob, as its name suggests, is the base job and it contains all of the functionality we need to illustrate our point.The AllowConcurrentJob inherits from BaseLongRunningJob, as does DisallowConcurrentJob. The difference between these two jobs is that DisallowConcurrentJob has been decorated with the DisallowConcurrentExecution attribute. Below is the source for all 3 classes:

class BaseLongRunningJob : IJob
{
    public void Execute(IJobExecutionContext context)
    {
        _Log.InfoFormat("BEGIN job {0} in group {1}", context.JobDetail.Key.Name, context.JobDetail.Key.Group);
        var runningJobs = string.Join(",", context.Scheduler.GetCurrentlyExecutingJobs().Select(j => j.JobDetail.Key.Name ));
        _Log.InfoFormat("Running jobs: {0}",runningJobs);
        Thread.Sleep(5000);
        _Log.InfoFormat("END job {0} in group {1}", context.JobDetail.Key.Name, context.JobDetail.Key.Group);

    }     private static ILog _Log = LogManager.GetLogger(typeof(BaseLongRunningJob)); }

 

class AllowConcurrentJob : BaseLongRunningJob
{
}

 

[DisallowConcurrentExecutionAttribute]
class DisallowConcurrentJob : BaseLongRunningJob
{

}

Now that we’ve discussed the source code, let’s see it in action. If you run the DisallowConcurrentExecutionAttributeExample, you’ll see output similar to this one:

2014-06-04 11:29:29,033 [7] - Default Quartz.NET properties loaded from embedded resource file
2014-06-04 11:29:29,055 [7] - Using default implementation for object serializer
2014-06-04 11:29:29,071 [7] - Using default implementation for ThreadExecutor
2014-06-04 11:29:29,110 [7] - Initialized Scheduler Signaller of type: Quartz.Core.SchedulerSignalerImpl
2014-06-04 11:29:29,110 [7] - Quartz Scheduler v.2.2.3.400 created.
2014-06-04 11:29:29,112 [7] - RAMJobStore initialized.
2014-06-04 11:29:29,115 [7] - Scheduler meta-data: Quartz Scheduler (v2.2.3.400) 'DefaultQuartzScheduler' with instanceId 'NON_CLUSTERED'
  Scheduler class: 'Quartz.Core.QuartzScheduler' - running locally.
  NOT STARTED.
  Currently in standby mode.
  Number of jobs executed: 0
  Using thread pool 'Quartz.Simpl.SimpleThreadPool' - with 10 threads.
  Using job-store 'Quartz.Simpl.RAMJobStore' - which does not support persistence. and is not clustered.

2014-06-04 11:29:29,115 [7] - Quartz scheduler ‘DefaultQuartzScheduler’ initialized 2014-06-04 11:29:29,115 [7] - Quartz scheduler version: 2.2.3.400 2014-06-04 11:29:29,117 [7] - Scheduler DefaultQuartzScheduler_$_NON_CLUSTERED started. 2014-06-04 11:29:29,144 [DefaultQuartzScheduler_Worker-2] - BEGIN job AllowConcurrentJob in group AllowConcurrentJobGroup 2014-06-04 11:29:29,144 [DefaultQuartzScheduler_Worker-1] - BEGIN job DisallowConcurrentJob in group DisallowConcurrentJobGroup 2014-06-04 11:29:29,145 [DefaultQuartzScheduler_Worker-1] - Running jobs: AllowConcurrentJob,DisallowConcurrentJob 2014-06-04 11:29:29,145 [DefaultQuartzScheduler_Worker-2] - Running jobs: AllowConcurrentJob,DisallowConcurrentJob 2014-06-04 11:29:30,127 [DefaultQuartzScheduler_Worker-3] - BEGIN job AllowConcurrentJob in group AllowConcurrentJobGroup 2014-06-04 11:29:30,127 [DefaultQuartzScheduler_Worker-3] - Running jobs: AllowConcurrentJob,DisallowConcurrentJob,AllowConcurrentJob 2014-06-04 11:29:31,128 [DefaultQuartzScheduler_Worker-4] - BEGIN job AllowConcurrentJob in group AllowConcurrentJobGroup 2014-06-04 11:29:31,128 [DefaultQuartzScheduler_Worker-4] - Running jobs: AllowConcurrentJob,DisallowConcurrentJob,AllowConcurrentJob,AllowConcurrentJob 2014-06-04 11:29:32,128 [DefaultQuartzScheduler_Worker-5] - BEGIN job AllowConcurrentJob in group AllowConcurrentJobGroup 2014-06-04 11:29:32,128 [DefaultQuartzScheduler_Worker-5] - Running jobs: AllowConcurrentJob,DisallowConcurrentJob,AllowConcurrentJob,AllowConcurrentJob,AllowConcurrentJob 2014-06-04 11:29:33,128 [DefaultQuartzScheduler_Worker-6] - BEGIN job AllowConcurrentJob in group AllowConcurrentJobGroup 2014-06-04 11:29:33,128 [DefaultQuartzScheduler_Worker-6] - Running jobs: AllowConcurrentJob,DisallowConcurrentJob,AllowConcurrentJob,AllowConcurrentJob,AllowConcurrentJob,AllowConcurrentJob 2014-06-04 11:29:34,129 [DefaultQuartzScheduler_Worker-7] - BEGIN job AllowConcurrentJob in group AllowConcurrentJobGroup 2014-06-04 11:29:34,129 [DefaultQuartzScheduler_Worker-7] - Running jobs: AllowConcurrentJob,DisallowConcurrentJob,AllowConcurrentJob,AllowConcurrentJob,AllowConcurrentJob,AllowConcurrentJob,AllowConcurrentJob 2014-06-04 11:29:34,147 [DefaultQuartzScheduler_Worker-2] - END job AllowConcurrentJob in group AllowConcurrentJobGroup 2014-06-04 11:29:34,147 [DefaultQuartzScheduler_Worker-1] - END job DisallowConcurrentJob in group DisallowConcurrentJobGroup 2014-06-04 11:29:34,157 [DefaultQuartzScheduler_Worker-8] - BEGIN job DisallowConcurrentJob in group DisallowConcurrentJobGroup

Let’s analyze this output line by line.

Lines 1-17 are the standard startup lines. At this point the scheduler is up and running and ready to start processing jobs.

In line 18, The scheduler starts a AllowConcurrentJob and in line 19, it starts a DisallowConcurrentJob. Lines 20 and 21 indicate that there are 2 jobs running, one of each kind.We get 2 lines because each job writes one line.

In line 22, the scheduler starts another AllowConcurrentJob. We now have 2 of these type of jobs running and that is illustrated in line 23. The scheduler keeps adding AllowConcurrentJobs up until line 32, where the first AllowConcurrentJob finishes. How do I know it’s the first one? Because it’s the same thread name on line 18 as on line 32 (DefaultQuartzScheduler_Worker-2).

On line 33 we can see that the DisallowConcurrentJob finishes and that the scheduler starts a new instance on line 34. At this point the whole process will just continue as we have described, with only one DisallowConcurrentJob running and multiple AllowConcurrentJob instances running at the same time.

This hopefully illustrates the behavior of jobs that have been marked with the DisallowConcurrentExecutionAttribute. Keep in mind that If you are using this attribute and you get multiple instances of the same job type running at the same time, it’s probably because the job keys are different.