From 92076c1b13c140936c89e0496bfe939f556c4eb0 Mon Sep 17 00:00:00 2001 From: zhiyuanliang Date: Wed, 2 Jul 2025 15:33:58 +0800 Subject: [PATCH 1/2] use Lazy to ensure lazy initialization --- .../ConfigurationFeatureDefinitionProvider.cs | 24 +++++++++---------- .../FeatureManagementTest.cs | 2 +- 2 files changed, 13 insertions(+), 13 deletions(-) diff --git a/src/Microsoft.FeatureManagement/ConfigurationFeatureDefinitionProvider.cs b/src/Microsoft.FeatureManagement/ConfigurationFeatureDefinitionProvider.cs index d8dbd996..b0ec8d6b 100644 --- a/src/Microsoft.FeatureManagement/ConfigurationFeatureDefinitionProvider.cs +++ b/src/Microsoft.FeatureManagement/ConfigurationFeatureDefinitionProvider.cs @@ -23,8 +23,8 @@ public sealed class ConfigurationFeatureDefinitionProvider : IFeatureDefinitionP // IFeatureDefinitionProviderCacheable interface is only used to mark this provider as cacheable. This allows our test suite's // provider to be marked for caching as well. private readonly IConfiguration _configuration; - private IEnumerable _dotnetFeatureDefinitionSections; - private IEnumerable _microsoftFeatureDefinitionSections; + private Lazy> _dotnetFeatureDefinitionSections; + private Lazy> _microsoftFeatureDefinitionSections; private readonly ConcurrentDictionary> _definitions; private IDisposable _changeSubscription; private int _stale = 0; @@ -50,9 +50,9 @@ public ConfigurationFeatureDefinitionProvider(IConfiguration configuration) return Task.FromResult(GetMicrosoftSchemaFeatureDefinition(featureName) ?? GetDotnetSchemaFeatureDefinition(featureName)); }; - _dotnetFeatureDefinitionSections = GetDotnetFeatureDefinitionSections(); + _dotnetFeatureDefinitionSections = new Lazy>(GetDotnetFeatureDefinitionSections); - _microsoftFeatureDefinitionSections = GetMicrosoftFeatureDefinitionSections(); + _microsoftFeatureDefinitionSections = new Lazy>(GetMicrosoftFeatureDefinitionSections); } /// @@ -96,9 +96,9 @@ public Task GetFeatureDefinitionAsync(string featureName) { _definitions.Clear(); - _dotnetFeatureDefinitionSections = GetDotnetFeatureDefinitionSections(); + _dotnetFeatureDefinitionSections = new Lazy>(GetDotnetFeatureDefinitionSections); - _microsoftFeatureDefinitionSections = GetMicrosoftFeatureDefinitionSections(); + _microsoftFeatureDefinitionSections = new Lazy>(GetMicrosoftFeatureDefinitionSections); } return _definitions.GetOrAdd(featureName, _getFeatureDefinitionFunc); @@ -119,12 +119,12 @@ public async IAsyncEnumerable GetAllFeatureDefinitionsAsync() { _definitions.Clear(); - _dotnetFeatureDefinitionSections = GetDotnetFeatureDefinitionSections(); + _dotnetFeatureDefinitionSections = new Lazy>(GetDotnetFeatureDefinitionSections); - _microsoftFeatureDefinitionSections = GetMicrosoftFeatureDefinitionSections(); + _microsoftFeatureDefinitionSections = new Lazy>(GetMicrosoftFeatureDefinitionSections); } - foreach (IConfigurationSection featureSection in _microsoftFeatureDefinitionSections) + foreach (IConfigurationSection featureSection in _microsoftFeatureDefinitionSections.Value) { string featureName = featureSection[MicrosoftFeatureManagementFields.Id]; @@ -143,7 +143,7 @@ public async IAsyncEnumerable GetAllFeatureDefinitionsAsync() } } - foreach (IConfigurationSection featureSection in _dotnetFeatureDefinitionSections) + foreach (IConfigurationSection featureSection in _dotnetFeatureDefinitionSections.Value) { string featureName = featureSection.Key; @@ -165,7 +165,7 @@ public async IAsyncEnumerable GetAllFeatureDefinitionsAsync() private FeatureDefinition GetDotnetSchemaFeatureDefinition(string featureName) { - IConfigurationSection dotnetFeatureDefinitionConfiguration = _dotnetFeatureDefinitionSections + IConfigurationSection dotnetFeatureDefinitionConfiguration = _dotnetFeatureDefinitionSections.Value .FirstOrDefault(section => string.Equals(section.Key, featureName, StringComparison.OrdinalIgnoreCase)); @@ -179,7 +179,7 @@ private FeatureDefinition GetDotnetSchemaFeatureDefinition(string featureName) private FeatureDefinition GetMicrosoftSchemaFeatureDefinition(string featureName) { - IConfigurationSection microsoftFeatureDefinitionConfiguration = _microsoftFeatureDefinitionSections + IConfigurationSection microsoftFeatureDefinitionConfiguration = _microsoftFeatureDefinitionSections.Value .LastOrDefault(section => string.Equals(section[MicrosoftFeatureManagementFields.Id], featureName, StringComparison.OrdinalIgnoreCase)); 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 0e6a873f742f7b73a73e55a43259bde7ff6e2bfd Mon Sep 17 00:00:00 2001 From: zhiyuanliang Date: Thu, 3 Jul 2025 11:53:37 +0800 Subject: [PATCH 2/2] use ensureinit pattern without lock --- .../ConfigurationFeatureDefinitionProvider.cs | 45 ++++++++++++------- 1 file changed, 29 insertions(+), 16 deletions(-) diff --git a/src/Microsoft.FeatureManagement/ConfigurationFeatureDefinitionProvider.cs b/src/Microsoft.FeatureManagement/ConfigurationFeatureDefinitionProvider.cs index b0ec8d6b..44b2bcc3 100644 --- a/src/Microsoft.FeatureManagement/ConfigurationFeatureDefinitionProvider.cs +++ b/src/Microsoft.FeatureManagement/ConfigurationFeatureDefinitionProvider.cs @@ -23,11 +23,12 @@ public sealed class ConfigurationFeatureDefinitionProvider : IFeatureDefinitionP // IFeatureDefinitionProviderCacheable interface is only used to mark this provider as cacheable. This allows our test suite's // provider to be marked for caching as well. private readonly IConfiguration _configuration; - private Lazy> _dotnetFeatureDefinitionSections; - private Lazy> _microsoftFeatureDefinitionSections; + private IEnumerable _dotnetFeatureDefinitionSections; + private IEnumerable _microsoftFeatureDefinitionSections; 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 = new Lazy>(GetDotnetFeatureDefinitionSections); - - _microsoftFeatureDefinitionSections = new Lazy>(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(); - _dotnetFeatureDefinitionSections = new Lazy>(GetDotnetFeatureDefinitionSections); + _microsoftFeatureDefinitionSections = GetMicrosoftFeatureDefinitionSections(); - _microsoftFeatureDefinitionSections = new Lazy>(GetMicrosoftFeatureDefinitionSections); + _definitions.Clear(); } return _definitions.GetOrAdd(featureName, _getFeatureDefinitionFunc); @@ -115,16 +114,18 @@ 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(); - _dotnetFeatureDefinitionSections = new Lazy>(GetDotnetFeatureDefinitionSections); + _microsoftFeatureDefinitionSections = GetMicrosoftFeatureDefinitionSections(); - _microsoftFeatureDefinitionSections = new Lazy>(GetMicrosoftFeatureDefinitionSections); + _definitions.Clear(); } - foreach (IConfigurationSection featureSection in _microsoftFeatureDefinitionSections.Value) + foreach (IConfigurationSection featureSection in _microsoftFeatureDefinitionSections) { string featureName = featureSection[MicrosoftFeatureManagementFields.Id]; @@ -143,7 +144,7 @@ public async IAsyncEnumerable GetAllFeatureDefinitionsAsync() } } - foreach (IConfigurationSection featureSection in _dotnetFeatureDefinitionSections.Value) + foreach (IConfigurationSection featureSection in _dotnetFeatureDefinitionSections) { string featureName = featureSection.Key; @@ -163,9 +164,21 @@ public async IAsyncEnumerable GetAllFeatureDefinitionsAsync() } } + private void EnsureInit() + { + if (_initialized == 0) + { + _dotnetFeatureDefinitionSections = GetDotnetFeatureDefinitionSections(); + + _microsoftFeatureDefinitionSections = GetMicrosoftFeatureDefinitionSections(); + + _initialized = 1; + } + } + private FeatureDefinition GetDotnetSchemaFeatureDefinition(string featureName) { - IConfigurationSection dotnetFeatureDefinitionConfiguration = _dotnetFeatureDefinitionSections.Value + IConfigurationSection dotnetFeatureDefinitionConfiguration = _dotnetFeatureDefinitionSections .FirstOrDefault(section => string.Equals(section.Key, featureName, StringComparison.OrdinalIgnoreCase)); @@ -179,7 +192,7 @@ private FeatureDefinition GetDotnetSchemaFeatureDefinition(string featureName) private FeatureDefinition GetMicrosoftSchemaFeatureDefinition(string featureName) { - IConfigurationSection microsoftFeatureDefinitionConfiguration = _microsoftFeatureDefinitionSections.Value + IConfigurationSection microsoftFeatureDefinitionConfiguration = _microsoftFeatureDefinitionSections .LastOrDefault(section => string.Equals(section[MicrosoftFeatureManagementFields.Id], featureName, StringComparison.OrdinalIgnoreCase));