/// <summary> /// Wraps a call to <see cref="TaskQueueDirective.Parse{T}(Common.ApplicationTaskQueue.ApplicationTask)"/> /// to extract any directive supplied to the <paramref name="job" />. /// </summary> /// <param name="job">The job to retrieve the directive for.</param> /// <returns>The directive, or null if no directive passed.</returns> public static TaskQueueDirective GetTaskQueueDirective(this TaskProcessorJob job) { return(job.GetTaskQueueDirective <TaskQueueDirective>()); }
/// <summary> /// Handles when a job from a task requires processing. /// Note that all tasks/jobs in the queue use the same task type ID, /// so the directive <see cref="BackgroundOperationTaskQueueDirective.BackgroundOperationName"/> /// is used to identify which tasks should be executed by /// which background operation. /// </summary> /// <param name="job">The job to process.</param> protected virtual void ProcessJobHandler(TaskProcessorJob job) { // Sanity. if (null == job) { return; } // Ensure cancellation has not been requested. job.ThrowIfCancellationRequested(); // What is the current state? var state = job.AppTaskState; // Update the progress of the task in the task queue. try { this.TaskProcessor.UpdateTaskAsAssignedToProcessor(job); } catch { // Could not mark the task as assigned to a processor. SysUtils.ReportToEventLog ( $"Could not mark task {job.AppTaskId} as assigned to a processor (queue id: {job.AppTaskQueueId}, state: {state}).", System.Diagnostics.EventLogEntryType.Warning ); return; } // Sanity. if (null == job.Data?.Value) { // This is an issue. We have no way to decide what background operation should run it. Die. SysUtils.ReportErrorToEventLog ( $"Job loaded with no application task (queue: {job.AppTaskQueueId}, task id: {job.AppTaskId})." ); return; } // Deserialize the background directive. var backgroundOperationDirective = job.GetTaskQueueDirective <BackgroundOperationTaskQueueDirective>(); if (null == backgroundOperationDirective) { // This is an issue. We have no way to decide what background operation should run it. Die. SysUtils.ReportErrorToEventLog ( $"Job loaded with no background operation name loaded (queue: {job.AppTaskQueueId}, task id: {job.AppTaskId})." ); return; } // If we have a directive then extract it. var dir = backgroundOperationDirective.GetParsedInternalDirective(); // If it is a broadcast directive, then was it generated on the same server? // If so then ignore. if (dir is BroadcastDirective broadcastDirective) { if (broadcastDirective.GeneratedFromGuid == TaskQueueBackgroundOperationManager.CurrentServer.ServerID) { return; } } // Find the background operation to run. TaskQueueBackgroundOperationOverview bo = null; lock (_lock) { if (false == this.BackgroundOperations.TryGetValue(backgroundOperationDirective.BackgroundOperationName, out bo)) { // We have no registered background operation to handle the callback. SysUtils.ReportErrorToEventLog ( $"No background operation found with name {backgroundOperationDirective.BackgroundOperationName}(queue: {job.AppTaskQueueId}, task id: {job.AppTaskId})." ); return; } } // Should we repeat? DateTime?nextRun = null; switch (bo.BackgroundOperation.RepeatType) { case TaskQueueBackgroundOperationRepeatType.Interval: // Add the interval to the current datetime. if (bo.BackgroundOperation.Interval.HasValue) { nextRun = DateTime.UtcNow.Add(bo.BackgroundOperation.Interval.Value); } break; case TaskQueueBackgroundOperationRepeatType.Schedule: // Get the next execution time from the schedule. nextRun = bo.BackgroundOperation.Schedule?.GetNextExecution(DateTime.Now); break; } // If we have a next run time then re-run. if (null != nextRun) { // Bind to the completed event ( called always ) of the job. // That way even if the job is canceled, fails, or finishes successfully // ...we always schedule the next run. job.ProcessingCompleted += (s, op) => { // Ensure that if two threads both run this at once we don't end up with a race condition. lock (_lock) { // Cancel any future executions (we only want the single one created below). this.CancelFutureExecutions(bo.BackgroundOperation.Name); // Now schedule it to run according to the interval. this.RunOnce ( bo.BackgroundOperation.Name, nextRun.Value.ToUniversalTime(), dir ); } }; } // Perform the action. try { // Mark the background operation as running. bo.Status = TaskQueueBackgroundOperationStatus.Running; bo.LastRun = DateTime.UtcNow; // Delegate to the background operation. bo.BackgroundOperation.RunJob(job, dir); } catch (Exception e) { // Exception. this.TaskProcessor.UpdateTaskInfo ( job.Data?.Value, MFTaskState.MFTaskStateFailed, e.Message, false ); // TODO: throw? } finally { // If the status is running then stop it. if (bo.Status == TaskQueueBackgroundOperationStatus.Running) { bo.Status = TaskQueueBackgroundOperationStatus.Stopped; } } }
/// <summary> /// Handles when a job from a task requires processing. /// Note that all tasks/jobs in the queue use the same task type ID, /// so the directive <see cref="BackgroundOperationTaskQueueDirective.BackgroundOperationName"/> /// is used to identify which tasks should be executed by /// which background operation. /// </summary> /// <param name="job">The job to process.</param> protected virtual void ProcessJobHandler(TaskProcessorJob job) { // Sanity. if (null == job) { return; } // Ensure cancellation has not been requested. job.ThrowIfCancellationRequested(); // Update the progress of the task in the task queue. this.TaskProcessor.UpdateTaskAsAssignedToProcessor(job); // Sanity. if (null == job.Data?.Value) { // This is an issue. We have no way to decide what background operation should run it. Die. SysUtils.ReportErrorToEventLog ( $"Job loaded with no application task (queue: {job.AppTaskQueueId}, task type: {job.AppTaskId})." ); return; } // Deserialize the background directive. var backgroundOperationDirective = job.GetTaskQueueDirective <BackgroundOperationTaskQueueDirective>(); if (null == backgroundOperationDirective) { // This is an issue. We have no way to decide what background operation should run it. Die. SysUtils.ReportErrorToEventLog ( $"Job loaded with no background operation name loaded (queue: {job.AppTaskQueueId}, task type: {job.AppTaskId})." ); return; } // If we have a directive then extract it. var dir = backgroundOperationDirective.GetParsedInternalDirective(); // If it is a broadcast directive, then was it generated on the same server? // If so then ignore. if (dir is BroadcastDirective broadcastDirective) { if (broadcastDirective.GeneratedFromGuid == TaskQueueBackgroundOperationManager.CurrentServer.ServerID) { return; } } // Find the background operation to run. TaskQueueBackgroundOperation bo = null; lock (_lock) { if (false == this.BackgroundOperations.TryGetValue(backgroundOperationDirective.BackgroundOperationName, out bo)) { // We have no registered background operation to handle the callback. SysUtils.ReportErrorToEventLog ( $"No background operation found with name {backgroundOperationDirective.BackgroundOperationName}(queue: {job.AppTaskQueueId}, task type: {job.AppTaskId})." ); return; } } // If it is recurring then use the ProcessingComplete event to schedule the next execution. if (bo.Recurring && bo.Interval.HasValue) { // Bind to the completed event ( called always ) of the job. // That way even if the job is canceled, fails, or finishes successfully // ...we always schedule the next run. job.ProcessingCompleted += (s, op) => this.RunOnce ( bo.Name, DateTime.UtcNow.Add(bo.Interval.Value), dir ); } // Perform the action. try { // Delegate to the background operation. bo.RunJob(job, dir); } catch (Exception e) { // Exception. this.TaskProcessor.UpdateTaskInfo ( job.Data?.Value, MFTaskState.MFTaskStateFailed, e.Message, false ); // TODO: throw? } }