/// <summary> /// Get Job Details by Job Id /// </summary> /// <param name="jobId"> /// JobId of the job /// </param> /// <returns> /// Job Details if job found else null /// </returns> public ScheduledJobDetails GetJobById(Guid jobId) { string rowKeyFilter = TableQuery.GenerateFilterCondition("RowKey", QueryComparisons.Equal, ScheduledJobEntity.GetRowKey(jobId)); TableQuery <ScheduledJobEntity> query = new TableQuery <ScheduledJobEntity>().Where(rowKeyFilter); IEnumerable <ScheduledJobEntity> entities = azureTableProvider.Table.ExecuteQuery(query); foreach (ScheduledJobEntity scheduledJobEntity in entities) { ScheduledJobDetails details = new ScheduledJobDetails() { JobId = new Guid(scheduledJobEntity.RowKey), JobState = (ScheduledJobState)Enum.Parse(typeof(ScheduledJobState), scheduledJobEntity.State), JobType = (ScheduledJobType)Enum.Parse(typeof(ScheduledJobType), scheduledJobEntity.PartitionKey), Recurrence = JsonConvert.DeserializeObject <Recurrence>(scheduledJobEntity.Recurrence), StartTime = scheduledJobEntity.StartTime, Version = scheduledJobEntity.Version }; if (scheduledJobEntity.Payload != null) { details.Payload = JsonConvert.DeserializeObject <ConcurrentDictionary <string, string> >(scheduledJobEntity.Payload); } return(details); } return(null); }
/// <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 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> /// 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> /// Update the job. This will requeue the message depending on the Recurrence. /// Use this to: /// 1. Update State : /// a. Set to Pause to pause a job /// b. Set to Running to resume a job /// c. Set to Canceled to cancel a job /// 2. Update payload. /// 3. Update Recurrence Schedule /// 4. Update description /// </summary> /// <param name="jobDetails"> /// Details to be updated /// </param> /// <returns> /// Task wrapper for async operation /// </returns> public async Task UpdateJobAsync(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 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) { // start time cannot be changed. // If we need to change start time, cancel this job and create a new one. jobDetails.StartTime = entity.StartTime; // always update state entity.State = Enum.GetName(typeof(ScheduledJobState), jobDetails.JobState); // always increment version number entity.Version = entity.Version + 1; // update the payload if needed if (jobDetails.Payload != null) { entity.Payload = JsonConvert.SerializeObject(jobDetails.Payload); } else { entity.Payload = null; } // update recurrence if needed if (jobDetails.Recurrence != null) { entity.Recurrence = JsonConvert.SerializeObject(jobDetails.Recurrence); } // udate description entity.JobDescription = jobDetails.JobDescription; //update table await azureTableProvider.UpdateAsync(entity).ConfigureAwait(false); //queue a new message with updated version if the status is supposed to be Running if (jobDetails.JobState == ScheduledJobState.Running) { // when should we run the job TimeSpan whenToRun = jobDetails.Recurrence.ToTimeSpan(); jobDetails.Version = entity.Version; await azureQueueProvider.EnqueueAsync( new CloudQueueMessage(JsonConvert.SerializeObject(jobDetails)), whenToRun).ConfigureAwait(false); } } Log.Info("Successfully updated job {0} ", jobDetails.JobId); }