Skip to content

Commit 3d91ad5

Browse files
martincostelloTratcher
authored andcommitted
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 dotnet/extensions@192abfd
1 parent f6ed44d commit 3d91ad5

File tree

2 files changed

+48
-2
lines changed

2 files changed

+48
-2
lines changed

src/Configuration/Config.KeyPerFile/src/KeyPerFileConfigurationProvider.cs

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -31,12 +31,13 @@ private static string TrimNewLine(string value)
3131
/// </summary>
3232
public override void Load()
3333
{
34-
Data = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase);
34+
var data = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase);
3535

3636
if (Source.FileProvider == null)
3737
{
3838
if (Source.Optional)
3939
{
40+
Data = data;
4041
return;
4142
}
4243

@@ -61,10 +62,12 @@ public override void Load()
6162
{
6263
if (Source.IgnoreCondition == null || !Source.IgnoreCondition(file.Name))
6364
{
64-
Data.Add(NormalizeKey(file.Name), TrimNewLine(streamReader.ReadToEnd()));
65+
data.Add(NormalizeKey(file.Name), TrimNewLine(streamReader.ReadToEnd()));
6566
}
6667
}
6768
}
69+
70+
Data = data;
6871
}
6972

7073
private string GetDirectoryName()

src/Configuration/Config.KeyPerFile/test/KeyPerFileTests.cs

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,8 @@
66
using System.Collections.Generic;
77
using System.IO;
88
using System.Text;
9+
using System.Threading;
10+
using System.Threading.Tasks;
911
using Microsoft.Extensions.FileProviders;
1012
using Microsoft.Extensions.Primitives;
1113
using Xunit;
@@ -179,6 +181,47 @@ public void CanUnIgnoreDefaultFiles()
179181
Assert.Equal("SecretValue1", config["ignore.Secret1"]);
180182
Assert.Equal("SecretValue2", config["Secret2"]);
181183
}
184+
185+
[Fact]
186+
public void BindingDoesNotThrowIfReloadedDuringBinding()
187+
{
188+
var testFileProvider = new TestFileProvider(
189+
new TestFile("Number", "-2"),
190+
new TestFile("Text", "Foo"));
191+
192+
var config = new ConfigurationBuilder()
193+
.AddKeyPerFile(o => o.FileProvider = testFileProvider)
194+
.Build();
195+
196+
MyOptions options = null;
197+
198+
using (var cts = new CancellationTokenSource(TimeSpan.FromMilliseconds(250)))
199+
{
200+
void ReloadLoop()
201+
{
202+
while (!cts.IsCancellationRequested)
203+
{
204+
config.Reload();
205+
}
206+
}
207+
208+
_ = Task.Run(ReloadLoop);
209+
210+
while (!cts.IsCancellationRequested)
211+
{
212+
options = config.Get<MyOptions>();
213+
}
214+
}
215+
216+
Assert.Equal(-2, options.Number);
217+
Assert.Equal("Foo", options.Text);
218+
}
219+
220+
private sealed class MyOptions
221+
{
222+
public int Number { get; set; }
223+
public string Text { get; set; }
224+
}
182225
}
183226

184227
class TestFileProvider : IFileProvider

0 commit comments

Comments
 (0)