/// <summary> /// Initializes a new instance of the JobOrchestrator class. /// </summary> /// <param name="job"> /// The job being executed. /// </param> /// <param name="jobDetails"> /// Details about the job being executed. /// </param> /// <param name="log"> /// The CommerceLog object through which log entries can be made. /// </param> public JobOrchestrator(IOrchestratedJob job, ScheduledJobDetails jobDetails, CommerceLog log) { Job = job; JobDetails = jobDetails; Log = log; }
/// <summary> /// Creates and initializes an instance of IOrchestratedJob of the specified type. /// </summary> /// <param name="jobDetails"> /// Details describing the IOrchestratedJob type to create. /// </param> /// <param name="scheduler"> /// The scheduler managing the job. /// </param> /// <param name="log"> /// The object through which log entries can be made. /// </param> /// <returns> /// An instance of IOrchestratedJob of the specified type. /// </returns> /// <exception cref="ArgumentNullException"> /// * Parameter jobDetails cannot be null. /// -OR- /// * Parameter log cannot be null. /// </exception> /// <exception cref="ArgumentException"> /// Parameter JobDetails does not specify a valid IOrchestratedJob type. /// </exception> public static IOrchestratedJob Create(ScheduledJobDetails jobDetails, IScheduler scheduler, CommerceLog log) { if (jobDetails == null) { throw new ArgumentNullException("jobDetails", "Parameter jobDetails cannot be null."); } if (log == null) { throw new ArgumentNullException("log", "Parameter log cannot be null."); } IOrchestratedJob result = null; if (jobDetails.Payload != null) { switch (jobDetails.JobType) { case ScheduledJobType.ApplyReferralReward: result = new ApplyReferralRewardJob(log); break; case ScheduledJobType.ApplyRedemptionReward: result = new ApplyRedemptionRewardJob(log); break; case ScheduledJobType.ClaimDiscountsForNewCard: result = new ClaimDiscountsForNewCardJob(log); break; case ScheduledJobType.ClaimDiscountForExistingCards: result = new ClaimDiscountForExistingCardsJob(log); break; case ScheduledJobType.AmexOfferRegistration: result = new AmexOfferRegistrationJob(log); break; default: throw new ArgumentException("Parameter JobDetails does not specify a valid IOrchestratedJob type.", "jobDetails"); } result.Initialize(jobDetails, scheduler); } else { log.Error("{0} orchestrated job contains no Payload.", null, jobDetails.JobType, ResultCode.JobContainsNoPayload); } return(result); }
/// <summary> /// Runs the orchestrated job described in the specified ScheduleJobDetails object. /// </summary> /// <param name="jobDetails"> /// The details of the orchestrated job to run. /// </param> /// <remarks> /// A job is limited to MaxJobRetries runs, even for successful runs, before being placed back in the queue to ensure /// that a job in an endless loop does not take down the entire worker role (pre-emptive multi-tasking.) But, if the job /// times out, another worker instance may attempt to run the job while it's still being run in another instance. Going /// forward, some mechanism to halt execution at time out should be added. /// </remarks> public async Task RunJobAsync(ScheduledJobDetails jobDetails) { OrchestratedExecutionResult executionResult; int maxRetries = CommerceConfig.MaxJobRetries; int retryLatency = CommerceConfig.InitialJobRetryLatency; int tryCount = 0; int tasksPerformed = 0; IOrchestratedJob job = JobFactory(jobDetails, Scheduler, Log); JobOrchestrator jobOrchestrator = null; if (job != null) { jobOrchestrator = new JobOrchestrator(job, jobDetails, Log); } if (jobOrchestrator != null) { do { try { Task <OrchestratedExecutionResult> executionResultTask = Task.Factory.StartNew(() => jobOrchestrator.Execute(out tasksPerformed)); executionResult = await executionResultTask; } catch (InvalidOperationException ex) { ResultCode resultCode; if (Enum.TryParse <ResultCode>(ex.Message, out resultCode) == true) { executionResult = OrchestratedExecutionResult.NonTerminalError; } else { throw; } } Log.Verbose("{0} orchestrated job completed {1} steps with result {2}.", jobDetails.JobType, tasksPerformed, executionResult); if (executionResult == OrchestratedExecutionResult.NonTerminalError && tryCount <= maxRetries) { Log.Verbose("Waiting {0} milliseconds before retrying job execution.", retryLatency); Thread.Sleep(retryLatency); retryLatency *= 2; } tryCount++; }while (executionResult != OrchestratedExecutionResult.TerminalError && tasksPerformed > 0 && tryCount <= maxRetries); // tear down the job here. executionResult = jobOrchestrator.Cleanup(executionResult); } else { executionResult = OrchestratedExecutionResult.TerminalError; } StringBuilder stringBuilder = new StringBuilder("{0} orchestrated job completed with result {1}."); if (executionResult == OrchestratedExecutionResult.NonTerminalError) { stringBuilder.Append(" Job will be sent to the back of the queue for reprocessing."); } Log.Information(stringBuilder.ToString(), jobDetails.JobType, executionResult); // Update Scheduler with result of running the job. switch (executionResult) { case OrchestratedExecutionResult.Success: await Scheduler.CompleteJobIterationAsync(jobDetails).ConfigureAwait(false); break; case OrchestratedExecutionResult.TerminalError: jobDetails.JobState = ScheduledJobState.Canceled; jobDetails.Payload = null; await Scheduler.UpdateJobAsync(jobDetails).ConfigureAwait(false); break; case OrchestratedExecutionResult.NonTerminalError: await Scheduler.ExponentiallyBackoffAsync(jobDetails, Log).ConfigureAwait(false); break; } }