microsoft / microsoft/FeatureManagement-Dotnet

Refreshing the Feature flag SDK cache which is loading from CDN is not working properly

Open
#571 5 comments 0 reactions 0 assignees View on GitHub

Nobody has claimed this yet.

Dominant language
C#
Stars
1.2k
Forks
129
Avg merge
1d 1h
Merged PRs (30d)
5

Description

Hi All,

We are using the Azure Feature flag in WPF application, which gets connected via CDN connection. (WPF -> CDN -> Azure Functions -> App Configuration)

I have three methods one is initialize and another is refresher and third to read the feature flag value.

Initialize - Method called only once
Refresher - Called periodically
Read Value - Called always while reading the value.

/// <inheritdoc/>
public override async Task<bool> InitAsync(CancellationToken cancellationToken)
{
    this.logger.WriteInfoEx($"Initializing the remote configuration client, loading the feature configuration from CDN source.");
    var configurationBuilder = new ConfigurationBuilder();            
    configurationBuilder.AddAzureAppConfiguration(options =>
    {
        // Disabling replica discovery to avoid potential edge-region inconsistencies and ensure stable feature flag resolution.
        // For more details, refer to the documentation: [Insert relevant documentation link here].
        options.ReplicaDiscoveryEnabled = false;
        options.Connect(connectionString)
        .ConfigureStartupOptions(startupOptions =>
        {
            // Set the time-out for the initial configuration load
            startupOptions.Timeout = TimeSpan.FromSeconds(DefaultFeatureFlagConnectionTimeoutInSeconds);
        })
        .UseFeatureFlags(config => config.SetRefreshInterval(Configuration.ConfigurationRefreshInterval));
        this.configurationRefresher = options.GetRefresher();
    }, true);

    this.logger.WriteInfoEx($"Completed loading the feature configuration from CDN source.");

    var configuration = configurationBuilder.Build();

    // Initialize Feature Management
    this.logger.WriteInfoEx($"Initializing the feature management service collection.");
    var serviceCollection = new ServiceCollection();
    serviceCollection.Configure<FeatureManagementOptions>(options =>
    {
        options.IgnoreMissingFeatures = false;
        // The 'IgnoreMissingFeatureFilters' flag cannot be used in combination with a feature of requirement type 'All'.
        options.IgnoreMissingFeatureFilters = false;                
    });

    serviceCollection.AddSingleton<IConfiguration>(configuration)
        .AddFeatureManagement()
        .AddFeatureFilter<ContextualTargetingFilter>()
        .AddFeatureFilter<DeviceFilter>();

    var serviceProvider = serviceCollection.BuildServiceProvider();
    this.featuremanager = serviceProvider.GetRequiredService<IFeatureManager>();

    this.logger.WriteInfoEx($"Completed the feature management service collection.");

    this.RefreshFeatureFlagConfigurationAsync(cancellationToken).AsBackgroundTask(
            "RemoteConfigurationFeatureProvider background refresh",
            options: TaskContinuationOptions.LongRunning, cancellationToken: cancellationToken);

    return await base.InitAsync(cancellationToken);
}

private async Task RefreshFeatureFlagConfigurationAsync(CancellationToken cancellationToken)
{
    while (!cancellationToken.IsCancellationRequested)
    {
        try
        {
            this.logger.WriteInfoEx("Refreshing the remote configuration");

            var refreshResult = await this.configurationRefresher.TryRefreshAsync(cancellationToken).ConfigureAwait(false);

            this.logger.WriteInfoEx($"Remote configuration refresh completed. Changes applied: {refreshResult}");

            this.logger.WriteInfoEx($"Background task going to wait for {this.Configuration.ConfigurationRefreshInterval} interval.");
            await Task.Delay(this.Configuration.ConfigurationRefreshInterval, cancellationToken).ConfigureAwait(false);
        }
        catch (Exception ex) when (!ex.IsCritical())
        {
            this.logger.WriteErrorEx($"Exception occurred during background refresh of remote configuration in RemoteConfigurationFeatureProvider. Exception: {ex.Message}");
        }
    }
}

/// <inheritdoc/>
protected override async Task<object> ReadProviderValue<T>(string featureId)
{
    try
    {                
        bool remoteFeatureValue = await this.featuremanager.IsEnabledAsync(featureId, DeviceContext).ConfigureAwait(false);
        return (T)Convert.ChangeType(remoteFeatureValue, typeof(T));
    }
    catch (FeatureManagementException ex) when (ex.Error == FeatureManagementError.MissingFeature)
    {
        // Null indicates that feature is not configured to the framework, logging handled by SDK
        this.logger.WriteWarningEx($"Feature '{featureId}' is not configured in the remote configuration.");                
        return null;
    }
}

Below is our SDK version

Microsoft.FeatureManagement - 4.0.0
Microsoft.Extensions.Configuration.AzureAppConfiguration - 8.0.0

While changing the value in Azure reflects in CDN response but Feature flag SDK still returns old value only.

bool remoteFeatureValue = await this.featuremanager.IsEnabledAsync(featureId, DeviceContext).ConfigureAwait(false);
return (T)Convert.ChangeType(remoteFeatureValue, typeof(T));

@jimmyca15 @rossgrambo @zhiyuanliang-ms Could you please let me know what's wrong here?

Contributor guide

No contributing guide indexed for this repository

First steps

  1. Read the whole issue, then the project's contributing guide.
  2. Comment on the issue to say you are picking it up — it saves two people doing the same work.
  3. Fork the repository and make your change on a branch.
  4. Open a pull request that references the issue number.

Research direction

Reproduce the issue using the supplied InitAsync, RefreshFeatureFlagConfigurationAsync, and ReadProviderValue paths with Microsoft.FeatureManagement 4.0.0 and Microsoft.Extensions.Configuration.AzureAppConfiguration 8.0.0. Compare the CDN response with the value returned by IsEnabledAsync after a refresh; done means a changed Azure feature flag is returned instead of the cached value.

Written by the indexing model from the issue text.

Assessment

Tech stack
azure, csharp
Domain
cloud, desktop
Issue type
Bug
Difficulty
4/5
Estimated time
3-5 days
Activity status
Stale
Clarity
Needs clarification
Newbie friendliness
30/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.