public async Task BindAsync_ReturnsExpectedTriggerData()
        {
            ParameterInfo parameter = GetType().GetMethod("TestTimerJob").GetParameters()[0];
            MethodInfo methodInfo = (MethodInfo)parameter.Member;
            string timerName = string.Format("{0}.{1}", methodInfo.DeclaringType.FullName, methodInfo.Name);

            Mock<ScheduleMonitor> mockScheduleMonitor = new Mock<ScheduleMonitor>(MockBehavior.Strict);
            ScheduleStatus status = new ScheduleStatus();
            mockScheduleMonitor.Setup(p => p.GetStatusAsync(timerName)).ReturnsAsync(status);

            TimerTriggerAttribute attribute = parameter.GetCustomAttribute<TimerTriggerAttribute>();
            TimersConfiguration config = new TimersConfiguration();
            config.ScheduleMonitor = mockScheduleMonitor.Object;
            TestTraceWriter trace = new TestTraceWriter();
            TimerTriggerBinding binding = new TimerTriggerBinding(parameter, attribute, config, trace);

            // when we bind to a non-TimerInfo (e.g. in a Dashboard invocation) a new
            // TimerInfo is created, with the ScheduleStatus populated
            FunctionBindingContext functionContext = new FunctionBindingContext(Guid.NewGuid(), CancellationToken.None, trace);
            ValueBindingContext context = new ValueBindingContext(functionContext, CancellationToken.None);
            TriggerData triggerData = (TriggerData)(await binding.BindAsync("", context));
            TimerInfo timerInfo = (TimerInfo)triggerData.ValueProvider.GetValue();
            Assert.Same(status, timerInfo.ScheduleStatus);

            // when we pass in a TimerInfo that is used
            TimerInfo expected = new TimerInfo(attribute.Schedule, status);
            triggerData = (TriggerData)(await binding.BindAsync(expected, context));
            timerInfo = (TimerInfo)triggerData.ValueProvider.GetValue();
            Assert.Same(expected, timerInfo);
        }
Beispiel #2
0
        /// <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
            {
                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);
                }

                // 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)
                {
                    lastStatus.Last = default(DateTime);
                    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
                };
                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;
            }
        }
        public async Task InvokeJobFunction_UpdatesScheduleMonitor()
        {
            DateTime lastOccurrence = DateTime.Now;
            DateTime nextOccurrence = _attribute.Schedule.GetNextOccurrence(lastOccurrence);

            ScheduleStatus status = new ScheduleStatus
            {
                Last = lastOccurrence,
                Next = nextOccurrence
            };
            _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 UpdateStatusAsync_MultipleFunctions()
        {
            // update status for 3 functions
            ScheduleStatus expected = new ScheduleStatus
            {
                Last = DateTime.Now.Subtract(TimeSpan.FromMinutes(5)),
                Next = DateTime.Now.AddMinutes(5)
            };
            for (int i = 0; i < 3; i++)
            {
                await _scheduleMonitor.UpdateStatusAsync(TestTimerName + i.ToString(), expected);
            }

            CloudBlockBlob[] statuses = _scheduleMonitor.TimerStatusDirectory.ListBlobs(useFlatBlobListing: true).Cast<CloudBlockBlob>().ToArray();
            Assert.Equal(3, statuses.Length);
            Assert.Equal("timers/testhostid/TestProgram.TestTimer0/status", statuses[0].Name);
            Assert.Equal("timers/testhostid/TestProgram.TestTimer1/status", statuses[1].Name);
            Assert.Equal("timers/testhostid/TestProgram.TestTimer2/status", statuses[2].Name);
        }
        public async Task GetStatusAsync_ReturnsExpectedStatus()
        {
            // no status, so should return null
            ScheduleStatus status = await _scheduleMonitor.GetStatusAsync(TestTimerName);
            Assert.Null(status);

            // update the status
            ScheduleStatus expected = new ScheduleStatus
            {
                Last = DateTime.Now.Subtract(TimeSpan.FromMinutes(5)),
                Next = DateTime.Now.AddMinutes(5)
            };
            await _scheduleMonitor.UpdateStatusAsync(TestTimerName, expected);

            // expect the status to be returned
            status = await _scheduleMonitor.GetStatusAsync(TestTimerName);
            Assert.Equal(expected.Last, status.Last);
            Assert.Equal(expected.Next, status.Next);
        }
Beispiel #7
0
        /// <inheritdoc/>
        public override async Task UpdateStatusAsync(string timerName, ScheduleStatus status)
        {
            string statusLine;

            using (StringWriter stringWriter = new StringWriter())
            {
                _serializer.Serialize(stringWriter, status);
                statusLine = stringWriter.ToString();
            }

            try
            {
                CloudBlockBlob statusBlob = GetStatusBlobReference(timerName);
                await statusBlob.UploadTextAsync(statusLine);
            }
            catch
            {
                // best effort
            }
        }
Beispiel #8
0
        /// <inheritdoc/>
        public override async Task UpdateStatusAsync(string timerName, ScheduleStatus status)
        {
            string statusLine;

            using (StringWriter stringWriter = new StringWriter())
            {
                _serializer.Serialize(stringWriter, status);
                statusLine = stringWriter.ToString();
            }

            try
            {
                CloudBlockBlob statusBlob = GetStatusBlobReference(timerName);
                await statusBlob.UploadTextAsync(statusLine);
            }
            catch (Exception ex)
            {
                // best effort
                _logger.LogError(ex, $"Function '{timerName}' failed to update the timer trigger status.");
            }
        }
        public async Task StartAsync_SchedulePastDue_InvokesJobFunctionImmediately()
        {
            // Set this to true to ensure that the function is only executed once
            // In this case, because it is run on startup due to being behind schedule,
            // it shouldn't be run twice.
            _attribute.RunOnStartup = true;

            ScheduleStatus status = new ScheduleStatus();
            _mockScheduleMonitor.Setup(p => p.GetStatusAsync(_testTimerName)).ReturnsAsync(status);

            DateTime lastOccurrence = default(DateTime);
            TimeSpan pastDueAmount = TimeSpan.FromMinutes(3);
            _mockScheduleMonitor.Setup(p => p.CheckPastDueAsync(_testTimerName, It.IsAny<DateTime>(), It.IsAny<TimerSchedule>(), status))
                .Callback<string, DateTime, TimerSchedule, ScheduleStatus>((mockTimerName, mockNow, mockNext, mockStatus) =>
                    {
                        lastOccurrence = mockNow;
                    })
                .ReturnsAsync(pastDueAmount);

            _mockScheduleMonitor.Setup(p => p.UpdateStatusAsync(_testTimerName, It.IsAny<ScheduleStatus>()))
                .Callback<string, ScheduleStatus>((mockTimerName, mockStatus) =>
                    {
                        Assert.Equal(lastOccurrence, mockStatus.Last);
                        DateTime expectedNextOccurrence = _attribute.Schedule.GetNextOccurrence(lastOccurrence);
                        Assert.Equal(expectedNextOccurrence, mockStatus.Next);
                    })
                .Returns(Task.FromResult(true));

            CancellationToken cancellationToken = new CancellationToken();
            await _listener.StartAsync(cancellationToken);

            TimerInfo timerInfo = (TimerInfo)_triggeredFunctionData.TriggerValue;
            Assert.Same(status, timerInfo.ScheduleStatus);
            Assert.True(timerInfo.IsPastDue);

            _mockTriggerExecutor.Verify(p => p.TryExecuteAsync(It.IsAny<TriggeredFunctionData>(), It.IsAny<CancellationToken>()), Times.Once());

            _listener.Dispose();
        }
Beispiel #10
0
        /// <inheritdoc/>
        public override Task UpdateStatusAsync(string timerName, ScheduleStatus status)
        {
            string statusLine;

            using (StringWriter stringWriter = new StringWriter())
            {
                _serializer.Serialize(stringWriter, status);
                statusLine = stringWriter.ToString();
            }

            string statusFileName = GetStatusFileName(timerName);

            try
            {
                File.WriteAllText(statusFileName, statusLine);
            }
            catch
            {
                // best effort
            }

            return(Task.FromResult(true));
        }
 /// <summary>
 /// Updates the schedule status for the specified timer.
 /// </summary>
 /// <param name="timerName">The name of the timer.</param>
 /// <param name="status">The new schedule status.</param>
 public abstract Task UpdateStatusAsync(string timerName, ScheduleStatus status);
        /// <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);
            }
        }
Beispiel #13
0
 /// <summary>
 /// Updates the schedule status for the specified timer.
 /// </summary>
 /// <param name="timerName">The name of the timer.</param>
 /// <param name="status">The new schedule status.</param>
 public abstract Task UpdateStatusAsync(string timerName, ScheduleStatus status);
        public async Task UpdateAsync_WritesStatusToFile()
        {
            DateTime now = DateTime.Now;
            DateTime expectedNext = DateTime.Now + TimeSpan.FromMinutes(1);

            File.Delete(_statusFile);
            Assert.False(File.Exists(_statusFile));

            ScheduleStatus status = new ScheduleStatus
            {
                Last = now,
                Next = expectedNext
            };
            await _monitor.UpdateStatusAsync(_testTimerName, status);

            Assert.True(File.Exists(_statusFile));
            VerifyScheduleStatus(now, expectedNext);
            
            now = expectedNext;
            expectedNext = now + TimeSpan.FromMinutes(1);
            status = new ScheduleStatus
            {
                Last = now,
                Next = expectedNext
            };
            await _monitor.UpdateStatusAsync(_testTimerName, status);
            VerifyScheduleStatus(now, expectedNext);
        }
        public async Task StartAsync_ScheduleNotPastDue_DoesNotInvokeJobFunctionImmediately()
        {
            ScheduleStatus status = new ScheduleStatus();
            _mockScheduleMonitor.Setup(p => p.GetStatusAsync(_testTimerName)).ReturnsAsync(status);

            TimeSpan pastDueAmount = TimeSpan.Zero;
            _mockScheduleMonitor.Setup(p => p.CheckPastDueAsync(_testTimerName, It.IsAny<DateTime>(), It.IsAny<TimerSchedule>(), status))
                .ReturnsAsync(pastDueAmount);

            CancellationToken cancellationToken = new CancellationToken();
            await _listener.StartAsync(cancellationToken);

            _mockTriggerExecutor.Verify(p => p.TryExecuteAsync(It.IsAny<TriggeredFunctionData>(), It.IsAny<CancellationToken>()), Times.Never());

            _listener.Dispose();
        }
        public async Task CheckPastDueAsync_ScheduleUpdate_UpdatesStatusFile()
        {
            Mock<TimerSchedule> mockSchedule = new Mock<TimerSchedule>(MockBehavior.Strict);
            DateTime now = DateTime.Now;
            DateTime next = now + TimeSpan.FromDays(2);
            ScheduleStatus status = new ScheduleStatus
            {
                Last = now,
                Next = next
            };
            await _monitor.UpdateStatusAsync(_testTimerName, status);

            mockSchedule.Setup(p => p.GetNextOccurrence(It.IsAny<DateTime>())).Returns(next);
            TimeSpan pastDueAmount = await _monitor.CheckPastDueAsync(_testTimerName, now, mockSchedule.Object, status);
            Assert.Equal(TimeSpan.Zero, pastDueAmount);

            // now adjust the schedule
            DateTime adjustedNext = next - TimeSpan.FromDays(1);
            mockSchedule.Setup(p => p.GetNextOccurrence(It.IsAny<DateTime>())).Returns(adjustedNext);
            pastDueAmount = await _monitor.CheckPastDueAsync(_testTimerName, now, mockSchedule.Object, status);
            Assert.Equal(TimeSpan.Zero, pastDueAmount);
            ScheduleStatus updatedStatus = await _monitor.GetStatusAsync(_testTimerName);
            Assert.Equal(adjustedNext, updatedStatus.Next);

            now = now + TimeSpan.FromHours(23);
            pastDueAmount = await _monitor.CheckPastDueAsync(_testTimerName, now, mockSchedule.Object, status);
            Assert.Equal(TimeSpan.Zero, pastDueAmount);

            now = now + TimeSpan.FromHours(1);
            pastDueAmount = await _monitor.CheckPastDueAsync(_testTimerName, now, mockSchedule.Object, status);
            Assert.Equal(TimeSpan.Zero, pastDueAmount);

            now = now + TimeSpan.FromHours(1);
            pastDueAmount = await _monitor.CheckPastDueAsync(_testTimerName, now, mockSchedule.Object, status);
            Assert.Equal(TimeSpan.FromHours(1), pastDueAmount);
        }
        public async Task CheckPastDueAsync_ReturnsExpectedResult()
        {
            Mock<TimerSchedule> mockSchedule = new Mock<TimerSchedule>(MockBehavior.Strict);
            DateTime now = DateTime.Now;
            DateTime next = now + TimeSpan.FromDays(1);
            ScheduleStatus status = new ScheduleStatus
            {
                Last = now,
                Next = next
            };

            mockSchedule.Setup(p => p.GetNextOccurrence(It.IsAny<DateTime>())).Returns(next);
            TimeSpan pastDueAmount = await _monitor.CheckPastDueAsync(_testTimerName, now, mockSchedule.Object, status);
            Assert.Equal(TimeSpan.Zero, pastDueAmount);

            now = now + TimeSpan.FromHours(23);
            pastDueAmount = await _monitor.CheckPastDueAsync(_testTimerName, now, mockSchedule.Object, status);
            Assert.Equal(TimeSpan.Zero, pastDueAmount);

            now = now + TimeSpan.FromHours(1);
            pastDueAmount = await _monitor.CheckPastDueAsync(_testTimerName, now, mockSchedule.Object, status);
            Assert.Equal(TimeSpan.Zero, pastDueAmount);

            now = now + TimeSpan.FromHours(1);
            pastDueAmount = await _monitor.CheckPastDueAsync(_testTimerName, now, mockSchedule.Object, status);
            Assert.Equal(TimeSpan.FromHours(1), pastDueAmount);
        }
 /// <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;
 }
        /// <inheritdoc/>
        public override Task UpdateStatusAsync(string timerName, ScheduleStatus status)
        {
            string statusLine;
            using (StringWriter stringWriter = new StringWriter())
            {
                _serializer.Serialize(stringWriter, status);
                statusLine = stringWriter.ToString();
            }

            string statusFileName = GetStatusFileName(timerName);
            try
            {
                File.WriteAllText(statusFileName, statusLine);
            }
            catch
            {
                // best effort
            }

            return Task.FromResult(true);
        }
        /// <inheritdoc/>
        public override async Task UpdateStatusAsync(string timerName, ScheduleStatus status)
        {
            string statusLine;
            using (StringWriter stringWriter = new StringWriter())
            {
                _serializer.Serialize(stringWriter, status);
                statusLine = stringWriter.ToString();
            }

            try
            {
                CloudBlockBlob statusBlob = GetStatusBlobReference(timerName);
                await statusBlob.UploadTextAsync(statusLine);
            }
            catch
            {
                // best effort
            }
        }