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);
        }
Esempio n. 2
0
        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);
        }
Esempio n. 3
0
        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();
        }
Esempio n. 4
0
        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;
            }   
        }