From 09b9a49da6d0e9b06a4fc3a05ae11ecb43149e33 Mon Sep 17 00:00:00 2001 From: Martin Costello Date: Mon, 4 Mar 2019 18:14:16 +0000 Subject: [PATCH] Atomically swap config data (dotnet/extensions#1202) Swap the configuration providers' data atomically, rather than directly changing the property, so that any enumeration of the dictionary running during the reload operation does not throw an InvalidOperationException due to the collection being modified. Relates to dotnet/extensions#1189.\n\nCommit migrated from https://github.com/dotnet/extensions/commit/192abfdf3e73106e40d7651eecfb621e4f78c344 --- .../src/KeyPerFileConfigurationProvider.cs | 7 ++- .../test/KeyPerFileTests.cs | 43 +++++++++++++++++++ 2 files changed, 48 insertions(+), 2 deletions(-) diff --git a/src/Configuration.KeyPerFile/src/KeyPerFileConfigurationProvider.cs b/src/Configuration.KeyPerFile/src/KeyPerFileConfigurationProvider.cs index 13541110e6..2e33b9dfcd 100644 --- a/src/Configuration.KeyPerFile/src/KeyPerFileConfigurationProvider.cs +++ b/src/Configuration.KeyPerFile/src/KeyPerFileConfigurationProvider.cs @@ -31,12 +31,13 @@ namespace Microsoft.Extensions.Configuration.KeyPerFile /// public override void Load() { - Data = new Dictionary(StringComparer.OrdinalIgnoreCase); + var data = new Dictionary(StringComparer.OrdinalIgnoreCase); if (Source.FileProvider == null) { if (Source.Optional) { + Data = data; return; } @@ -61,10 +62,12 @@ namespace Microsoft.Extensions.Configuration.KeyPerFile { if (Source.IgnoreCondition == null || !Source.IgnoreCondition(file.Name)) { - Data.Add(NormalizeKey(file.Name), TrimNewLine(streamReader.ReadToEnd())); + data.Add(NormalizeKey(file.Name), TrimNewLine(streamReader.ReadToEnd())); } } } + + Data = data; } private string GetDirectoryName() diff --git a/src/Configuration.KeyPerFile/test/KeyPerFileTests.cs b/src/Configuration.KeyPerFile/test/KeyPerFileTests.cs index 4de528c011..838e62222d 100644 --- a/src/Configuration.KeyPerFile/test/KeyPerFileTests.cs +++ b/src/Configuration.KeyPerFile/test/KeyPerFileTests.cs @@ -6,6 +6,8 @@ using System.Collections; using System.Collections.Generic; using System.IO; using System.Text; +using System.Threading; +using System.Threading.Tasks; using Microsoft.Extensions.FileProviders; using Microsoft.Extensions.Primitives; using Xunit; @@ -179,6 +181,47 @@ namespace Microsoft.Extensions.Configuration.KeyPerFile.Test Assert.Equal("SecretValue1", config["ignore.Secret1"]); Assert.Equal("SecretValue2", config["Secret2"]); } + + [Fact] + public void BindingDoesNotThrowIfReloadedDuringBinding() + { + var testFileProvider = new TestFileProvider( + new TestFile("Number", "-2"), + new TestFile("Text", "Foo")); + + var config = new ConfigurationBuilder() + .AddKeyPerFile(o => o.FileProvider = testFileProvider) + .Build(); + + MyOptions options = null; + + using (var cts = new CancellationTokenSource(TimeSpan.FromMilliseconds(250))) + { + void ReloadLoop() + { + while (!cts.IsCancellationRequested) + { + config.Reload(); + } + } + + _ = Task.Run(ReloadLoop); + + while (!cts.IsCancellationRequested) + { + options = config.Get(); + } + } + + Assert.Equal(-2, options.Number); + Assert.Equal("Foo", options.Text); + } + + private sealed class MyOptions + { + public int Number { get; set; } + public string Text { get; set; } + } } class TestFileProvider : IFileProvider