public async Task StartAsync(CancellationToken cancellationToken) { ThrowIfDisposed(); if (_timer != null && _timer.Enabled) { throw new InvalidOperationException("The listener has already been started."); } // if schedule monitoring is enabled, record (or initialize) // the current schedule status bool isPastDue = false; // we use DateTime.Now rather than DateTime.UtcNow to allow the local machine to set the time zone. In Azure this will be // UTC by default, but can be configured to use any time zone if it makes scheduling easier. DateTime now = DateTime.Now; if (ScheduleMonitor != null) { // check to see if we've missed an occurrence since we last started. // If we have, invoke it immediately. ScheduleStatus = await ScheduleMonitor.GetStatusAsync(_timerName); _logger.LogDebug($"Function '{_timerName}' initial status: Last='{ScheduleStatus?.Last.ToString("o")}', Next='{ScheduleStatus?.Next.ToString("o")}', LastUpdated='{ScheduleStatus?.LastUpdated.ToString("o")}'"); TimeSpan pastDueDuration = await ScheduleMonitor.CheckPastDueAsync(_timerName, now, _schedule, ScheduleStatus); isPastDue = pastDueDuration != TimeSpan.Zero; } if (ScheduleStatus == null) { // no schedule status has been stored yet, so initialize ScheduleStatus = new ScheduleStatus { Last = default(DateTime), Next = _schedule.GetNextOccurrence(now) }; } if (isPastDue) { _logger.LogDebug($"Function '{_timerName}' is past due on startup. Executing now."); await InvokeJobFunction(now, isPastDue : true); } else if (_attribute.RunOnStartup) { // The job is configured to run immediately on startup _logger.LogDebug($"Function '{_timerName}' is configured to run on startup. Executing now."); await InvokeJobFunction(now, runOnStartup : true); } // log the next several occurrences to console for visibility string nextOccurrences = TimerInfo.FormatNextOccurrences(_schedule, 5); _logger.LogInformation(nextOccurrences); StartTimer(DateTime.Now); }
public async Task StartAsync(CancellationToken cancellationToken) { ThrowIfDisposed(); if (_timer != null && _timer.Enabled) { throw new InvalidOperationException("The listener has already been started."); } // if schedule monitoring is enabled, record (or initialize) // the current schedule status bool isPastDue = false; DateTime now = DateTime.Now; if (ScheduleMonitor != null) { // check to see if we've missed an occurrence since we last started. // If we have, invoke it immediately. ScheduleStatus = await ScheduleMonitor.GetStatusAsync(_timerName); TimeSpan pastDueDuration = await ScheduleMonitor.CheckPastDueAsync(_timerName, now, _schedule, ScheduleStatus); isPastDue = pastDueDuration != TimeSpan.Zero; } if (ScheduleStatus == null) { // no schedule status has been stored yet, so initialize ScheduleStatus = new ScheduleStatus { Last = default(DateTime), Next = _schedule.GetNextOccurrence(now) }; } if (isPastDue) { _trace.Verbose(string.Format("Function '{0}' is past due on startup. Executing now.", _timerName)); await InvokeJobFunction(now, isPastDue : true); } else if (_attribute.RunOnStartup) { // The job is configured to run immediately on startup _trace.Verbose(string.Format("Function '{0}' is configured to run on startup. Executing now.", _timerName)); await InvokeJobFunction(now, runOnStartup : true); } // log the next several occurrences to console for visibility string nextOccurrences = TimerInfo.FormatNextOccurrences(_schedule, 5); _trace.Verbose(nextOccurrences); StartTimer(DateTime.Now); }
public async Task InvokeJobFunction_UpdatesScheduleMonitor() { DateTime lastOccurrence = DateTime.Now; DateTime nextOccurrence = _schedule.GetNextOccurrence(lastOccurrence); _mockScheduleMonitor.Setup(p => p.UpdateStatusAsync(_testTimerName, It.Is <ScheduleStatus>(q => q.Last == lastOccurrence && q.Next == nextOccurrence))) .Returns(Task.FromResult(true)); await _listener.InvokeJobFunction(lastOccurrence, false); _listener.Dispose(); }
public async Task StartAsync(CancellationToken cancellationToken) { ThrowIfDisposed(); if (_timer != null && _timer.Enabled) { throw new InvalidOperationException("The listener has already been started."); } // See if we need to invoke the function immediately now, and if // so invoke it. DateTime now = DateTime.Now; if (_attribute.UseMonitor && await _scheduleMonitor.IsPastDueAsync(_timerName, now, _schedule)) { // If schedule monitoring is enabled for this timer job, check to see if we've // missed an occurrence since we last started. If we have, invoke it immediately. _trace.Verbose(string.Format("Function '{0}' is past due on startup. Executing now.", _timerName)); await InvokeJobFunction(now, true); } else if (_attribute.RunOnStartup) { // The job is configured to run immediately on startup _trace.Verbose(string.Format("Function '{0}' is configured to run on startup. Executing now.", _timerName)); await InvokeJobFunction(now); } // log the next several occurrences to console for visibility string nextOccurrences = TimerInfo.FormatNextOccurrences(_schedule, 5); _trace.Verbose(nextOccurrences); // start the timer now = DateTime.Now; DateTime nextOccurrence = _schedule.GetNextOccurrence(now); TimeSpan nextInterval = nextOccurrence - now; StartTimer(nextInterval); }
public void GetNextInterval_NextWithinDST_ReturnsExpectedValue(TimerSchedule schedule, TimeSpan expectedInterval) { // This only works with a DST-supported time zone, so throw a nice exception if (!TimeZoneInfo.Local.SupportsDaylightSavingTime) { throw new InvalidOperationException("This test will only pass if the time zone supports DST."); } // Running at 1:59 AM, i.e. one minute before the DST switch at 2 AM on 3/11 (Pacific Standard Time) // Note: this test uses Local time, so if you're running in a timezone where // DST doesn't transition the test might not be valid. var now = new DateTime(2018, 3, 11, 1, 59, 0, DateTimeKind.Local); var next = schedule.GetNextOccurrence(now); var interval = TimerListener.GetNextTimerInterval(next, now, schedule.AdjustForDST); Assert.Equal(expectedInterval, interval); }
public void GetNextInterval_NextAfterDST_ReturnsExpectedValue(TimerSchedule schedule, TimeSpan expectedInterval) { // This only works with a DST-supported time zone, so throw a nice exception if (!TimeZoneInfo.Local.SupportsDaylightSavingTime) { throw new InvalidOperationException("This test will only pass if the time zone supports DST."); } // Running on the Friday before the DST switch at 2 AM on 3/11 (Pacific Standard Time) // Note: this test uses Local time, so if you're running in a timezone where // DST doesn't transition the test might not be valid. // The input schedules will run after DST changes. For some (Cron), they will subtract // an hour to account for the shift. For others (Constant), they will not. var now = new DateTime(2018, 3, 9, 18, 0, 0, DateTimeKind.Local); var next = schedule.GetNextOccurrence(now); var interval = TimerListener.GetNextTimerInterval(next, now, schedule.AdjustForDST); // One week is normally 168 hours, but it's 167 hours across DST Assert.Equal(interval, expectedInterval); }
/// <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; } }