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 192abfdf3e
This commit is contained in:
parent
aff09d16a1
commit
09b9a49da6
|
|
@ -31,12 +31,13 @@ namespace Microsoft.Extensions.Configuration.KeyPerFile
|
|||
/// </summary>
|
||||
public override void Load()
|
||||
{
|
||||
Data = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase);
|
||||
var data = new Dictionary<string, string>(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()
|
||||
|
|
|
|||
|
|
@ -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<MyOptions>();
|
||||
}
|
||||
}
|
||||
|
||||
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
|
||||
|
|
|
|||
Loading…
Reference in New Issue