/// <summary> /// Processes a re-occuring hourly task. /// </summary> /// <param name="job">The hourly task job.</param> private void ProcessHourlyTask(TaskProcessorJob job) { // Debug Logging. if (this.Configuration.LoggingEnabled) { Debug.WriteLine($"Hourly task processing with task id => {job.Data?.Value.Id}."); } // 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.TaskProcessor.CreateApplicationTaskSafe( true, VaultApplication.BackgroundOperationTaskQueueId, VaultApplication.TaskTypeHourlyRecurringTask, null, DateTime.Now.AddHours(1).ToUniversalTime()); // The hourly task has come due and is being processed. job.ThrowIfCancellationRequested(); // Update has having been assigned. this.TaskProcessor.UpdateTaskAsAssignedToProcessor(job); // TODO: Do hourly work here... }
private void ProcessBroadcastTask(TaskProcessorJob job) { // 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) { return; } // Deserialize the directive. EmailByUserPatchDirective dir = TaskQueueDirective.Parse <EmailByUserPatchDirective>(job.Data.Value); if (dir.GeneratedFromGuid != CurrentServer.ServerID) { // Debug Logging. if (this.Configuration.LoggingEnabled) { SysUtils.ReportInfoToEventLog($"Broadcast processing with task id => {job.Data.Value.Id} on server {CurrentServer.ServerID}."); } // The task was created on another server, so we should process it on this server. AddOrUpdateEmailByUserId(dir.UserAccount, dir.Email); } }
/// <summary> /// Processes a single job. /// </summary> /// <param name="job">Task processor job.</param> private void ProcessSequentialTask(TaskProcessorJob job) { // Debug Logging. if (this.Configuration.LoggingEnabled) { Debug.WriteLine($"Sequential task processing with task id => {job.Data?.Value.Id}."); } // 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) { return; } // Deserialize the directive. var dir = TaskQueueDirective.Parse <ObjVerExTaskQueueDirective>(job.Data?.Value); // Sanity. if (string.IsNullOrWhiteSpace(dir?.ObjVerEx)) { return; } // Update the object. try { // Mark that we're updating the object. this.TaskProcessor.UpdateTaskInfo ( job.Data?.Value, MFTaskState.MFTaskStateInProgress, $"Updating object {dir.ObjVerEx}", false ); // Load the object, check it out, update, check it in. var objVerEx = ObjVerEx.Parse(job.Vault, dir.ObjVerEx); objVerEx.CheckOut(); objVerEx.SetProperty ( MFBuiltInPropertyDef.MFBuiltInPropertyDefNameOrTitle, MFDataType.MFDatatypeText, DateTime.Now.ToLongTimeString() ); objVerEx.SaveProperties(); objVerEx.CheckIn(); // Updated. this.TaskProcessor.UpdateTaskInfo ( job.Data?.Value, MFTaskState.MFTaskStateCompleted, $"Object {dir.ObjVerEx} updated", false ); } catch (Exception e) { // Exception. this.TaskProcessor.UpdateTaskInfo ( job.Data?.Value, MFTaskState.MFTaskStateFailed, e.Message, false ); } }
/// <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? } }