internal async Task InvokeJobFunction()

in src/WebJobs.Extensions/Extensions/Timers/Listener/TimerListener.cs [292:387]


        internal async Task InvokeJobFunction(DateTimeOffset invocationTime, bool isPastDue = false, bool runOnStartup = false, DateTimeOffset? originalSchedule = null)
        {
            try
            {
                await _invocationLock.WaitAsync();

                // if Cancel, Stop, or Dispose have been called, skip the invocation
                // since we're stopping the listener
                if (_cancellationTokenSource.IsCancellationRequested)
                {
                    return;
                }

                CancellationToken token = _cancellationTokenSource.Token;
                ScheduleStatus timerInfoStatus = null;
                if (ScheduleMonitor != null)
                {
                    timerInfoStatus = ScheduleStatus;
                }
                TimerInfo timerInfo = new TimerInfo(_schedule, timerInfoStatus, isPastDue);

                // Build up trigger details that will be logged if the timer is running at a different time 
                // than originally scheduled.
                IDictionary<string, string> details = new Dictionary<string, string>();
                if (isPastDue)
                {
                    details[UnscheduledInvocationReasonKey] = "IsPastDue";
                }
                else if (runOnStartup)
                {
                    details[UnscheduledInvocationReasonKey] = "RunOnStartup";
                }

                if (originalSchedule.HasValue)
                {
                    details[OriginalScheduleKey] = originalSchedule.Value.ToString("o");
                }

                try
                {
                    if (timerInfo?.ScheduleStatus is not null)
                    {
                        details[ScheduleStatusKey] = JsonConvert.SerializeObject(timerInfo.ScheduleStatus, _serializerSettings);
                    }
                }
                catch
                {
                    // best effort
                }

                TriggeredFunctionData input = new TriggeredFunctionData
                {
                    TriggerValue = timerInfo,
                    TriggerDetails = details
                };

                try
                {
                    await _executor.TryExecuteAsync(input, token);
                }
                catch
                {
                    // We don't want any function errors to stop the execution
                    // schedule. Invocation errors are already logged.
                }

                // If the trigger fired before it was officially scheduled (likely under 1 second due to clock skew),
                // adjust the invocation time forward for the purposes of calculating the next occurrence.
                // Without this, it's possible to set the 'Next' value to the same time twice in a row, 
                // which results in duplicate triggers if the site restarts.
                DateTimeOffset adjustedInvocationTime = invocationTime;
                if (!isPastDue && !runOnStartup && ScheduleStatus?.Next > invocationTime)
                {
                    adjustedInvocationTime = ScheduleStatus.Next;
                }

                // Create the Last value with the adjustedInvocationTime; otherwise, the listener will
                // consider this a schedule change when the host next starts.
                ScheduleStatus = new ScheduleStatus
                {
                    Last = adjustedInvocationTime.LocalDateTime,
                    Next = _schedule.GetNextOccurrence(adjustedInvocationTime.LocalDateTime),
                    LastUpdated = adjustedInvocationTime.LocalDateTime
                };

                if (ScheduleMonitor != null)
                {
                    await ScheduleMonitor.UpdateStatusAsync(_timerLookupName, ScheduleStatus);
                    _logger.LogDebug($"Function '{_functionLogName}' updated status: Last='{ScheduleStatus.Last.ToString("o")}', Next='{ScheduleStatus.Next.ToString("o")}', LastUpdated='{ScheduleStatus.LastUpdated.ToString("o")}'");
                }
            }
            finally
            {
                _invocationLock.Release();
            }
        }