From f7fee379e0727b489a4a5854103f95bc377e7026 Mon Sep 17 00:00:00 2001 From: hippy Date: Thu, 1 Feb 2024 23:10:17 -0800 Subject: [PATCH] dh: Add Procrastinating after N requests handler --- .../ProcrastinatingAfterNRequestsHandler.cs | 57 +++++++++++++++ ...ocrastinatingAfterNRequestsHandlerTests.cs | 69 +++++++++++++++++++ 2 files changed, 126 insertions(+) create mode 100644 src/rm.DelegatingHandlers/ProcrastinatingAfterNRequestsHandler.cs create mode 100644 tests/rm.DelegatingHandlersTest/ProcrastinatingAfterNRequestsHandlerTests.cs diff --git a/src/rm.DelegatingHandlers/ProcrastinatingAfterNRequestsHandler.cs b/src/rm.DelegatingHandlers/ProcrastinatingAfterNRequestsHandler.cs new file mode 100644 index 0000000..4ef7d8f --- /dev/null +++ b/src/rm.DelegatingHandlers/ProcrastinatingAfterNRequestsHandler.cs @@ -0,0 +1,57 @@ +using System; +using System.Net.Http; +using System.Threading; +using System.Threading.Tasks; + +namespace rm.DelegatingHandlers; + +/// +/// Causes delay after N requests to induce http timeouts. +/// +public class ProcrastinatingAfterNRequestsHandler : DelegatingHandler +{ + private readonly IProcrastinatingAfterNRequestsHandlerSettings procrastinatingAfterNRequestsHandlerSettings; + + private long n; + + /// + public ProcrastinatingAfterNRequestsHandler( + IProcrastinatingAfterNRequestsHandlerSettings procrastinatingAfterNRequestsHandlerSettings) + { + this.procrastinatingAfterNRequestsHandlerSettings = procrastinatingAfterNRequestsHandlerSettings + ?? throw new ArgumentNullException(nameof(procrastinatingAfterNRequestsHandlerSettings)); + } + + protected override async Task SendAsync( + HttpRequestMessage request, + CancellationToken cancellationToken) + { + if (Interlocked.Read(ref n) >= procrastinatingAfterNRequestsHandlerSettings.N) + { + await Task.Delay(procrastinatingAfterNRequestsHandlerSettings.DelayInMilliseconds, cancellationToken) + .ConfigureAwait(false); + } + + var response = await base.SendAsync(request, cancellationToken) + .ConfigureAwait(false); + + if (Interlocked.Read(ref n) < procrastinatingAfterNRequestsHandlerSettings.N) + { + Interlocked.Increment(ref n); + } + + return response; + } +} + +public interface IProcrastinatingAfterNRequestsHandlerSettings +{ + long N { get; } + int DelayInMilliseconds { get; } +} + +public record class ProcrastinatingAfterNRequestsHandlerSettings : IProcrastinatingAfterNRequestsHandlerSettings +{ + public long N { get; init; } + public int DelayInMilliseconds { get; init; } +} diff --git a/tests/rm.DelegatingHandlersTest/ProcrastinatingAfterNRequestsHandlerTests.cs b/tests/rm.DelegatingHandlersTest/ProcrastinatingAfterNRequestsHandlerTests.cs new file mode 100644 index 0000000..3e76b02 --- /dev/null +++ b/tests/rm.DelegatingHandlersTest/ProcrastinatingAfterNRequestsHandlerTests.cs @@ -0,0 +1,69 @@ +using System.Diagnostics; +using System.Net.Http; +using AutoFixture; +using AutoFixture.AutoMoq; +using NUnit.Framework; +using rm.DelegatingHandlers; + +namespace rm.DelegatingHandlersTest; + +[TestFixture] +public class ProcrastinatingAfterNRequestsHandlerTests +{ + [Retry(5)] + [Test] + public async Task Procrastinates() + { + var fixture = new Fixture().Customize(new AutoMoqCustomization()); + + var n = 5; + var delayInMilliseconds = 25; + var procrastinatingAfterNRequestsHandler = new ProcrastinatingAfterNRequestsHandler( + new ProcrastinatingAfterNRequestsHandlerSettings + { + N = n, + DelayInMilliseconds = delayInMilliseconds, + }); + + using var invoker = HttpMessageInvokerFactory.Create( + fixture.Create(), procrastinatingAfterNRequestsHandler); + + for (int i = 0; i < n; i++) + { + using var _ = await invoker.SendAsync(fixture.Create(), CancellationToken.None); + } + using var requestMessage = fixture.Create(); + var stopwatch = Stopwatch.StartNew(); + using var response = await invoker.SendAsync(requestMessage, CancellationToken.None); + stopwatch.Stop(); + Console.WriteLine(stopwatch.ElapsedMilliseconds); + + Assert.GreaterOrEqual(stopwatch.ElapsedMilliseconds, delayInMilliseconds); + } + + [Test] + public async Task Does_Not_Procrastinate() + { + var fixture = new Fixture().Customize(new AutoMoqCustomization()); + + var n = 5; + var delayInMilliseconds = 1000; + var procrastinatingAfterNRequestsHandler = new ProcrastinatingAfterNRequestsHandler( + new ProcrastinatingAfterNRequestsHandlerSettings + { + N = n, + DelayInMilliseconds = delayInMilliseconds, + }); + + using var invoker = HttpMessageInvokerFactory.Create( + fixture.Create(), procrastinatingAfterNRequestsHandler); + + using var requestMessage = fixture.Create(); + var stopwatch = Stopwatch.StartNew(); + using var response = await invoker.SendAsync(requestMessage, CancellationToken.None); + stopwatch.Stop(); + Console.WriteLine(stopwatch.ElapsedMilliseconds); + + Assert.Less(stopwatch.ElapsedMilliseconds, delayInMilliseconds); + } +}