in src/WebJobs.Extensions/Extensions/Timers/Listener/TimerListener.cs [93:164]
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 DateTimeOffset.Now rather than DateTimeOffset.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.
DateTimeOffset now = DateTimeOffset.Now;
Logger.ScheduleAndTimeZone(_logger, _functionLogName, _schedule, TimeZoneInfo.Local.DisplayName);
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.GetSafeStatusAsync(_timerLookupName);
Logger.InitialStatus(_logger, _functionLogName, ScheduleStatus?.Last.ToString("o"), ScheduleStatus?.Next.ToString("o"), ScheduleStatus?.LastUpdated.ToString("o"));
TimeSpan pastDueDuration = await ScheduleMonitor.CheckPastDueAsync(_timerLookupName, now, _schedule, ScheduleStatus);
isPastDue = pastDueDuration != TimeSpan.Zero;
}
if (ScheduleStatus == null)
{
// no schedule status has been stored yet, so initialize
ScheduleStatus = new ScheduleStatus
{
Last = ScheduleMonitor.DefaultDateTime,
Next = _schedule.GetNextOccurrence(now.LocalDateTime),
LastUpdated = ScheduleMonitor.DefaultDateTime
};
}
// log the next several occurrences to console for visibility
string nextOccurrences = TimerInfo.FormatNextOccurrences(_schedule, 5);
Logger.NextOccurrences(_logger, _functionLogName, _schedule, nextOccurrences);
if (isPastDue)
{
// when we're past due, so we schedule an immediate invocation
StartupInvocation = new StartupInvocationContext
{
IsPastDue = true,
OriginalSchedule = ScheduleStatus.Next
};
StartTimer(StartupInvocation.Interval);
}
else if (_attribute.RunOnStartup)
{
// function is marked RunOnStartup, so we schedule an immediate invocation
StartupInvocation = new StartupInvocationContext
{
RunOnStartup = true
};
StartTimer(StartupInvocation.Interval);
}
else
{
// start the regular schedule
StartTimer(DateTimeOffset.Now);
}
_logger.LogDebug($"Timer listener started ({_functionLogName})");
}