From b3610b58bd8481223589e3e766d53b10ef8bcebf Mon Sep 17 00:00:00 2001 From: Zhiyuan Liang <141655842+zhiyuanliang-ms@users.noreply.github.com> Date: Tue, 8 Jul 2025 10:50:48 +0800 Subject: [PATCH 1/2] Bug fix - Respect root configuration fallback (#547) * use Lazy to ensure lazy initialization * use ensureinit pattern without lock --- .../ConfigurationFeatureDefinitionProvider.cs | 29 ++++++++++++++----- .../FeatureManagementTest.cs | 2 +- 2 files changed, 22 insertions(+), 9 deletions(-) diff --git a/src/Microsoft.FeatureManagement/ConfigurationFeatureDefinitionProvider.cs b/src/Microsoft.FeatureManagement/ConfigurationFeatureDefinitionProvider.cs index d8dbd996..44b2bcc3 100644 --- a/src/Microsoft.FeatureManagement/ConfigurationFeatureDefinitionProvider.cs +++ b/src/Microsoft.FeatureManagement/ConfigurationFeatureDefinitionProvider.cs @@ -28,6 +28,7 @@ public sealed class ConfigurationFeatureDefinitionProvider : IFeatureDefinitionP private readonly ConcurrentDictionary> _definitions; private IDisposable _changeSubscription; private int _stale = 0; + private int _initialized = 0; private readonly Func> _getFeatureDefinitionFunc; const string ParseValueErrorString = "Invalid setting '{0}' with value '{1}' for feature '{2}'."; @@ -49,10 +50,6 @@ public ConfigurationFeatureDefinitionProvider(IConfiguration configuration) { return Task.FromResult(GetMicrosoftSchemaFeatureDefinition(featureName) ?? GetDotnetSchemaFeatureDefinition(featureName)); }; - - _dotnetFeatureDefinitionSections = GetDotnetFeatureDefinitionSections(); - - _microsoftFeatureDefinitionSections = GetMicrosoftFeatureDefinitionSections(); } /// @@ -92,13 +89,15 @@ public Task GetFeatureDefinitionAsync(string featureName) throw new ArgumentException($"The value '{ConfigurationPath.KeyDelimiter}' is not allowed in the feature name.", nameof(featureName)); } + EnsureInit(); + if (Interlocked.Exchange(ref _stale, 0) != 0) { - _definitions.Clear(); - _dotnetFeatureDefinitionSections = GetDotnetFeatureDefinitionSections(); _microsoftFeatureDefinitionSections = GetMicrosoftFeatureDefinitionSections(); + + _definitions.Clear(); } return _definitions.GetOrAdd(featureName, _getFeatureDefinitionFunc); @@ -115,13 +114,15 @@ public Task GetFeatureDefinitionAsync(string featureName) public async IAsyncEnumerable GetAllFeatureDefinitionsAsync() #pragma warning restore CS1998 { + EnsureInit(); + if (Interlocked.Exchange(ref _stale, 0) != 0) { - _definitions.Clear(); - _dotnetFeatureDefinitionSections = GetDotnetFeatureDefinitionSections(); _microsoftFeatureDefinitionSections = GetMicrosoftFeatureDefinitionSections(); + + _definitions.Clear(); } foreach (IConfigurationSection featureSection in _microsoftFeatureDefinitionSections) @@ -163,6 +164,18 @@ public async IAsyncEnumerable GetAllFeatureDefinitionsAsync() } } + private void EnsureInit() + { + if (_initialized == 0) + { + _dotnetFeatureDefinitionSections = GetDotnetFeatureDefinitionSections(); + + _microsoftFeatureDefinitionSections = GetMicrosoftFeatureDefinitionSections(); + + _initialized = 1; + } + } + private FeatureDefinition GetDotnetSchemaFeatureDefinition(string featureName) { IConfigurationSection dotnetFeatureDefinitionConfiguration = _dotnetFeatureDefinitionSections diff --git a/tests/Tests.FeatureManagement/FeatureManagementTest.cs b/tests/Tests.FeatureManagement/FeatureManagementTest.cs index b9c3d7c1..5c71a60b 100644 --- a/tests/Tests.FeatureManagement/FeatureManagementTest.cs +++ b/tests/Tests.FeatureManagement/FeatureManagementTest.cs @@ -90,7 +90,7 @@ public async Task ReadsTopLevelConfiguration() IFeatureManager featureManager = serviceProvider.GetRequiredService(); - //Assert.True(await featureManager.IsEnabledAsync("FeatureX")); + Assert.True(await featureManager.IsEnabledAsync("FeatureX")); string json = @" { From 3b2e5510c73e03c94970843d6232c35f3b981823 Mon Sep 17 00:00:00 2001 From: Zhiyuan Liang <141655842+zhiyuanliang-ms@users.noreply.github.com> Date: Wed, 9 Jul 2025 10:54:55 +0800 Subject: [PATCH 2/2] version bump 4.2.1 (#548) --- .../Microsoft.FeatureManagement.AspNetCore.csproj | 2 +- ...osoft.FeatureManagement.Telemetry.ApplicationInsights.csproj | 2 +- .../Microsoft.FeatureManagement.csproj | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/Microsoft.FeatureManagement.AspNetCore/Microsoft.FeatureManagement.AspNetCore.csproj b/src/Microsoft.FeatureManagement.AspNetCore/Microsoft.FeatureManagement.AspNetCore.csproj index c38c9cc6..f4d65766 100644 --- a/src/Microsoft.FeatureManagement.AspNetCore/Microsoft.FeatureManagement.AspNetCore.csproj +++ b/src/Microsoft.FeatureManagement.AspNetCore/Microsoft.FeatureManagement.AspNetCore.csproj @@ -6,7 +6,7 @@ 4 2 - 0 + 1 diff --git a/src/Microsoft.FeatureManagement.Telemetry.ApplicationInsights/Microsoft.FeatureManagement.Telemetry.ApplicationInsights.csproj b/src/Microsoft.FeatureManagement.Telemetry.ApplicationInsights/Microsoft.FeatureManagement.Telemetry.ApplicationInsights.csproj index 68472dc4..4068f7b4 100644 --- a/src/Microsoft.FeatureManagement.Telemetry.ApplicationInsights/Microsoft.FeatureManagement.Telemetry.ApplicationInsights.csproj +++ b/src/Microsoft.FeatureManagement.Telemetry.ApplicationInsights/Microsoft.FeatureManagement.Telemetry.ApplicationInsights.csproj @@ -5,7 +5,7 @@ 4 2 - 0 + 1 diff --git a/src/Microsoft.FeatureManagement/Microsoft.FeatureManagement.csproj b/src/Microsoft.FeatureManagement/Microsoft.FeatureManagement.csproj index 9bc078c0..264473ca 100644 --- a/src/Microsoft.FeatureManagement/Microsoft.FeatureManagement.csproj +++ b/src/Microsoft.FeatureManagement/Microsoft.FeatureManagement.csproj @@ -6,7 +6,7 @@ 4 2 - 0 + 1