public async Task ScheduleCommandForRetryIfEligibleAsyncSchedulesInTheFuture() { var initialPublishCount = 2; var publishTimes = new List <Instant>(); var mockPublisher = new Mock <ICommandPublisher <SubmitOrderForProduction> >(); var fakeClock = new FakeClock(Instant.FromDateTimeUtc(new DateTime(2011, 11, 14, 03, 56, 22, DateTimeKind.Utc))); var functionBase = new TestWebJobFunction(null); var thresholds = new CommandRetryThresholds { CommandRetryMaxCount = 4, CommandRetryExponentialSeconds = 5, CommandRetryJitterSeconds = 1 }; var command = new SubmitOrderForProduction { PreviousAttemptsToHandleCount = initialPublishCount }; var result = await functionBase.ScheduleCommandForRetryIfEligibleAsync(command, thresholds, new Random(), fakeClock, mockPublisher.Object); mockPublisher .Setup(publisher => publisher.PublishAsync(It.Is <SubmitOrderForProduction>(value => value == command), It.Is <Instant?>(value => value.HasValue))) .Returns(Task.CompletedTask) .Callback <SubmitOrderForProduction, Instant?>((publishedCommand, publishTime) => publishTimes.Add(publishTime.Value)); result.Should().BeTrue("because the command should be recognized as schedulable"); publishTimes.Count(time => time > fakeClock.GetCurrentInstant()).Should().Be(publishTimes.Count, "because all of the scheduled publish times should have been in the future"); }
public TestOrderSubmitterFunctions(JsonSerializer jsonSerializer, CommandRetryThresholds retryThresholds, IClock clock, IOrderSubmitter orderSubmitter, ICommandPublisher <SubmitOrderForProduction> submitOrderForProductionPublisher, ICommandPublisher <NotifyOfFatalFailure> notifyOfFatalFailurePublisher, IEventPublisher <EventBase> eventPublisher, ILogger logger, IDisposable lifetimeScope) : base(jsonSerializer, retryThresholds, clock, orderSubmitter, submitOrderForProductionPublisher, notifyOfFatalFailurePublisher, eventPublisher, logger, lifetimeScope) { }
public async Task ScheduleCommandForRetryIfEligibleAsyncDoesNotScheduleIfRetryCountIsExceeded() { var functionBase = new TestWebJobFunction(null); var thresholds = new CommandRetryThresholds { CommandRetryMaxCount = 2 }; var command = new SubmitOrderForProduction { PreviousAttemptsToHandleCount = 3 }; var result = await functionBase.ScheduleCommandForRetryIfEligibleAsync(command, thresholds, new Random(), Mock.Of <IClock>(), Mock.Of <ICommandPublisher <SubmitOrderForProduction> >()); result.Should().BeFalse("because the command had no remaining retries"); }
/// <summary> /// Initializes a new instance of the <see cref="NotifierFunctions"/> class. /// </summary> /// /// <param name="jsonSerializer">The JSON serializer to use for parsing messages from the queue.</param> /// <param name="retryThresholds">The thresholds for use when retrying commands on a backoff policy.</param> /// <param name="clock">The clock to use for time-realated operations.</param> /// <param name="notifier">The notifier to use for sending notifications.</param> /// <param name="notifyOfFatalFailurePublisher">The publisher to use for sending of the <see cref="NotifyOfFatalFailure" /> command.</param> /// <param name="eventPublisher">The publisher to use for the sending of events.</param> /// <param name="logger">The logger to be used for emitting telemetry from the controller.</param> /// <param name="lifetimeScope">The lifetime scope associated with the class; this will be disposed when the class is by the base class.</param> /// public NotifierFunctions(JsonSerializer jsonSerializer, CommandRetryThresholds retryThresholds, IClock clock, INotifier notifier, ICommandPublisher <NotifyOfFatalFailure> notifyOfFatalFailurePublisher, IEventPublisher <EventBase> eventPublisher, ILogger logger, IDisposable lifetimeScope) : base(lifetimeScope) { this.jsonSerializer = jsonSerializer ?? throw new ArgumentNullException(nameof(jsonSerializer)); this.retryThresholds = retryThresholds ?? throw new ArgumentNullException(nameof(retryThresholds)); this.clock = clock ?? throw new ArgumentNullException(nameof(clock)); this.notifier = notifier ?? throw new ArgumentNullException(nameof(notifier)); this.notifyOfFatalFailurePublisher = notifyOfFatalFailurePublisher ?? throw new ArgumentNullException(nameof(notifyOfFatalFailurePublisher)); this.eventPublisher = eventPublisher ?? throw new ArgumentNullException(nameof(eventPublisher)); this.Log = logger ?? throw new ArgumentNullException(nameof(logger)); this.rng = new Random(); }
public async Task ScheduleCommandForRetryIfEligibleAsyncSchedulesTheCommand() { var initialPublishCount = 2; var mockPublisher = new Mock <ICommandPublisher <SubmitOrderForProduction> >(); var fakeClock = new FakeClock(Instant.FromDateTimeUtc(new DateTime(2011, 11, 14, 03, 56, 22, DateTimeKind.Utc))); var functionBase = new TestWebJobFunction(null); var thresholds = new CommandRetryThresholds { CommandRetryMaxCount = 4, CommandRetryExponentialSeconds = 1, CommandRetryJitterSeconds = 1 }; var command = new SubmitOrderForProduction { PreviousAttemptsToHandleCount = initialPublishCount }; var result = await functionBase.ScheduleCommandForRetryIfEligibleAsync(command, thresholds, new Random(), fakeClock, mockPublisher.Object); result.Should().BeTrue("because the command should be recognized as schedulable"); command.PreviousAttemptsToHandleCount.Should().Be(initialPublishCount + 1, "because the publish should should have been incremented"); mockPublisher.Verify(publisher => publisher.PublishAsync(It.Is <SubmitOrderForProduction>(value => value == command), It.Is <Instant?>(value => value.HasValue)), Times.Once, "The command should have been published"); }
/// <summary> /// Initializes a new instance of the <see cref="OrderProcessorFunctions"/> class. /// </summary> /// /// <param name="jsonSerializer">The JSON serializer to use for parsing messages from the queue.</param> /// <param name="retryThresholds">The thresholds for use when retrying commands on a backoff policy.</param> /// <param name="clock">The clock to use for time-realated operations.</param> /// <param name="orderProcessor">The processor to use for the order associated with a ProcesOrder command,</param> /// <param name="processOrderPublisher">The publisher to use for sending of the <see cref="ProcessOrder" /> command</param> /// <param name="submitOrderForProductionPublisher">The publisher to use for sending of the <see cref="SubmitOrderForProduction" /> command</param> /// <param name="notifyOfFatalFailurePublisher">The publisher to use for sending of the <see cref="NotifyOfFatalFailure" /> command.</param> /// <param name="eventPublisher">The publisher to use for the sending of events.</param> /// <param name="logger">The logger to be used for emitting telemetry from the controller.</param> /// <param name="lifetimeScope">The lifetime scope associated with the class; this will be disposed when the class is by the base class..</param> /// public OrderProcessorFunctions(JsonSerializer jsonSerializer, CommandRetryThresholds retryThresholds, IClock clock, IOrderProcessor orderProcessor, ICommandPublisher <ProcessOrder> processOrderPublisher, ICommandPublisher <SubmitOrderForProduction> submitOrderForProductionPublisher, ICommandPublisher <NotifyOfFatalFailure> notifyOfFatalFailurePublisher, IEventPublisher <EventBase> eventPublisher, ILogger logger, IDisposable lifetimeScope) : base(lifetimeScope) { this.jsonSerializer = jsonSerializer ?? throw new ArgumentNullException(nameof(jsonSerializer)); this.retryThresholds = retryThresholds ?? throw new ArgumentNullException(nameof(retryThresholds)); this.clock = clock ?? throw new ArgumentNullException(nameof(clock)); this.orderProcessor = orderProcessor ?? throw new ArgumentNullException(nameof(orderProcessor)); this.processOrderPublisher = processOrderPublisher ?? throw new ArgumentNullException(nameof(processOrderPublisher)); this.submitOrderForProductionPublisher = submitOrderForProductionPublisher ?? throw new ArgumentNullException(nameof(submitOrderForProductionPublisher)); this.notifyOfFatalFailurePublisher = notifyOfFatalFailurePublisher ?? throw new ArgumentNullException(nameof(notifyOfFatalFailurePublisher)); this.eventPublisher = eventPublisher ?? throw new ArgumentNullException(nameof(eventPublisher)); this.Log = logger ?? throw new ArgumentNullException(nameof(logger)); this.rng = new Random(); }
public new Task <bool> ScheduleCommandForRetryIfEligibleAsync <TCommand>(TCommand command, CommandRetryThresholds retryThresholds, Random rng, IClock clock, ICommandPublisher <TCommand> commandPublisher) where TCommand : CommandBase => base.ScheduleCommandForRetryIfEligibleAsync(command, retryThresholds, rng, clock, commandPublisher);
/// <summary> /// Schedules a command for retry, if it is eligible for doing so. /// </summary> /// /// <typeparam name="TCommand">The type of the command to be scheduled.</typeparam> /// /// <param name="command">The command to be .</param> /// <param name="retryThresholds">The retry thresholds to apply when assigning the retry backoff.</param> /// <param name="rng">The random number generator to use for computing retry jitter.</param> /// <param name="clock">The clock to use for calculating the retry delay.</param> /// <param name="commandPublisher">The publisher to use for scheduling the command to be retried.</param> /// /// <returns><c>true</c> if the command was scheduled for retry; <c>false</c> if it was not eligible for retry.</returns> /// /// <remarks> /// If scheduled to be retried, the incoming <paramref name="command"/> will have it's <see cref="CommandBase.PreviousAttemptsToHandleCount" /> /// incremented by this method. Otherwise, it will be unaltered. /// </remarks> /// protected virtual async Task <bool> ScheduleCommandForRetryIfEligibleAsync <TCommand>(TCommand command, CommandRetryThresholds retryThresholds, Random rng, IClock clock, ICommandPublisher <TCommand> commandPublisher) where TCommand : CommandBase { if (command == null) { throw new ArgumentNullException(nameof(command)); } if (retryThresholds == null) { throw new ArgumentNullException(nameof(retryThresholds)); } if (rng == null) { throw new ArgumentNullException(nameof(rng)); } if (clock == null) { throw new ArgumentNullException(nameof(clock)); } if (commandPublisher == null) { throw new ArgumentNullException(nameof(commandPublisher)); } // If the command is out of retries, then take no further action. var attempts = command.PreviousAttemptsToHandleCount; if (attempts >= retryThresholds.CommandRetryMaxCount) { return(false); } ++attempts; command.PreviousAttemptsToHandleCount = attempts; // Publish the retry using an exponential backoff with random jitter. var retryInstant = clock.GetCurrentInstant().Plus(Duration.FromSeconds((Math.Pow(2, attempts) * retryThresholds.CommandRetryExponentialSeconds) + (rng.NextDouble() * retryThresholds.CommandRetryJitterSeconds))); await commandPublisher.PublishAsync(command, retryInstant); return(true); }