public async Task WriteNewScheduleTargets() { // Decisions about whether the once-daily run target is near enough: if (await WasLastRunToday()) { return; } // This will be an HHmm value such as 1030 or 2200; SchedulerQueue only invokes this // grain at 15-minute intervals, so quit unless we're near or past the run-at time. var paddedUtc = todayUtc.PlusMinutes(5); var paddedTime = paddedUtc.TimeOfDay; var runTargetUtc = await configCache.GetValue(ConstConfigKeys.ScheduleWriterRunTargetUtc); var(runHour, runMinute) = DateTimeAnalysis.GetHourMinute(runTargetUtc); if (paddedUtc.Date == todayUtc.Date && (paddedTime.Hour < runHour || (paddedTime.Hour == runHour && paddedTime.Minute < runMinute))) { return; } // Decision made: yes, write the next day's schedule records. logger.LogInformation($"WriteNewScheduleTargets"); var jobs = await scheduleRepository.GetJobsWithScheduleSettings(); if (jobs.Count == 0) { return; } foreach (var job in jobs) { try { var planner = new TargetPlanner(job, today.Plus(Duration.FromDays(1)), logger); var newSchedules = planner.GetSchedules(); if (newSchedules.Count > 0) { await InsertScheduleRows(newSchedules); } newSchedules = null; } catch { } } await configRepository.UpdateConfig(ConstConfigKeys.ScheduleWriterLastRunDateUtc, InstantPattern.General.Format(today)); }
public TargetPlanner( JobsWithSchedulesQuery job, Instant forTargetDate, ILogger parentLogger = null) { this.job = job; targetDate = forTargetDate; logger = parentLogger; jobZone = DateTimeZoneProviders.Tzdb[job.ScheduleTimeZone]; var zonedDate = forTargetDate.InZone(jobZone).Date; analysis = DateTimeAnalysis.ForZonedTargetDate(zonedDate); }
private async Task StartNextStep(int skipToStepNumber = 0) { var stepNum = (skipToStepNumber == 0) ? ++status.JobTypeProperties.SequenceStep : skipToStepNumber; currentStep = steps.Where(s => s.Step == stepNum).FirstOrDefault(); // TODO set correct status and wrap up job processing before throwing if (currentStep == null) { throw new JobFacInvalidDataException($"Step {stepNum} not defined for sequence {jobDefinition.Id}"); } var stepStatus = new StatusSequenceStep { Step = status.JobTypeProperties.SequenceStep }; status.JobTypeProperties.StepStatus.Add(stepNum, stepStatus); if (!await ProcessStartDecision()) { return; } var jobIds = Formatting.SplitCommaSeparatedList(currentStep.JobDefinitionIdList); foreach (var id in jobIds) { var options = new FactoryStartOptions { DefinitionId = id, SequenceInstanceId = jobInstanceKey, // TODO filter spawned-job args/payloads to the specific job? ReplacementArguments = status.StartOptions.ReplacementArguments, StartupPayloads = status.StartOptions.StartupPayloads }; var jobId = await jobFactory.StartJob(options); status.JobTypeProperties.JobInstanceStepMap.Add(jobId, stepNum); stepStatus.JobStatus.Add(jobId, new JobStatus <StatusExternalProcess> { Key = jobId, StartOptions = options, RunStatus = RunStatus.Unknown, }); } await StoreNewRunStatus(RunStatus.Running); if (currentStep.ExitDecision == StepExitDecision.DoNextStepWithoutWaiting) { await StartNextStep(); } // local function returns value indicating whether to continue start-up async Task <bool> ProcessStartDecision() { // The validator ensures StartDates and StartTimes are correct. if (currentStep.StartDateDecision != StepStartDateDecision.NoDecision) { var analysis = DateTimeAnalysis.Now(currentStep.StartDecisionTimeZone); var dates = Formatting.SplitCommaSeparatedList(currentStep.StartDates); bool startDecision = currentStep.StartDateDecision switch { // see scheduler TargetPlanner for descriptions StepStartDateDecision.DaysOfWeek => analysis.InDaysOfWeek(dates), StepStartDateDecision.DaysOfMonth => analysis.InDaysOfMonth(dates), StepStartDateDecision.SpecificDates => analysis.InSpecificDates(dates), StepStartDateDecision.DateRanges => analysis.InDateRanges(dates), StepStartDateDecision.Weekdays => analysis.InWeekdays(dates), _ => true, }; if (startDecision && currentStep.StartTimeDecision != StepStartTimeDecision.NoDecision) { var times = Formatting.SplitCommaSeparatedList(currentStep.StartTimes); startDecision = currentStep.StartTimeDecision switch { StepStartTimeDecision.IfHours => analysis.InHours(times), StepStartTimeDecision.IfMinutes => analysis.InMinutes(times), StepStartTimeDecision.IfTime => analysis.InSpecificTimes(times), StepStartTimeDecision.IfTimeRange => analysis.InTimeRanges(times), _ => true, }; } stepStatus.StartDecisionSuccess = startDecision; if (!startDecision) { await historyRepo.UpdateStatus(status); switch (currentStep.StartFalseAction) { case StepAction.DoNextStep: await StartNextStep(); break; case StepAction.DoStepNumber: await StartNextStep(currentStep.StartFalseStepNumber); break; case StepAction.EndSequence: await Stop(); break; } return(false); } } return(true); } }
private void CreateSchedulesForDate() { var todayUtc = SystemClock.Instance.GetCurrentInstant().InUtc(); var targetDateIsToday = (todayUtc.Date == targetDate.InUtc().Date); // When the schedules are being created for the current date, don't // create new schedules targeting times which have already passed. var filterHour = todayUtc.Hour; var filterMinute = todayUtc.Minute; var times = Formatting.SplitCommaSeparatedList(job.ScheduleTimes); switch (job.ScheduleTimeMode) { // every hour at the indicated minutes (eg. 00,15,30,45) case ScheduleTimeMode.Minutes: for (int hour = 0; hour < 24; hour++) { foreach (var min in times) { var minute = int.Parse(min); TryAddUtcEntry(hour, minute); } } break; // specific times: 1130,1400,2245 case ScheduleTimeMode.HoursMinutes: foreach (var time in times) { var(hour, minute) = DateTimeAnalysis.GetHourMinute(time); TryAddUtcEntry(hour, minute); } break; // a single value, every N minutes starting after midnight (eg. 30) case ScheduleTimeMode.Interval: for (int hour = 0; hour < 24; hour++) { int interval = int.Parse(times[0]); for (int minute = 0; minute < 59; minute += interval) { TryAddUtcEntry(hour, minute); } } break; } void TryAddUtcEntry(int hour, int minute) { var local = new LocalDateTime(analysis.Date.Year, analysis.Month, analysis.Day, hour, minute); try { var scheduleTarget = local .InZoneStrictly(jobZone) // TODO make InZoneStrictly vs Leniently a config option .WithZone(DateTimeZone.Utc) .ToDateTimeOffset(); if (!targetDateIsToday || filterHour < scheduleTarget.Hour || (filterHour == scheduleTarget.Hour && filterMinute <= scheduleTarget.Minute)) { newSchedules.Add(new ScheduledJobsTable { DefinitionId = job.Id, ScheduleTarget = scheduleTarget }); } } catch (Exception ex) when(ex is SkippedTimeException || ex is AmbiguousTimeException) { logger?.LogWarning($"Skipping invalid/ambiguous results applying timezone {jobZone.Id} to {LocalDateTimePattern.GeneralIso.Format(local)}"); } } }