/// <summary> /// Mark the job iteration as complete. /// </summary> /// <param name="jobDetails"> /// The details of the job which we want to mark as complete /// </param> /// <returns> /// Task wrapper for async operation /// </returns> public async Task CompleteJobIterationAsync(ScheduledJobDetails jobDetails) { Log.Info("Incoming request to complete a job iteration \r\n" + "details: {0}", jobDetails); await DeleteJobFromQueueAsync(jobDetails).ConfigureAwait(false); Log.Verbose("Job {0} deleted from the queue", jobDetails.JobId); TableResult result = await azureTableProvider.RetrieveAsync( ScheduledJobEntity.GetPartitionKey(jobDetails.JobType), ScheduledJobEntity.GetRowKey(jobDetails.JobId)); ScheduledJobEntity entity = (ScheduledJobEntity)result.Result; if (entity != null) { entity.LastRunTime = DateTime.UtcNow; entity.Count = entity.Count + 1; if (jobDetails.Payload != null) { entity.Payload = JsonConvert.SerializeObject(jobDetails.Payload); } else { entity.Payload = null; } Recurrence scheduledRecurrence = JsonConvert.DeserializeObject <Recurrence>(entity.Recurrence); if (entity.Count == scheduledRecurrence.Count) { entity.State = Enum.GetName(typeof(ScheduledJobState), ScheduledJobState.Completed); Log.Verbose("Job {0} completed all scheduled runs. Will be marked complete ", jobDetails.JobId); // if we are marking job as complete, delete payload. It unnecessary inflates the job size // and we don't need it. // Max size for each property is 64K ! entity.Payload = null; } else { // schedule next recurrence AzureScheduledJobDetails details = jobDetails as AzureScheduledJobDetails; if (details != null) { details.QueueMessage = null; // TODO : Include StartTime in calculation ... else drift will increase await azureQueueProvider.EnqueueAsync(new CloudQueueMessage(JsonConvert.SerializeObject(details)), scheduledRecurrence.ToTimeSpan()).ConfigureAwait(false); Log.Verbose("Job {0} completed {1} runs, scheduling the next due occurrence", jobDetails.JobId, entity.Count); } } await azureTableProvider.UpdateAsync(entity).ConfigureAwait(false); } Log.Info("Successfully marked iteration of job {0} as complete", jobDetails.JobId); }
/// <summary> /// Get Job Details from Cloud Queue Message /// </summary> /// <param name="queueMessage"> /// Cloud Queue Message /// </param> /// <returns> /// Job Details /// </returns> public static ScheduledJobDetails FromCloudQueueMessage(CloudQueueMessage queueMessage) { AzureScheduledJobDetails jobDetails = JsonConvert.DeserializeObject <AzureScheduledJobDetails>(queueMessage.AsString); jobDetails.QueueMessage = queueMessage; return(jobDetails); }
/// <summary> /// Get a job off the queue to process if available /// </summary> /// <returns> /// Details of the job to run, or NULL if none present /// </returns> public async Task <ScheduledJobDetails> GetJobToProcessAsync() { // Get message from queue - timeout of 1 min CloudQueueMessage message = await azureQueueProvider.DequeueAsync(TimeSpan.FromMinutes(1)).ConfigureAwait(false); ScheduledJobDetails details = null; //TODO: Add logic to log these every _n_ times instead of once per polling interval (currently 50ms). // Log.Verbose("Incoming request to get a job to be processed"); if (message != null) { // Get entity from table details = AzureScheduledJobDetails.FromCloudQueueMessage(message); TableResult result = await azureTableProvider.RetrieveAsync( ScheduledJobEntity.GetPartitionKey(details.JobType), ScheduledJobEntity.GetRowKey(details.JobId)).ConfigureAwait(false); ScheduledJobEntity entity = (ScheduledJobEntity)result.Result; if (entity == null) { // something bad happened in scheduling Log.Critical(500, "Deleting Job from queue as entity does not exist , jobId {0} ", details.JobId); await DeleteJobFromQueueAsync(details).ConfigureAwait(false); details = null; } else { int currentVersion = entity.Version; // if version mismatch, that means job has been updated // We should delete the current message from the Queue if (currentVersion != details.Version) { await DeleteJobFromQueueAsync(details).ConfigureAwait(false); details = null; } } } if (details != null) { Log.Info("Job to be processed retrieved \r\n" + "details : {0}", details); } return(details); }
/// <summary> /// Use this to increase visibility timeout of a job which might take longer to process /// </summary> /// <param name="jobDetails"> /// Job Details /// </param> /// <param name="newTimeout"> /// What should be the new timeout /// </param> /// <returns> /// Async Task Wrapper /// </returns> public async Task IncreaseVisibilityTimeout(ScheduledJobDetails jobDetails, TimeSpan newTimeout) { if (jobDetails == null) { throw new SchedulerException("JobDetails cannot be null"); } Log.Info("Updating timeout of job {0} to {1} ", jobDetails.JobId, newTimeout.ToString()); AzureScheduledJobDetails azureJobDetails = jobDetails as AzureScheduledJobDetails; CloudQueueMessage message = AzureScheduledJobDetails.ToCloudQueueMessage(azureJobDetails); await azureQueueProvider.IncreaseTimeout(message, newTimeout).ConfigureAwait(false); Log.Info("Successfully updated timeout of job {0} ", jobDetails.JobId); }
/// <summary> /// Update the job payload to the new payload specified /// </summary> /// <param name="jobDetails"> /// Details of the job to be updated /// </param> /// <returns> /// Task wrapper for async operation /// </returns> /// <remarks> /// This update is done in place, hence queue order is not changed. /// Please be aware that this call only updates the payload. /// Imp : NOT THREADSAFE. So multiple calls can arrive out of order /// to make payload be off sync. Be careful if you have to use it. /// </remarks> public async Task UpdateJobPayload(ScheduledJobDetails jobDetails) { if (jobDetails == null) { throw new SchedulerException("JobDetails cannot be null"); } string errorMessage; if (!jobDetails.ValidateUpdate(out errorMessage)) { throw new SchedulerException(errorMessage); } Log.Info("Incoming request to update payload of a job \r\n" + "details: {0}", jobDetails); TableResult result = await azureTableProvider.RetrieveAsync( ScheduledJobEntity.GetPartitionKey(jobDetails.JobType), ScheduledJobEntity.GetRowKey(jobDetails.JobId)).ConfigureAwait(false); ScheduledJobEntity entity = (ScheduledJobEntity)result.Result; if (entity != null) { entity.Payload = JsonConvert.SerializeObject(jobDetails.Payload); await azureTableProvider.UpdateAsync(entity).ConfigureAwait(false); AzureScheduledJobDetails azureJobDetails = jobDetails as AzureScheduledJobDetails; CloudQueueMessage message = AzureScheduledJobDetails.ToCloudQueueMessage(azureJobDetails); // azureJobDetails.QueueMessage = null; message.SetMessageContent(JsonConvert.SerializeObject(jobDetails)); await azureQueueProvider.UpdateAsync(message).ConfigureAwait(false); } Log.Info("Successfully updated payload of job {0} ", jobDetails.JobId); }
/// <summary> /// Convert Job Details to Cloud Queue Message /// </summary> /// <param name="azureScheduledJobDetails"> /// Job Details /// </param> /// <returns> /// Cloud Queue Message /// </returns> public static CloudQueueMessage ToCloudQueueMessage(AzureScheduledJobDetails azureScheduledJobDetails) { return(azureScheduledJobDetails.QueueMessage); }
/// <summary> /// Delete job from the queue /// </summary> /// <param name="jobDetails"> /// Job Details /// </param> /// <returns> /// Task wrapper for async operations /// </returns> private async Task DeleteJobFromQueueAsync(ScheduledJobDetails jobDetails) { await azureQueueProvider.DeleteAsync( AzureScheduledJobDetails.ToCloudQueueMessage((AzureScheduledJobDetails)jobDetails)).ConfigureAwait(false); }