Skip to content

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

@sathishkumarkaliavaradhan

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?

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type
    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions