internal static TimerSchedule Create(TimerTriggerAttribute attribute, INameResolver nameResolver) { TimerSchedule schedule = null; if (!string.IsNullOrEmpty(attribute.ScheduleExpression)) { string resolvedExpression = nameResolver.ResolveWholeString(attribute.ScheduleExpression); CronSchedule cronSchedule = null; if (CronSchedule.TryCreate(resolvedExpression, out cronSchedule)) { schedule = cronSchedule; DateTime[] nextOccurrences = cronSchedule.InnerSchedule.GetNextOccurrences(DateTime.Now, DateTime.Now + TimeSpan.FromMinutes(1)).ToArray(); if (nextOccurrences.Length > 1) { // if there is more than one occurrence due in the next minute, // assume that this is a sub-minute constant schedule and disable // persistence attribute.UseMonitor = false; } else if (!attribute.UseMonitor.HasValue) { // if the user hasn't specified a value // set to true attribute.UseMonitor = true; } } else { TimeSpan periodTimespan = TimeSpan.Parse(resolvedExpression); schedule = new ConstantSchedule(periodTimespan); if (periodTimespan.TotalMinutes < 1) { // for very frequent constant schedules, we want to disable persistence attribute.UseMonitor = false; } else if (!attribute.UseMonitor.HasValue) { // if the user hasn't specified a value // set to true attribute.UseMonitor = true; } } } else { schedule = (TimerSchedule)Activator.CreateInstance(attribute.ScheduleType); if (!attribute.UseMonitor.HasValue) { // if the user hasn't specified a value // set to true attribute.UseMonitor = true; } } return(schedule); }
/// <summary> /// Checks whether the schedule is currently past due. /// </summary> /// <remarks> /// On startup, all schedules are checked to see if they are past due. Any /// timers that are past due will be executed immediately by default. Subclasses can /// change this behavior by inspecting the current time and schedule to determine /// whether it should be considered past due. /// </remarks> /// <param name="timerName">The name of the timer to check.</param> /// <param name="now">The time to check.</param> /// <param name="schedule">The <see cref="TimerSchedule"/></param> /// <param name="lastStatus">The last recorded status, or null if the status has never been recorded.</param> /// <returns>A non-zero <see cref="TimeSpan"/> if the schedule is past due, otherwise <see cref="TimeSpan.Zero"/>.</returns> public virtual async Task<TimeSpan> CheckPastDueAsync(string timerName, DateTime now, TimerSchedule schedule, ScheduleStatus lastStatus) { DateTime recordedNextOccurrence; if (lastStatus == null) { // If we've never recorded a status for this timer, write an initial // status entry. This ensures that for a new timer, we've captured a // status log for the next occurrence even though no occurrence has happened yet // (ensuring we don't miss an occurrence) DateTime nextOccurrence = schedule.GetNextOccurrence(now); lastStatus = new ScheduleStatus { Last = default(DateTime), Next = nextOccurrence }; await UpdateStatusAsync(timerName, lastStatus); recordedNextOccurrence = nextOccurrence; } else { // ensure that the schedule hasn't been updated since the last // time we checked, and if it has, update the status DateTime expectedNextOccurrence; if (lastStatus.Last == default(DateTime)) { // there have been no executions of the function yet, so compute // from now expectedNextOccurrence = schedule.GetNextOccurrence(now); } else { // compute the next occurrence from the last expectedNextOccurrence = schedule.GetNextOccurrence(lastStatus.Last); } if (lastStatus.Next != expectedNextOccurrence) { lastStatus.Next = expectedNextOccurrence; await UpdateStatusAsync(timerName, lastStatus); } recordedNextOccurrence = lastStatus.Next; } if (now > recordedNextOccurrence) { // if now is after the last next occurrence we recorded, we know we've missed // at least one schedule instance and we are past due return now - recordedNextOccurrence; } else { // not past due return TimeSpan.Zero; } }
internal static string FormatNextOccurrences(TimerSchedule schedule, int count, DateTime? now = null) { if (schedule == null) { throw new ArgumentNullException("schedule"); } IEnumerable<DateTime> nextOccurrences = schedule.GetNextOccurrences(count, now); StringBuilder builder = new StringBuilder(); builder.AppendLine(string.Format("The next {0} occurrences of the schedule will be:", count)); foreach (DateTime occurrence in nextOccurrences) { builder.AppendLine(occurrence.ToString()); } return builder.ToString(); }
internal static TimerSchedule Create(TimerTriggerAttribute attribute, INameResolver nameResolver) { TimerSchedule schedule = null; if (!string.IsNullOrEmpty(attribute.ScheduleExpression)) { string resolvedExpression = nameResolver.ResolveWholeString(attribute.ScheduleExpression); CronSchedule cronSchedule = null; TimeSpan periodTimespan; if (CronSchedule.TryCreate(resolvedExpression, out cronSchedule)) { schedule = cronSchedule; DateTime[] nextOccurrences = cronSchedule.InnerSchedule.GetNextOccurrences(DateTime.Now, DateTime.Now + TimeSpan.FromMinutes(1)).ToArray(); if (nextOccurrences.Length > 1) { // if there is more than one occurrence due in the next minute, // assume that this is a sub-minute constant schedule and disable // persistence attribute.UseMonitor = false; } } else if (TimeSpan.TryParse(resolvedExpression, out periodTimespan)) { schedule = new ConstantSchedule(periodTimespan); if (periodTimespan.TotalMinutes < 1) { // for very frequent constant schedules, we want to disable persistence attribute.UseMonitor = false; } } else { throw new ArgumentException("The schedule expression was not recognized as a valid cron expression or timespan string."); } } else { schedule = (TimerSchedule)Activator.CreateInstance(attribute.ScheduleType); } return(schedule); }
internal static TimerSchedule Create(TimerTriggerAttribute attribute, INameResolver nameResolver, ILogger logger) { TimerSchedule schedule = null; if (!string.IsNullOrEmpty(attribute.ScheduleExpression)) { string resolvedExpression = nameResolver.ResolveWholeString(attribute.ScheduleExpression); if (CronSchedule.TryCreate(resolvedExpression, out CronSchedule cronSchedule)) { schedule = cronSchedule; if (attribute.UseMonitor && ShouldDisableScheduleMonitor(cronSchedule, DateTime.Now)) { logger.LogDebug("UseMonitor changed to false based on schedule frequency."); attribute.UseMonitor = false; } } else if (TimeSpan.TryParse(resolvedExpression, out TimeSpan periodTimespan)) { schedule = new ConstantSchedule(periodTimespan); if (attribute.UseMonitor && periodTimespan.TotalMinutes < 1) { // for very frequent constant schedules, we want to disable persistence logger.LogDebug("UseMonitor changed to false based on schedule frequency."); attribute.UseMonitor = false; } } else { throw new ArgumentException(string.Format("The schedule expression '{0}' was not recognized as a valid cron expression or timespan string.", resolvedExpression)); } } else { schedule = (TimerSchedule)Activator.CreateInstance(attribute.ScheduleType); } return(schedule); }
/// <inheritdoc/> public override async Task<bool> IsPastDueAsync(string timerName, DateTime now, TimerSchedule schedule) { TimeSpan pastDueDuration = await GetPastDueDuration(timerName, now, schedule); return pastDueDuration != TimeSpan.Zero; }
/// <summary> /// Returns the <see cref="TimeSpan"/> duration that the specified timer is past due. /// </summary> /// <param name="timerName">The name of the timer.</param> /// <param name="now">The current time.</param> /// <param name="schedule">The <see cref="TimerSchedule"/>.</param> /// <returns>The duration the timer is past due.</returns> protected async Task<TimeSpan> GetPastDueDuration(string timerName, DateTime now, TimerSchedule schedule) { StatusEntry status = GetStatus(timerName); DateTime recordedNextOccurrence; if (status == null) { // If we've never recorded a status for this timer, write an initial // status entry. This ensures that for a new timer, we've captured a // status log for the next occurrence even though no occurrence has happened yet // (ensuring we don't miss an occurrence) DateTime nextOccurrence = schedule.GetNextOccurrence(now); await UpdateAsync(timerName, default(DateTime), nextOccurrence); recordedNextOccurrence = nextOccurrence; } else { // ensure that the schedule hasn't been updated since the last // time we checked, and if it has, update the status file DateTime expectedNextOccurrence = schedule.GetNextOccurrence(status.Last); if (status.Next != expectedNextOccurrence) { await UpdateAsync(timerName, status.Last, expectedNextOccurrence); } recordedNextOccurrence = status.Next; } if (now > recordedNextOccurrence) { // if now is after the last next occurrence we recorded, we know we've missed // at least one schedule instance and we are past due return now - recordedNextOccurrence; } else { // not past due return TimeSpan.Zero; } }
/// <summary> /// Checks whether the schedule is currently past due. /// </summary> /// <remarks> /// On startup, all schedules are checked to see if they are past due. Any /// timers that are past due will be executed immediately by default. Subclasses can /// change this behavior by inspecting the current time and schedule to determine /// whether it should be considered past due. /// </remarks> /// <param name="timerName">The name of the timer to check.</param> /// <param name="now">The time to check.</param> /// <param name="schedule">The <see cref="TimerSchedule"/></param> /// <param name="lastStatus">The last recorded status, or null if the status has never been recorded.</param> /// <returns>A non-zero <see cref="TimeSpan"/> if the schedule is past due, otherwise <see cref="TimeSpan.Zero"/>.</returns> public virtual async Task <TimeSpan> CheckPastDueAsync(string timerName, DateTime now, TimerSchedule schedule, ScheduleStatus lastStatus) { DateTime recordedNextOccurrence; if (lastStatus == null) { // If we've never recorded a status for this timer, write an initial // status entry. This ensures that for a new timer, we've captured a // status log for the next occurrence even though no occurrence has happened yet // (ensuring we don't miss an occurrence) DateTime nextOccurrence = schedule.GetNextOccurrence(now); lastStatus = new ScheduleStatus { Last = default(DateTime), Next = nextOccurrence }; await UpdateStatusAsync(timerName, lastStatus); recordedNextOccurrence = nextOccurrence; } else { // ensure that the schedule hasn't been updated since the last // time we checked, and if it has, update the status DateTime expectedNextOccurrence; if (lastStatus.Last == default(DateTime)) { // there have been no executions of the function yet, so compute // from now expectedNextOccurrence = schedule.GetNextOccurrence(now); } else { // compute the next occurrence from the last expectedNextOccurrence = schedule.GetNextOccurrence(lastStatus.Last); } if (lastStatus.Next != expectedNextOccurrence) { lastStatus.Next = expectedNextOccurrence; await UpdateStatusAsync(timerName, lastStatus); } recordedNextOccurrence = lastStatus.Next; } if (now > recordedNextOccurrence) { // if now is after the last next occurrence we recorded, we know we've missed // at least one schedule instance and we are past due return(now - recordedNextOccurrence); } else { // not past due return(TimeSpan.Zero); } }
/// <summary> /// Checks whether the schedule is currently past due. /// </summary> /// <remarks> /// On startup, all schedules are checked to see if they are past due. Any /// timers that are past due will be executed immediately by default. Subclasses can /// change this behavior by inspecting the current time and schedule to determine /// whether it should be considered past due. /// </remarks> /// <param name="timerName">The name of the timer to check.</param> /// <param name="now">The time to check.</param> /// <param name="schedule">The <see cref="TimerSchedule"/>.</param> /// <param name="lastStatus">The last recorded status, or null if the status has never been recorded.</param> /// <returns>A non-zero <see cref="TimeSpan"/> if the schedule is past due, otherwise <see cref="TimeSpan.Zero"/>.</returns> public virtual async Task <TimeSpan> CheckPastDueAsync(string timerName, DateTime now, TimerSchedule schedule, ScheduleStatus lastStatus) { DateTime recordedNextOccurrence; if (lastStatus == null) { // If we've never recorded a status for this timer, write an initial // status entry. This ensures that for a new timer, we've captured a // status log for the next occurrence even though no occurrence has happened yet // (ensuring we don't miss an occurrence) DateTime nextOccurrence = schedule.GetNextOccurrence(now); lastStatus = new ScheduleStatus { Last = default(DateTime), Next = nextOccurrence, LastUpdated = now }; await UpdateStatusAsync(timerName, lastStatus); recordedNextOccurrence = nextOccurrence; } else { DateTime expectedNextOccurrence; // Track the time that was used to create 'expectedNextOccurrence'. DateTime lastUpdated; if (lastStatus.Last != default(DateTime)) { // If we have a 'Last' value, we know that we used this to calculate 'Next' // in a previous invocation. expectedNextOccurrence = schedule.GetNextOccurrence(lastStatus.Last); lastUpdated = lastStatus.Last; } else if (lastStatus.LastUpdated != default(DateTime)) { // If the trigger has never fired, we won't have 'Last', but we will have // 'LastUpdated', which tells us the last time that we used to calculate 'Next'. expectedNextOccurrence = schedule.GetNextOccurrence(lastStatus.LastUpdated); lastUpdated = lastStatus.LastUpdated; } else { // If we do not have 'LastUpdated' or 'Last', we don't have enough information to // properly calculate 'Next', so we'll calculate it from the current time. expectedNextOccurrence = schedule.GetNextOccurrence(now); lastUpdated = now; } // ensure that the schedule hasn't been updated since the last // time we checked, and if it has, update the status to use the new schedule if (lastStatus.Next != expectedNextOccurrence) { // if the schedule has changed and the next occurrence is in the past, // recalculate it based on the current time as we don't want it to register // immediately as 'past due'. if (now > expectedNextOccurrence) { expectedNextOccurrence = schedule.GetNextOccurrence(now); lastUpdated = now; } lastStatus.Last = default(DateTime); lastStatus.Next = expectedNextOccurrence; lastStatus.LastUpdated = lastUpdated; await UpdateStatusAsync(timerName, lastStatus); } recordedNextOccurrence = lastStatus.Next; } if (now > recordedNextOccurrence) { // if now is after the last next occurrence we recorded, we know we've missed // at least one schedule instance and we are past due return(now - recordedNextOccurrence); } else { // not past due return(TimeSpan.Zero); } }
/// <summary> /// Determines whether the schedule is currently past due. /// </summary> /// <remarks> /// On startup, all schedules are checked to see if they are past due. Any /// timers that are past due will be executed immediately by default. Subclasses can /// change this behavior by inspecting the current time and schedule to determine /// whether it should be considered past due. /// </remarks> /// <param name="timerName">The name of the timer to check</param> /// <param name="now">The time to check</param> /// <param name="schedule">The <see cref="TimerSchedule"/></param> /// <returns>True if the schedule is past due, false otherwise.</returns> public abstract Task <bool> IsPastDueAsync(string timerName, DateTime now, TimerSchedule schedule);
/// <summary> /// Determines whether the schedule is currently past due. /// </summary> /// <remarks> /// On startup, all schedules are checked to see if they are past due. Any /// timers that are past due will be executed immediately by default. Subclasses can /// change this behavior by inspecting the current time and schedule to determine /// whether it should be considered past due. /// </remarks> /// <param name="timerName">The name of the timer to check</param> /// <param name="now">The time to check</param> /// <param name="schedule">The <see cref="TimerSchedule"/></param> /// <returns>True if the schedule is past due, false otherwise.</returns> public abstract Task<bool> IsPastDueAsync(string timerName, DateTime now, TimerSchedule schedule);
/// <inheritdoc/> public override async Task <bool> IsPastDueAsync(string timerName, DateTime now, TimerSchedule schedule) { TimeSpan pastDueDuration = await GetPastDueDuration(timerName, now, schedule); return(pastDueDuration != TimeSpan.Zero); }
/// <summary> /// Constructs a new instances /// </summary> /// <param name="schedule">The timer trigger schedule.</param> public TimerInfo(TimerSchedule schedule) { Schedule = schedule; }
/// <summary> /// Constructs a new instance /// </summary> /// <param name="schedule">The timer trigger schedule.</param> /// <param name="status">The current schedule status.</param> /// <param name="isPastDue">True if the schedule is past due, false otherwise.</param> public TimerInfo(TimerSchedule schedule, ScheduleStatus status, bool isPastDue = false) { Schedule = schedule; ScheduleStatus = status; IsPastDue = isPastDue; }