Dan
Dan

Reputation: 45751

Getting data from a running job in Quartz.net

I am trying to create a job in Quartz.net that will monitor the state of all the other jobs and regularly update a log file. It only gets data back from a job once that job has completed execution, but I am trying to get on the fly information of the state of the job.

I wrote as simple a test job as possible and the test half works (which is frustrating because I can't tell what is different in the actual code). This is the test code:

The jobs

[PersistJobDataAfterExecution]
[DisallowConcurrentExecution]
class SimpleFeedbackJob : IInterruptableJob
{
    private DateTime _lastRun;
    public string LastRun { get { return _lastRun.ToString(); } }
    private string _status;

    public void Interrupt()
    {
    }

    public void Execute(IJobExecutionContext context)
    {
        _status = "working";
        _lastRun = DateTime.Now;

        JobDataMap jobData = context.JobDetail.JobDataMap;
        jobData["time"] = LastRun;
        jobData["status"] = _status;

        DateTime n = DateTime.Now.AddSeconds(5);
        while (DateTime.Now < n) { }
        //Thread.Sleep(5000);

        _status = "idle";
        jobData["status"] = _status;
    }
}

public class LogUpdaterJob : IInterruptableJob
{
    private IScheduler _scheduler = TaskManager.Scheduler; //This is the same scheduler that will call this task :/
    private string _filepath = Configs.BasePath + @"Logs\log.txt";

    public void Execute(IJobExecutionContext context)
    {
        Func<string, string> UpdatedLineData
           = name =>
           {
               JobKey jobKey = _scheduler.GetJobKeys(GroupMatcher<JobKey>.GroupContains("test")).Where(k => k.Name == name).First();
               IJobDetail job = _scheduler.GetJobDetail(jobKey);
               ITrigger trigger = _scheduler.GetTriggersOfJob(jobKey).First();

               string status = job.JobDataMap.Get("time") as string;
               string time = job.JobDataMap.Get("status") as string;

               return string.Format("{0,-25} {1,-25}", time, status);
           };

        List<string> lines = new List<string>();
        lines.Add(UpdatedLineData("feedback_test"));
        File.WriteAllLines(_filepath, lines);
    }

    public void Interrupt()
    {
    }
}

Relevant extracts from TaskScheduler

private static IScheduler _scheduler = StdSchedulerFactory.GetDefaultScheduler();
public static IScheduler Scheduler { get { return _scheduler; } }

public void Run()
{
    _scheduler.Start();

    IJobDetail feedbackJob = JobBuilder.Create<SimpleFeedbackJob>()
                                       .WithIdentity("feedback_test", "test")
                                       .UsingJobData("time", "")
                                       .UsingJobData("status", "")
                                       .Build();

    ITrigger feedbackTrigger = TriggerBuilder.Create()
                                             .WithIdentity("feedback_test", "test")
                                             .WithSimpleSchedule(x => x.WithIntervalInSeconds(10)
                                                                       .RepeatForever())
                                             .Build();

    IJobDetail loggerJob = JobBuilder.Create<LogUpdaterJob>()
                                     .WithIdentity("LogUpdater", "Admin")
                                     .Build();

    ITrigger loggerTrigger = TriggerBuilder.Create()
                                           .WithIdentity("LogUpdater", "Admin")
                                           .WithSimpleSchedule(x => x.WithIntervalInSeconds(1)
                                                                     .RepeatForever())
                                           .Build();

    _scheduler.ScheduleJob(feedbackJob, feedbackTrigger);
    _scheduler.ScheduleJob(loggerJob, loggerTrigger);
}

So this does output some data to log.txt, it gets the last run time correct but it only ever displays a status of "idle" where I think it should be "working" half the time. In otherwords I would like the job data to be written to and accessible while the job is still running.

Is it possible to get data back from the job midway through the jobs Execute() like this?

Upvotes: 7

Views: 10795

Answers (2)

Rafał Rutkowski
Rafał Rutkowski

Reputation: 1449

It looks like job data changes are not available until the job has finished. So instead use a dedicated data structure for the purpose of monitoring the job status. In the following example I have exposed the status information using a simple public static property StatusInfo which is available to the logging job at any time.

One more minor change: I have replaced WriteAllLines with AppendAllLines.

class StatusInfo
{
    public DateTime LastRun;
    public string Status;
}

[PersistJobDataAfterExecution]
[DisallowConcurrentExecution]
class SimpleFeedbackJob : IInterruptableJob
{
    public static StatusInfo StatusInfo;

    static SimpleFeedbackJob()
    {
        SetStatus("idle");
    }

    public void Interrupt()
    {
    }

    public void Execute(IJobExecutionContext context)
    {
        SetStatus("working");

        Thread.Sleep(5000);

        SetStatus("idle");
    }

    private static void SetStatus(string status)
    {
        StatusInfo = new StatusInfo
            {
                LastRun = DateTime.Now,
                Status = status
            };
    }
}

class LogUpdaterJob : IInterruptableJob
{
    private string _filepath = @"D:\Temp\Logs\log.txt";

    public void Execute(IJobExecutionContext context)
    {
        List<string> lines = new List<string>();
        var statusInfo = SimpleFeedbackJob.StatusInfo;
        lines.Add(String.Format("{0,-25} {1,-25}",
            statusInfo.LastRun,
            statusInfo.Status));
        File.AppendAllLines(_filepath, lines);
    }

    public void Interrupt()
    {
    }
}

Upvotes: 10

jvilalta
jvilalta

Reputation: 6769

The short answer is that there is no way to do this out of the box. Here is the bit of code that executes your job in Quartz.Net.

// Execute the job
try
{
    if (log.IsDebugEnabled)
    {
        log.Debug("Calling Execute on job " + jobDetail.Key);
    }
    job.Execute(jec);
    endTime = SystemTime.UtcNow();
}
catch (JobExecutionException jee)
{
    endTime = SystemTime.UtcNow();
    jobExEx = jee;
    log.Info(string.Format(CultureInfo.InvariantCulture, "Job {0} threw a JobExecutionException: ", jobDetail.Key), jobExEx);
}
catch (Exception e)
{
    endTime = SystemTime.UtcNow();
    log.Error(string.Format(CultureInfo.InvariantCulture, "Job {0} threw an unhandled Exception: ", jobDetail.Key), e);
    SchedulerException se = new SchedulerException("Job threw an unhandled exception.", e);
    qs.NotifySchedulerListenersError(
    string.Format(CultureInfo.InvariantCulture, "Job ({0} threw an exception.", jec.JobDetail.Key), se);
    jobExEx = new JobExecutionException(se, false);
}

You'll see that once the execute method is called, the job runs off and does its thing and only returns when the job has finished executing, so there's no way to receive updates. This is in the JobRunShell.cs file in case you're interested in looking at the full context.

You can use job listeners to be notified of when jobs start running, error out or finish. That won't give you in progress information though. If you wanted to track progress of your jobs you could:

  1. Use something like the logging model, where you pass the job a reference to the logger and it logs its progress to it. Then your other job can read from this logger to find status. You'd have to make this logger globally available as the jobs run on separate threads and there are threading considerations to handle.
  2. You can swap out the implementation of the JobRunShell for yours. The built in JobRunShell has a reference to the job's execution context so it would be able to read the data map (maybe using a timer?) and then report back on status. You'll have to create your own IJobRunShellFactory for this. Here is the default jobRunShellFactory implementation.

Quartz.Net executes jobs on different threads so communicating with them while they're in progress comes with the same issues as trying to communicate to any other regular thread while it's in flight. So if you've ever tried to solve that problem then you know what you're up against.

Upvotes: 1

Related Questions