Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Using the CacheExpirationInterval option does not work on a console app #252

Closed
pregoli opened this issue Aug 17, 2023 · 4 comments
Closed

Comments

@pregoli
Copy link

pregoli commented Aug 17, 2023

We need to get a the latest feature flag value every time we read from Azure App configuration - FeatureManager.
By using:
.UseFeatureFlags(options => options.CacheExpirationInterval = TimeSpan.FromSeconds(15))

The above extension works as expected when used within an API project.

The same approach does NOT work on a console app.

Extra info: The extensions AddAzureAppConfiguration & UseAzureAppConfiguration are correctly used.

Steps to reproduce:

  1. Create a console app (.Net 6 or 7)
  2. Add an hosted service (eg. builder.Services.AddHostedService<MyHostedService>();) as BackgroundService
  3. Add the Microsoft.FeatureManagement package and make sure to add and use it for your app as:
    3.1 Services.AddAzureAppConfiguration()
    3.2 app.UseAzureAppConfiguration();
    3.3 .UseFeatureFlags(options => options.CacheExpirationInterval = TimeSpan.FromSeconds(15))
  4. Create a Feature flag on your Azure named "featureTest" as disabled
  5. On the background service MyHostedService class add the following code during the ExecuteAsync overridden method:
    while (!stoppingToken.IsCancellationRequested) { var featureTestEnabled = await _featureManager.IsEnabledAsync("featureTest") if (featureTestEnabled) { //do something } else { //do something else } await timer.WaitForNextTickAsync(stoppingToken); }
  6. Run the code and verify the featureTestEnabled is false
  7. Enable the featureTest feature flag on Azure and wait for the expiration time setup on startup.
  8. Run the code again and verify the featureTestEnabled is still False while it should be TRUE
@rossgrambo
Copy link
Contributor

Hello @pregoli ,

For console apps (and Azure Functions), I believe the only way to refresh is to explicitly call TryRefreshAsync. See the Azure Functions Guide for an example.

I'll continue to look into this, because it's not clear to me why the refresh is restricted in this way.

@pregoli
Copy link
Author

pregoli commented Aug 23, 2023

Thanks for your reply @rossgrambo but I think refreshing explicitely is not the way We or The consumers of this library aim to go. You could end up refreshing on a schedule way even when there's no need to refresh it because no changes happened on FFs values.

I think the current behavior is due to the refresh/trigger is happening within the ASP.Net pipeline middleware while in the backgroundservice is not.

Please keep us in the loop.

Thanks!

@rossgrambo
Copy link
Contributor

After syncing with some other members, I see now why it's built the way it is. Here's a summary of it, only a couple parts might be of interest to you.


The library that handles dynamic configurations (.AddAzureAppConfiguration()) is our Provider Library.

This library reads from our server, and places it into Configuration for the app. Configuration is a part of .NET and can be thought of as a dictionary for traversing json-like data.

Configuration doesn't have a hook we could add a refresh check in, so when refreshing was built it used a timer approach and would update the config in a scheduled way. As you called out, this led to a bunch of refreshes for no reason. This was especially bad for stale apps, that continued to ping us without users or purpose.

The timer was replaced with a middleware. The middleware is setup via UseAzureAppConfiguration() where it adds AzureAppConfigurationRefreshMiddleware to be invoked during http requests. To refresh, TryRefreshAsync is called as an http request comes in. TryRefreshAsync does nothing if we're still within the cache expiration interval, otherwise it checks for a refresh. This means it's safe to call it multiple times.

Console apps don't have a consistent workflow we can add a hook to in the same way. So the current state is to leave it to the developer to trigger the refresh appropriately. Allowing a flag to change mid-execution might not make sense for some apps.

Here's a guide for now to setup refreshing for a .NET Core app: https://learn.microsoft.com/en-us/azure/azure-app-configuration/enable-dynamic-configuration-dotnet-core?tabs=core6x


All of that to say, I think you should add TryRefreshAsync() above your IsEnabledAsync() check. Because it's ignored when the cache hasn't expired, it should be safe to do. Alternatively, you could write a helper method that does the refresh and IsEnabled call.

I could see a world where we add a TryRefreshAsync onto calls to IsEnabledAsync as a part of FeatureManagement. This would add a dependency between FeatureManagement and the Provider which hasn't existed, and there are other concerns like flag flipping mid execution. Currently there's a clear separation of the libraries: FeatureManagement just reads from the Configuration, while the Provider manages updating the Configuration.

@rossgrambo
Copy link
Contributor

Closing this for now.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

2 participants