From 0786cb00872274167a1b5cc89ab7a1738bd93c79 Mon Sep 17 00:00:00 2001 From: AMER JUSUPOVIC Date: Thu, 22 Jun 2023 14:58:18 -0500 Subject: [PATCH 01/13] first draft using executioncontext.suppressflow to prevent httpcontext use in tryrefreshasync --- .../AzureAppConfigurationProvider.cs | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/src/Microsoft.Extensions.Configuration.AzureAppConfiguration/AzureAppConfigurationProvider.cs b/src/Microsoft.Extensions.Configuration.AzureAppConfiguration/AzureAppConfigurationProvider.cs index f8142736..36d60be3 100644 --- a/src/Microsoft.Extensions.Configuration.AzureAppConfiguration/AzureAppConfigurationProvider.cs +++ b/src/Microsoft.Extensions.Configuration.AzureAppConfiguration/AzureAppConfigurationProvider.cs @@ -405,7 +405,12 @@ public async Task TryRefreshAsync(CancellationToken cancellationToken) { try { - await RefreshAsync(cancellationToken).ConfigureAwait(false); + using (var flowControl = ExecutionContext.SuppressFlow()) + { + await RefreshAsync(cancellationToken).ConfigureAwait(false); + + flowControl.Undo(); + } } catch (RequestFailedException e) { From 4f41c90d965d783a8f6173e28ee0736a1d971cd9 Mon Sep 17 00:00:00 2001 From: AMER JUSUPOVIC Date: Tue, 27 Jun 2023 11:46:24 -0500 Subject: [PATCH 02/13] working draft for suppressflow --- .../AzureAppConfigurationRefreshMiddleware.cs | 14 +++++++++++--- .../AzureAppConfigurationProvider.cs | 7 +------ 2 files changed, 12 insertions(+), 9 deletions(-) diff --git a/src/Microsoft.Azure.AppConfiguration.AspNetCore/AzureAppConfigurationRefreshMiddleware.cs b/src/Microsoft.Azure.AppConfiguration.AspNetCore/AzureAppConfigurationRefreshMiddleware.cs index 53e6370c..ffb8ec5b 100644 --- a/src/Microsoft.Azure.AppConfiguration.AspNetCore/AzureAppConfigurationRefreshMiddleware.cs +++ b/src/Microsoft.Azure.AppConfiguration.AspNetCore/AzureAppConfigurationRefreshMiddleware.cs @@ -4,6 +4,7 @@ using Microsoft.AspNetCore.Http; using Microsoft.Extensions.Configuration.AzureAppConfiguration; using System.Collections.Generic; +using System.Threading; using System.Threading.Tasks; namespace Microsoft.Azure.AppConfiguration.AspNetCore @@ -24,11 +25,18 @@ public AzureAppConfigurationRefreshMiddleware(RequestDelegate next, IConfigurati public async Task InvokeAsync(HttpContext context) { - foreach (var refresher in Refreshers) + using (var flowControl = ExecutionContext.SuppressFlow()) { - _ = refresher.TryRefreshAsync(); + foreach (var refresher in Refreshers) + { + await Task.Run(() => + { + _ = refresher.TryRefreshAsync(); + }) + .ConfigureAwait(false); + } } - + // Call the next delegate/middleware in the pipeline await _next(context).ConfigureAwait(false); } diff --git a/src/Microsoft.Extensions.Configuration.AzureAppConfiguration/AzureAppConfigurationProvider.cs b/src/Microsoft.Extensions.Configuration.AzureAppConfiguration/AzureAppConfigurationProvider.cs index 36d60be3..f8142736 100644 --- a/src/Microsoft.Extensions.Configuration.AzureAppConfiguration/AzureAppConfigurationProvider.cs +++ b/src/Microsoft.Extensions.Configuration.AzureAppConfiguration/AzureAppConfigurationProvider.cs @@ -405,12 +405,7 @@ public async Task TryRefreshAsync(CancellationToken cancellationToken) { try { - using (var flowControl = ExecutionContext.SuppressFlow()) - { - await RefreshAsync(cancellationToken).ConfigureAwait(false); - - flowControl.Undo(); - } + await RefreshAsync(cancellationToken).ConfigureAwait(false); } catch (RequestFailedException e) { From 2a5f671677c7a452126bb6837b5eb178e850c14b Mon Sep 17 00:00:00 2001 From: AMER JUSUPOVIC Date: Tue, 27 Jun 2023 13:21:00 -0500 Subject: [PATCH 03/13] add to functions worker as well --- .../AzureAppConfigurationRefreshMiddleware.cs | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/src/Microsoft.Azure.AppConfiguration.Functions.Worker/AzureAppConfigurationRefreshMiddleware.cs b/src/Microsoft.Azure.AppConfiguration.Functions.Worker/AzureAppConfigurationRefreshMiddleware.cs index ab309e94..16b4a1cc 100644 --- a/src/Microsoft.Azure.AppConfiguration.Functions.Worker/AzureAppConfigurationRefreshMiddleware.cs +++ b/src/Microsoft.Azure.AppConfiguration.Functions.Worker/AzureAppConfigurationRefreshMiddleware.cs @@ -6,6 +6,7 @@ using Microsoft.Azure.Functions.Worker.Middleware; using Microsoft.Extensions.Configuration.AzureAppConfiguration; using System.Collections.Generic; +using System.Threading; using System.Threading.Tasks; namespace Microsoft.Azure.AppConfiguration.Functions.Worker @@ -24,9 +25,16 @@ public AzureAppConfigurationRefreshMiddleware(IConfigurationRefresherProvider re public async Task Invoke(FunctionContext context, FunctionExecutionDelegate next) { - foreach (IConfigurationRefresher refresher in Refreshers) + using (var flowControl = ExecutionContext.SuppressFlow()) { - _ = refresher.TryRefreshAsync(); + foreach (IConfigurationRefresher refresher in Refreshers) + { + await Task.Run(() => + { + _ = refresher.TryRefreshAsync(); + }) + .ConfigureAwait(false); + } } await next(context).ConfigureAwait(false); From 3e00202de04e39cbfc585c4ce8838326af36c82c Mon Sep 17 00:00:00 2001 From: AMER JUSUPOVIC Date: Tue, 27 Jun 2023 18:58:36 -0500 Subject: [PATCH 04/13] fix http 500 error --- .../AzureAppConfigurationRefreshMiddleware.cs | 10 +++------- .../AzureAppConfigurationRefreshMiddleware.cs | 7 +------ 2 files changed, 4 insertions(+), 13 deletions(-) diff --git a/src/Microsoft.Azure.AppConfiguration.AspNetCore/AzureAppConfigurationRefreshMiddleware.cs b/src/Microsoft.Azure.AppConfiguration.AspNetCore/AzureAppConfigurationRefreshMiddleware.cs index ffb8ec5b..111f534f 100644 --- a/src/Microsoft.Azure.AppConfiguration.AspNetCore/AzureAppConfigurationRefreshMiddleware.cs +++ b/src/Microsoft.Azure.AppConfiguration.AspNetCore/AzureAppConfigurationRefreshMiddleware.cs @@ -27,16 +27,12 @@ public async Task InvokeAsync(HttpContext context) { using (var flowControl = ExecutionContext.SuppressFlow()) { - foreach (var refresher in Refreshers) + foreach (IConfigurationRefresher refresher in Refreshers) { - await Task.Run(() => - { - _ = refresher.TryRefreshAsync(); - }) - .ConfigureAwait(false); + _ = Task.Run(() => refresher.TryRefreshAsync()); } } - + // Call the next delegate/middleware in the pipeline await _next(context).ConfigureAwait(false); } diff --git a/src/Microsoft.Azure.AppConfiguration.Functions.Worker/AzureAppConfigurationRefreshMiddleware.cs b/src/Microsoft.Azure.AppConfiguration.Functions.Worker/AzureAppConfigurationRefreshMiddleware.cs index 16b4a1cc..af045e13 100644 --- a/src/Microsoft.Azure.AppConfiguration.Functions.Worker/AzureAppConfigurationRefreshMiddleware.cs +++ b/src/Microsoft.Azure.AppConfiguration.Functions.Worker/AzureAppConfigurationRefreshMiddleware.cs @@ -1,7 +1,6 @@ // Copyright (c) Microsoft Corporation. // Licensed under the MIT license. // - using Microsoft.Azure.Functions.Worker; using Microsoft.Azure.Functions.Worker.Middleware; using Microsoft.Extensions.Configuration.AzureAppConfiguration; @@ -29,11 +28,7 @@ public async Task Invoke(FunctionContext context, FunctionExecutionDelegate next { foreach (IConfigurationRefresher refresher in Refreshers) { - await Task.Run(() => - { - _ = refresher.TryRefreshAsync(); - }) - .ConfigureAwait(false); + _ = Task.Run(() => refresher.TryRefreshAsync()); } } From 0f36f38c38c4e530dc8184d669e0e3e7c3520ac1 Mon Sep 17 00:00:00 2001 From: AMER JUSUPOVIC Date: Fri, 30 Jun 2023 15:28:16 -0500 Subject: [PATCH 05/13] add comment explaining suppressflow change --- .../AzureAppConfigurationRefreshMiddleware.cs | 3 +++ .../AzureAppConfigurationRefreshMiddleware.cs | 3 +++ 2 files changed, 6 insertions(+) diff --git a/src/Microsoft.Azure.AppConfiguration.AspNetCore/AzureAppConfigurationRefreshMiddleware.cs b/src/Microsoft.Azure.AppConfiguration.AspNetCore/AzureAppConfigurationRefreshMiddleware.cs index 111f534f..4dfc6e58 100644 --- a/src/Microsoft.Azure.AppConfiguration.AspNetCore/AzureAppConfigurationRefreshMiddleware.cs +++ b/src/Microsoft.Azure.AppConfiguration.AspNetCore/AzureAppConfigurationRefreshMiddleware.cs @@ -25,6 +25,9 @@ public AzureAppConfigurationRefreshMiddleware(RequestDelegate next, IConfigurati public async Task InvokeAsync(HttpContext context) { + // + // Configuration refresh is meant to execute as an isolated background task. + // To prevent access of request-based resources, such as HttpContext, we suppress the execution context within the refresh operation. using (var flowControl = ExecutionContext.SuppressFlow()) { foreach (IConfigurationRefresher refresher in Refreshers) diff --git a/src/Microsoft.Azure.AppConfiguration.Functions.Worker/AzureAppConfigurationRefreshMiddleware.cs b/src/Microsoft.Azure.AppConfiguration.Functions.Worker/AzureAppConfigurationRefreshMiddleware.cs index af045e13..aaee0df0 100644 --- a/src/Microsoft.Azure.AppConfiguration.Functions.Worker/AzureAppConfigurationRefreshMiddleware.cs +++ b/src/Microsoft.Azure.AppConfiguration.Functions.Worker/AzureAppConfigurationRefreshMiddleware.cs @@ -24,6 +24,9 @@ public AzureAppConfigurationRefreshMiddleware(IConfigurationRefresherProvider re public async Task Invoke(FunctionContext context, FunctionExecutionDelegate next) { + // + // Configuration refresh is meant to execute as an isolated background task. + // To prevent access of request-based resources, such as HttpContext, we suppress the execution context within the refresh operation. using (var flowControl = ExecutionContext.SuppressFlow()) { foreach (IConfigurationRefresher refresher in Refreshers) From 93b6b0ab4345418a337a87111b866b9e522b40ab Mon Sep 17 00:00:00 2001 From: AMER JUSUPOVIC Date: Tue, 11 Jul 2023 11:50:18 -0500 Subject: [PATCH 06/13] fix refreshmiddleware tests to wait for TryRefreshAsync calls --- .../RefreshMiddlewareTests.cs | 35 ++++++++++++++++--- 1 file changed, 31 insertions(+), 4 deletions(-) diff --git a/tests/Tests.AzureAppConfiguration.Functions.Worker/RefreshMiddlewareTests.cs b/tests/Tests.AzureAppConfiguration.Functions.Worker/RefreshMiddlewareTests.cs index 4bcdcb50..f1b898ee 100644 --- a/tests/Tests.AzureAppConfiguration.Functions.Worker/RefreshMiddlewareTests.cs +++ b/tests/Tests.AzureAppConfiguration.Functions.Worker/RefreshMiddlewareTests.cs @@ -6,7 +6,9 @@ using Microsoft.Azure.Functions.Worker.Middleware; using Microsoft.Extensions.Configuration.AzureAppConfiguration; using Moq; +using System; using System.Threading; +using System.Threading.Tasks; using Xunit; namespace Tests.AzureAppConfiguration.Functions.Worker @@ -14,11 +16,16 @@ namespace Tests.AzureAppConfiguration.Functions.Worker public class RefreshMiddlewareTests { [Fact] - public void RefreshMiddlewareTests_MiddlewareConstructorRetrievesIConfigurationRefresher() + public async Task RefreshMiddlewareTests_MiddlewareConstructorRetrievesIConfigurationRefresher() { // Arrange var mockRefresher = new Mock(MockBehavior.Strict); - mockRefresher.Setup(provider => provider.TryRefreshAsync(It.IsAny())).ReturnsAsync(true); + + int callCount = 0; + + mockRefresher.Setup(provider => provider.TryRefreshAsync(It.IsAny())) + .Callback(() => Interlocked.Increment(ref callCount)) + .ReturnsAsync(true); var mockRefresherProvider = new Mock(MockBehavior.Strict); mockRefresherProvider.SetupGet(provider => provider.Refreshers).Returns(new IConfigurationRefresher[] { mockRefresher.Object }); @@ -30,16 +37,28 @@ public void RefreshMiddlewareTests_MiddlewareConstructorRetrievesIConfigurationR var middleware = new AzureAppConfigurationRefreshMiddleware(mockRefresherProvider.Object); _ = middleware.Invoke(mockContext.Object, mockFunctionExecutionDelegate.Object); + using var cts = new CancellationTokenSource(TimeSpan.FromSeconds(10)); + + while (callCount < 1) + { + await Task.Delay(TimeSpan.FromSeconds(1), cts.Token); + } + // Assert mockRefresher.Verify(refresher => refresher.TryRefreshAsync(It.IsAny()), Times.Once); } [Fact] - public void RefreshMiddlewareTests_MiddlewareConstructorRetrievesMultipleIConfigurationRefreshers() + public async Task RefreshMiddlewareTests_MiddlewareConstructorRetrievesMultipleIConfigurationRefreshers() { // Arrange var mockRefresher = new Mock(MockBehavior.Strict); - mockRefresher.Setup(provider => provider.TryRefreshAsync(It.IsAny())).ReturnsAsync(true); + + int callCount = 0; + + mockRefresher.Setup(provider => provider.TryRefreshAsync(It.IsAny())) + .Callback(() => Interlocked.Increment(ref callCount)) + .ReturnsAsync(true); var mockRefresherProvider = new Mock(MockBehavior.Strict); mockRefresherProvider.SetupGet(provider => provider.Refreshers).Returns(new IConfigurationRefresher[] { mockRefresher.Object, mockRefresher.Object }); @@ -51,8 +70,16 @@ public void RefreshMiddlewareTests_MiddlewareConstructorRetrievesMultipleIConfig var middleware = new AzureAppConfigurationRefreshMiddleware(mockRefresherProvider.Object); _ = middleware.Invoke(mockContext.Object, mockFunctionExecutionDelegate.Object); + using var cts = new CancellationTokenSource(TimeSpan.FromSeconds(10)); + + while (callCount < 2) + { + await Task.Delay(TimeSpan.FromSeconds(1), cts.Token); + } + // Assert mockRefresher.Verify(refresher => refresher.TryRefreshAsync(It.IsAny()), Times.Exactly(2)); + } } } From 795254d7af6743a8bb09368c4ab6fe08d38d90bd Mon Sep 17 00:00:00 2001 From: AMER JUSUPOVIC Date: Tue, 11 Jul 2023 13:06:41 -0500 Subject: [PATCH 07/13] remove extra space in test --- .../RefreshMiddlewareTests.cs | 1 - 1 file changed, 1 deletion(-) diff --git a/tests/Tests.AzureAppConfiguration.Functions.Worker/RefreshMiddlewareTests.cs b/tests/Tests.AzureAppConfiguration.Functions.Worker/RefreshMiddlewareTests.cs index f1b898ee..37aaf4b9 100644 --- a/tests/Tests.AzureAppConfiguration.Functions.Worker/RefreshMiddlewareTests.cs +++ b/tests/Tests.AzureAppConfiguration.Functions.Worker/RefreshMiddlewareTests.cs @@ -79,7 +79,6 @@ public async Task RefreshMiddlewareTests_MiddlewareConstructorRetrievesMultipleI // Assert mockRefresher.Verify(refresher => refresher.TryRefreshAsync(It.IsAny()), Times.Exactly(2)); - } } } From 48af1e891647129221c93b4bb30c22e8d45cbf7c Mon Sep 17 00:00:00 2001 From: AMER JUSUPOVIC Date: Tue, 11 Jul 2023 17:26:49 -0500 Subject: [PATCH 08/13] use explicit type for flowControl --- .../AzureAppConfigurationRefreshMiddleware.cs | 2 +- .../AzureAppConfigurationRefreshMiddleware.cs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Microsoft.Azure.AppConfiguration.AspNetCore/AzureAppConfigurationRefreshMiddleware.cs b/src/Microsoft.Azure.AppConfiguration.AspNetCore/AzureAppConfigurationRefreshMiddleware.cs index 4dfc6e58..4d0683f3 100644 --- a/src/Microsoft.Azure.AppConfiguration.AspNetCore/AzureAppConfigurationRefreshMiddleware.cs +++ b/src/Microsoft.Azure.AppConfiguration.AspNetCore/AzureAppConfigurationRefreshMiddleware.cs @@ -28,7 +28,7 @@ public async Task InvokeAsync(HttpContext context) // // Configuration refresh is meant to execute as an isolated background task. // To prevent access of request-based resources, such as HttpContext, we suppress the execution context within the refresh operation. - using (var flowControl = ExecutionContext.SuppressFlow()) + using (AsyncFlowControl flowControl = ExecutionContext.SuppressFlow()) { foreach (IConfigurationRefresher refresher in Refreshers) { diff --git a/src/Microsoft.Azure.AppConfiguration.Functions.Worker/AzureAppConfigurationRefreshMiddleware.cs b/src/Microsoft.Azure.AppConfiguration.Functions.Worker/AzureAppConfigurationRefreshMiddleware.cs index aaee0df0..0eb9efd3 100644 --- a/src/Microsoft.Azure.AppConfiguration.Functions.Worker/AzureAppConfigurationRefreshMiddleware.cs +++ b/src/Microsoft.Azure.AppConfiguration.Functions.Worker/AzureAppConfigurationRefreshMiddleware.cs @@ -27,7 +27,7 @@ public async Task Invoke(FunctionContext context, FunctionExecutionDelegate next // // Configuration refresh is meant to execute as an isolated background task. // To prevent access of request-based resources, such as HttpContext, we suppress the execution context within the refresh operation. - using (var flowControl = ExecutionContext.SuppressFlow()) + using (AsyncFlowControl flowControl = ExecutionContext.SuppressFlow()) { foreach (IConfigurationRefresher refresher in Refreshers) { From 7ea8f56ee076a15557c8fa7ec148fae5916e593c Mon Sep 17 00:00:00 2001 From: AMER JUSUPOVIC Date: Mon, 24 Jul 2023 11:29:39 -0500 Subject: [PATCH 09/13] add delay between calling any refresh logic --- .../AzureAppConfigurationRefreshMiddleware.cs | 19 +++++++++++++------ .../AzureAppConfigurationRefreshMiddleware.cs | 19 +++++++++++++------ 2 files changed, 26 insertions(+), 12 deletions(-) diff --git a/src/Microsoft.Azure.AppConfiguration.AspNetCore/AzureAppConfigurationRefreshMiddleware.cs b/src/Microsoft.Azure.AppConfiguration.AspNetCore/AzureAppConfigurationRefreshMiddleware.cs index 4d0683f3..1ab91028 100644 --- a/src/Microsoft.Azure.AppConfiguration.AspNetCore/AzureAppConfigurationRefreshMiddleware.cs +++ b/src/Microsoft.Azure.AppConfiguration.AspNetCore/AzureAppConfigurationRefreshMiddleware.cs @@ -3,6 +3,7 @@ // using Microsoft.AspNetCore.Http; using Microsoft.Extensions.Configuration.AzureAppConfiguration; +using System; using System.Collections.Generic; using System.Threading; using System.Threading.Tasks; @@ -16,6 +17,7 @@ internal class AzureAppConfigurationRefreshMiddleware { private readonly RequestDelegate _next; public IEnumerable Refreshers { get; } + private DateTimeOffset refreshReadyTime = DateTimeOffset.UtcNow; public AzureAppConfigurationRefreshMiddleware(RequestDelegate next, IConfigurationRefresherProvider refresherProvider) { @@ -25,15 +27,20 @@ public AzureAppConfigurationRefreshMiddleware(RequestDelegate next, IConfigurati public async Task InvokeAsync(HttpContext context) { - // - // Configuration refresh is meant to execute as an isolated background task. - // To prevent access of request-based resources, such as HttpContext, we suppress the execution context within the refresh operation. - using (AsyncFlowControl flowControl = ExecutionContext.SuppressFlow()) + if (refreshReadyTime <= DateTimeOffset.UtcNow) { - foreach (IConfigurationRefresher refresher in Refreshers) + // + // Configuration refresh is meant to execute as an isolated background task. + // To prevent access of request-based resources, such as HttpContext, we suppress the execution context within the refresh operation. + using (AsyncFlowControl flowControl = ExecutionContext.SuppressFlow()) { - _ = Task.Run(() => refresher.TryRefreshAsync()); + foreach (IConfigurationRefresher refresher in Refreshers) + { + _ = Task.Run(() => refresher.TryRefreshAsync()); + } } + + refreshReadyTime = DateTimeOffset.UtcNow.Add(TimeSpan.FromSeconds(1)); } // Call the next delegate/middleware in the pipeline diff --git a/src/Microsoft.Azure.AppConfiguration.Functions.Worker/AzureAppConfigurationRefreshMiddleware.cs b/src/Microsoft.Azure.AppConfiguration.Functions.Worker/AzureAppConfigurationRefreshMiddleware.cs index 0eb9efd3..b992339d 100644 --- a/src/Microsoft.Azure.AppConfiguration.Functions.Worker/AzureAppConfigurationRefreshMiddleware.cs +++ b/src/Microsoft.Azure.AppConfiguration.Functions.Worker/AzureAppConfigurationRefreshMiddleware.cs @@ -4,6 +4,7 @@ using Microsoft.Azure.Functions.Worker; using Microsoft.Azure.Functions.Worker.Middleware; using Microsoft.Extensions.Configuration.AzureAppConfiguration; +using System; using System.Collections.Generic; using System.Threading; using System.Threading.Tasks; @@ -16,6 +17,7 @@ namespace Microsoft.Azure.AppConfiguration.Functions.Worker internal class AzureAppConfigurationRefreshMiddleware : IFunctionsWorkerMiddleware { private IEnumerable Refreshers { get; } + private DateTimeOffset refreshReadyTime = DateTimeOffset.UtcNow; public AzureAppConfigurationRefreshMiddleware(IConfigurationRefresherProvider refresherProvider) { @@ -24,15 +26,20 @@ public AzureAppConfigurationRefreshMiddleware(IConfigurationRefresherProvider re public async Task Invoke(FunctionContext context, FunctionExecutionDelegate next) { - // - // Configuration refresh is meant to execute as an isolated background task. - // To prevent access of request-based resources, such as HttpContext, we suppress the execution context within the refresh operation. - using (AsyncFlowControl flowControl = ExecutionContext.SuppressFlow()) + if (refreshReadyTime <= DateTimeOffset.UtcNow) { - foreach (IConfigurationRefresher refresher in Refreshers) + // + // Configuration refresh is meant to execute as an isolated background task. + // To prevent access of request-based resources, such as HttpContext, we suppress the execution context within the refresh operation. + using (AsyncFlowControl flowControl = ExecutionContext.SuppressFlow()) { - _ = Task.Run(() => refresher.TryRefreshAsync()); + foreach (IConfigurationRefresher refresher in Refreshers) + { + _ = Task.Run(() => refresher.TryRefreshAsync()); + } } + + refreshReadyTime = DateTimeOffset.UtcNow.Add(TimeSpan.FromSeconds(1)); } await next(context).ConfigureAwait(false); From f75183ba0cc79013ff845f4e4142fdea0ac21997 Mon Sep 17 00:00:00 2001 From: AMER JUSUPOVIC Date: Mon, 24 Jul 2023 15:52:04 -0500 Subject: [PATCH 10/13] ensure no more than 1 access to refresh logic per second --- .../AzureAppConfigurationRefreshMiddleware.cs | 13 +++++++++---- .../AzureAppConfigurationRefreshMiddleware.cs | 12 ++++++++---- 2 files changed, 17 insertions(+), 8 deletions(-) diff --git a/src/Microsoft.Azure.AppConfiguration.AspNetCore/AzureAppConfigurationRefreshMiddleware.cs b/src/Microsoft.Azure.AppConfiguration.AspNetCore/AzureAppConfigurationRefreshMiddleware.cs index 1ab91028..3d6f3215 100644 --- a/src/Microsoft.Azure.AppConfiguration.AspNetCore/AzureAppConfigurationRefreshMiddleware.cs +++ b/src/Microsoft.Azure.AppConfiguration.AspNetCore/AzureAppConfigurationRefreshMiddleware.cs @@ -15,9 +15,11 @@ namespace Microsoft.Azure.AppConfiguration.AspNetCore /// internal class AzureAppConfigurationRefreshMiddleware { + // The minimum refresh interval on the configuration provider is 1 second, so refreshing more often is unnecessary + private static readonly long MinimumRefreshInterval = TimeSpan.FromSeconds(1).Ticks; private readonly RequestDelegate _next; + private long _refreshReadyTime = DateTimeOffset.UtcNow.Ticks; public IEnumerable Refreshers { get; } - private DateTimeOffset refreshReadyTime = DateTimeOffset.UtcNow; public AzureAppConfigurationRefreshMiddleware(RequestDelegate next, IConfigurationRefresherProvider refresherProvider) { @@ -27,7 +29,12 @@ public AzureAppConfigurationRefreshMiddleware(RequestDelegate next, IConfigurati public async Task InvokeAsync(HttpContext context) { - if (refreshReadyTime <= DateTimeOffset.UtcNow) + long utcNow = DateTimeOffset.UtcNow.Ticks; + + long refreshReadyTime = Interlocked.Read(ref _refreshReadyTime); + + if (refreshReadyTime <= utcNow && + Interlocked.CompareExchange(ref _refreshReadyTime, refreshReadyTime + MinimumRefreshInterval, refreshReadyTime) == refreshReadyTime) { // // Configuration refresh is meant to execute as an isolated background task. @@ -39,8 +46,6 @@ public async Task InvokeAsync(HttpContext context) _ = Task.Run(() => refresher.TryRefreshAsync()); } } - - refreshReadyTime = DateTimeOffset.UtcNow.Add(TimeSpan.FromSeconds(1)); } // Call the next delegate/middleware in the pipeline diff --git a/src/Microsoft.Azure.AppConfiguration.Functions.Worker/AzureAppConfigurationRefreshMiddleware.cs b/src/Microsoft.Azure.AppConfiguration.Functions.Worker/AzureAppConfigurationRefreshMiddleware.cs index b992339d..0f896729 100644 --- a/src/Microsoft.Azure.AppConfiguration.Functions.Worker/AzureAppConfigurationRefreshMiddleware.cs +++ b/src/Microsoft.Azure.AppConfiguration.Functions.Worker/AzureAppConfigurationRefreshMiddleware.cs @@ -16,8 +16,9 @@ namespace Microsoft.Azure.AppConfiguration.Functions.Worker /// internal class AzureAppConfigurationRefreshMiddleware : IFunctionsWorkerMiddleware { + private static readonly long MinimumRefreshInterval = TimeSpan.FromSeconds(1).Ticks; private IEnumerable Refreshers { get; } - private DateTimeOffset refreshReadyTime = DateTimeOffset.UtcNow; + private long _refreshReadyTime = DateTimeOffset.UtcNow.Ticks; public AzureAppConfigurationRefreshMiddleware(IConfigurationRefresherProvider refresherProvider) { @@ -26,7 +27,12 @@ public AzureAppConfigurationRefreshMiddleware(IConfigurationRefresherProvider re public async Task Invoke(FunctionContext context, FunctionExecutionDelegate next) { - if (refreshReadyTime <= DateTimeOffset.UtcNow) + long utcNow = DateTimeOffset.UtcNow.Ticks; + + long refreshReadyTime = Interlocked.Read(ref _refreshReadyTime); + + if (refreshReadyTime <= utcNow && + Interlocked.CompareExchange(ref _refreshReadyTime, refreshReadyTime + MinimumRefreshInterval, refreshReadyTime) == refreshReadyTime) { // // Configuration refresh is meant to execute as an isolated background task. @@ -38,8 +44,6 @@ public async Task Invoke(FunctionContext context, FunctionExecutionDelegate next _ = Task.Run(() => refresher.TryRefreshAsync()); } } - - refreshReadyTime = DateTimeOffset.UtcNow.Add(TimeSpan.FromSeconds(1)); } await next(context).ConfigureAwait(false); From 324fc5b8600d5242f7e1d8cda981a97e273e33cd Mon Sep 17 00:00:00 2001 From: AMER JUSUPOVIC Date: Mon, 24 Jul 2023 15:53:13 -0500 Subject: [PATCH 11/13] simplify code --- .../AzureAppConfigurationRefreshMiddleware.cs | 4 +--- .../AzureAppConfigurationRefreshMiddleware.cs | 4 +--- 2 files changed, 2 insertions(+), 6 deletions(-) diff --git a/src/Microsoft.Azure.AppConfiguration.AspNetCore/AzureAppConfigurationRefreshMiddleware.cs b/src/Microsoft.Azure.AppConfiguration.AspNetCore/AzureAppConfigurationRefreshMiddleware.cs index 3d6f3215..fdcb0033 100644 --- a/src/Microsoft.Azure.AppConfiguration.AspNetCore/AzureAppConfigurationRefreshMiddleware.cs +++ b/src/Microsoft.Azure.AppConfiguration.AspNetCore/AzureAppConfigurationRefreshMiddleware.cs @@ -29,11 +29,9 @@ public AzureAppConfigurationRefreshMiddleware(RequestDelegate next, IConfigurati public async Task InvokeAsync(HttpContext context) { - long utcNow = DateTimeOffset.UtcNow.Ticks; - long refreshReadyTime = Interlocked.Read(ref _refreshReadyTime); - if (refreshReadyTime <= utcNow && + if (refreshReadyTime <= DateTimeOffset.UtcNow.Ticks && Interlocked.CompareExchange(ref _refreshReadyTime, refreshReadyTime + MinimumRefreshInterval, refreshReadyTime) == refreshReadyTime) { // diff --git a/src/Microsoft.Azure.AppConfiguration.Functions.Worker/AzureAppConfigurationRefreshMiddleware.cs b/src/Microsoft.Azure.AppConfiguration.Functions.Worker/AzureAppConfigurationRefreshMiddleware.cs index 0f896729..f90dd43f 100644 --- a/src/Microsoft.Azure.AppConfiguration.Functions.Worker/AzureAppConfigurationRefreshMiddleware.cs +++ b/src/Microsoft.Azure.AppConfiguration.Functions.Worker/AzureAppConfigurationRefreshMiddleware.cs @@ -27,11 +27,9 @@ public AzureAppConfigurationRefreshMiddleware(IConfigurationRefresherProvider re public async Task Invoke(FunctionContext context, FunctionExecutionDelegate next) { - long utcNow = DateTimeOffset.UtcNow.Ticks; - long refreshReadyTime = Interlocked.Read(ref _refreshReadyTime); - if (refreshReadyTime <= utcNow && + if (refreshReadyTime <= DateTimeOffset.UtcNow.Ticks && Interlocked.CompareExchange(ref _refreshReadyTime, refreshReadyTime + MinimumRefreshInterval, refreshReadyTime) == refreshReadyTime) { // From 34e00864281d1e0b45f2f88b397a6dd2685b4fed Mon Sep 17 00:00:00 2001 From: AMER JUSUPOVIC Date: Mon, 24 Jul 2023 15:59:26 -0500 Subject: [PATCH 12/13] update revisions --- .../AzureAppConfigurationRefreshMiddleware.cs | 7 +++++-- .../AzureAppConfigurationRefreshMiddleware.cs | 9 ++++++--- 2 files changed, 11 insertions(+), 5 deletions(-) diff --git a/src/Microsoft.Azure.AppConfiguration.AspNetCore/AzureAppConfigurationRefreshMiddleware.cs b/src/Microsoft.Azure.AppConfiguration.AspNetCore/AzureAppConfigurationRefreshMiddleware.cs index fdcb0033..70e43365 100644 --- a/src/Microsoft.Azure.AppConfiguration.AspNetCore/AzureAppConfigurationRefreshMiddleware.cs +++ b/src/Microsoft.Azure.AppConfiguration.AspNetCore/AzureAppConfigurationRefreshMiddleware.cs @@ -19,6 +19,7 @@ internal class AzureAppConfigurationRefreshMiddleware private static readonly long MinimumRefreshInterval = TimeSpan.FromSeconds(1).Ticks; private readonly RequestDelegate _next; private long _refreshReadyTime = DateTimeOffset.UtcNow.Ticks; + public IEnumerable Refreshers { get; } public AzureAppConfigurationRefreshMiddleware(RequestDelegate next, IConfigurationRefresherProvider refresherProvider) @@ -29,10 +30,12 @@ public AzureAppConfigurationRefreshMiddleware(RequestDelegate next, IConfigurati public async Task InvokeAsync(HttpContext context) { + long utcNow = DateTimeOffset.UtcNow.Ticks; + long refreshReadyTime = Interlocked.Read(ref _refreshReadyTime); - if (refreshReadyTime <= DateTimeOffset.UtcNow.Ticks && - Interlocked.CompareExchange(ref _refreshReadyTime, refreshReadyTime + MinimumRefreshInterval, refreshReadyTime) == refreshReadyTime) + if (refreshReadyTime <= utcNow && + Interlocked.CompareExchange(ref _refreshReadyTime, utcNow + MinimumRefreshInterval, refreshReadyTime) == refreshReadyTime) { // // Configuration refresh is meant to execute as an isolated background task. diff --git a/src/Microsoft.Azure.AppConfiguration.Functions.Worker/AzureAppConfigurationRefreshMiddleware.cs b/src/Microsoft.Azure.AppConfiguration.Functions.Worker/AzureAppConfigurationRefreshMiddleware.cs index f90dd43f..9aef2b6b 100644 --- a/src/Microsoft.Azure.AppConfiguration.Functions.Worker/AzureAppConfigurationRefreshMiddleware.cs +++ b/src/Microsoft.Azure.AppConfiguration.Functions.Worker/AzureAppConfigurationRefreshMiddleware.cs @@ -17,9 +17,10 @@ namespace Microsoft.Azure.AppConfiguration.Functions.Worker internal class AzureAppConfigurationRefreshMiddleware : IFunctionsWorkerMiddleware { private static readonly long MinimumRefreshInterval = TimeSpan.FromSeconds(1).Ticks; - private IEnumerable Refreshers { get; } private long _refreshReadyTime = DateTimeOffset.UtcNow.Ticks; + private IEnumerable Refreshers { get; } + public AzureAppConfigurationRefreshMiddleware(IConfigurationRefresherProvider refresherProvider) { Refreshers = refresherProvider.Refreshers; @@ -27,10 +28,12 @@ public AzureAppConfigurationRefreshMiddleware(IConfigurationRefresherProvider re public async Task Invoke(FunctionContext context, FunctionExecutionDelegate next) { + long utcNow = DateTimeOffset.UtcNow.Ticks; + long refreshReadyTime = Interlocked.Read(ref _refreshReadyTime); - if (refreshReadyTime <= DateTimeOffset.UtcNow.Ticks && - Interlocked.CompareExchange(ref _refreshReadyTime, refreshReadyTime + MinimumRefreshInterval, refreshReadyTime) == refreshReadyTime) + if (refreshReadyTime <= utcNow && + Interlocked.CompareExchange(ref _refreshReadyTime, utcNow + MinimumRefreshInterval, refreshReadyTime) == refreshReadyTime) { // // Configuration refresh is meant to execute as an isolated background task. From b5dc00301de29c79c45b9f7ccb34e066b4f54075 Mon Sep 17 00:00:00 2001 From: AMER JUSUPOVIC Date: Mon, 24 Jul 2023 16:39:43 -0500 Subject: [PATCH 13/13] add comment to functions worker --- .../AzureAppConfigurationRefreshMiddleware.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/src/Microsoft.Azure.AppConfiguration.Functions.Worker/AzureAppConfigurationRefreshMiddleware.cs b/src/Microsoft.Azure.AppConfiguration.Functions.Worker/AzureAppConfigurationRefreshMiddleware.cs index 9aef2b6b..02dd87b7 100644 --- a/src/Microsoft.Azure.AppConfiguration.Functions.Worker/AzureAppConfigurationRefreshMiddleware.cs +++ b/src/Microsoft.Azure.AppConfiguration.Functions.Worker/AzureAppConfigurationRefreshMiddleware.cs @@ -16,6 +16,7 @@ namespace Microsoft.Azure.AppConfiguration.Functions.Worker /// internal class AzureAppConfigurationRefreshMiddleware : IFunctionsWorkerMiddleware { + // The minimum refresh interval on the configuration provider is 1 second, so refreshing more often is unnecessary private static readonly long MinimumRefreshInterval = TimeSpan.FromSeconds(1).Ticks; private long _refreshReadyTime = DateTimeOffset.UtcNow.Ticks;