Search code examples
c#mefquartz.net

Quartz.NET JobBuilder.Create<> reuses same instance of IJob


I would like to have new instance of IJob for every execution. Here is how I have job setup:

[Export(typeof(IJob))]
[PartCreationPolicy(CreationPolicy.NonShared)]
[DisallowConcurrentExecution]
public class TestProcessor : IJob
{
    [Import]
    public ILoggerService LoggerService { get; set; }
    
    private string InstanceId { get; }

    public TestProcessor()
    {
        this.InstanceId = Guid.NewGuid().ToString().Substring(0, 4);
    }

    public void Execute(IJobExecutionContext context)
    {        
        // TODO: Here I expect to see log entry with different InstanceId every time
        string processGroupId = null;
        if (context != null && context.MergedJobDataMap.ContainsKey(JobListener.ProcessGroupId))
            processGroupId = context.MergedJobDataMap[JobListener.ProcessGroupId].ToString();

        this.LoggerService.Log(null, $"TEST: Group: {processGroupId}, JobInstance: {this.InstanceId}, ServiceInstance: {this.FuelPriceService.InstanceId}", Category.Debug, Priority.Low);
                
    }
}

Here is how I schedule this job for execution. Idea here, to run concurrent executions for different "groups" so those instances run on same schedule. But I see same instance Id in all Logs for all groups

foreach (var pgId in processGroups)
{    
    // Test Processor
    var testJobDetail = JobBuilder.Create<TestProcessor>().Build();
    testJobDetail.JobDataMap.Add(JobListener.ProcessGroupId, pgId);
    testJobDetail.JobDataMap.Add(JobListener.TimeLimitSeconds, "300");
    
    this.scheduler.ScheduleJob(testJobDetail, TriggerBuilder.Create().WithCronSchedule("0 0/5 * * * ?").Build());
}

I am not sure how

JobBuilder.Create<>()

works. It seems to ignore MEF attribute and uses its own container? I expect to have new instance every time I run JobBuilder.Create<> but not seeing it.

EDIT: More details

Code runs inside windows service project. Inside ServiceBase constructor following code executed. After those objects created I schedule jobs like shown above. Qauartz.JobBuilder used to create them (not custom one). There is no other code, specifically nothing about configuring MEF for Quartz.

// Import job factory 
var jobFactoryInstance = Bootstrapper.CompositionContainer.GetExports<IJobFactory>().FirstOrDefault();
if (jobFactoryInstance == null) throw new InvalidOperationException("Job Factory instance wasn't created!");
var jobFactory = jobFactoryInstance.Value;

// construct a scheduler factory
ISchedulerFactory schedulerFactory = new StdSchedulerFactory();
this.scheduler = schedulerFactory.GetScheduler();
this.scheduler.JobFactory = jobFactory;

// Import job listener
var jobListenerInstance = Bootstrapper.CompositionContainer.GetExports<IJobListener>().FirstOrDefault();
if (jobListenerInstance == null) throw new InvalidOperationException();
var jobListener = jobListenerInstance.Value;
this.scheduler.ListenerManager.AddJobListener(jobListener, EverythingMatcher<JobKey>.AllJobs());

EDIT2: Job factory details, this is only place that manually instantiated. Looks like this is where I need to provide fix by manually creating instance instead of using pre-imported list? Sounds like I am answering my q..

[Export(typeof(IJobFactory))]
public class JobFactory : IJobFactory
{
    [ImportMany(typeof(IJob))]
    public List<IJob> Jobs { get; private set; }

    public virtual IJob NewJob(TriggerFiredBundle bundle, IScheduler scheduler)
    {
        try
        {
            Debug.WriteLine("IDATT.WindowsService.JobFactory - getting job from a list");
            return this.Jobs.First(j => j.GetType() == bundle.JobDetail.JobType);
        }
        catch (Exception e)
        {
            var se = new SchedulerException(string.Format(CultureInfo.InvariantCulture, "Problem instantiating class '{0}'", bundle.JobDetail.JobType.FullName), e);
            throw se;
        }
    }

    public virtual void ReturnJob(IJob job)
    {
    }
}

Solution

  • As you already found - you have custom JobBuilder which is responsible for creating new job instances. In that builder you have static list of job instances (List<IJob> Jobs) imported from MEF, and then you getting an instance from that list based on requested JobType. So in result you indeed explicitly made it so that single instance of TestProcessor is being returned every time.