@@ -14,15 +14,20 @@ namespace Microsoft.Extensions.Configuration
1414{
1515 /// <summary>
1616 /// ConfigurationManager is a mutable configuration object. It is both an <see cref="IConfigurationBuilder"/> and an <see cref="IConfigurationRoot"/>.
17- /// As sources are added, it updates its current view of configuration. Once Build is called, configuration is frozen.
17+ /// As sources are added, it updates its current view of configuration.
1818 /// </summary>
1919 public sealed class ConfigurationManager : IConfigurationBuilder , IConfigurationRoot , IDisposable
2020 {
21+ // Concurrently modifying config sources or properties is not thread-safe. However, it is thread-safe to read config while modifying sources or properties.
2122 private readonly ConfigurationSources _sources ;
2223 private readonly ConfigurationBuilderProperties _properties ;
2324
24- private readonly object _providerLock = new ( ) ;
25- private readonly List < IConfigurationProvider > _providers = new ( ) ;
25+ // ReferenceCountedProviderManager manages copy-on-write references to support concurrently reading config while modifying sources.
26+ // It waits for readers to unreference the providers before disposing them without blocking on any concurrent operations.
27+ private readonly ReferenceCountedProviderManager _providerManager = new ( ) ;
28+
29+ // _changeTokenRegistrations is only modified when config sources are modified. It is not referenced by any read operations.
30+ // Because modify config sources is not thread-safe, modifying _changeTokenRegistrations does not need to be thread-safe either.
2631 private readonly List < IDisposable > _changeTokenRegistrations = new ( ) ;
2732 private ConfigurationReloadToken _changeToken = new ( ) ;
2833
@@ -43,55 +48,36 @@ public string? this[string key]
4348 {
4449 get
4550 {
46- lock ( _providerLock )
47- {
48- return ConfigurationRoot . GetConfiguration ( _providers , key ) ;
49- }
51+ using ReferenceCountedProviders reference = _providerManager . GetReference ( ) ;
52+ return ConfigurationRoot . GetConfiguration ( reference . Providers , key ) ;
5053 }
5154 set
5255 {
53- lock ( _providerLock )
54- {
55- ConfigurationRoot . SetConfiguration ( _providers , key , value ) ;
56- }
56+ using ReferenceCountedProviders reference = _providerManager . GetReference ( ) ;
57+ ConfigurationRoot . SetConfiguration ( reference . Providers , key , value ) ;
5758 }
5859 }
5960
6061 /// <inheritdoc/>
6162 public IConfigurationSection GetSection ( string key ) => new ConfigurationSection ( this , key ) ;
6263
6364 /// <inheritdoc/>
64- public IEnumerable < IConfigurationSection > GetChildren ( )
65- {
66- lock ( _providerLock )
67- {
68- // ToList() to eagerly evaluate inside lock.
69- return this . GetChildrenImplementation ( null ) . ToList ( ) ;
70- }
71- }
65+ public IEnumerable < IConfigurationSection > GetChildren ( ) => this . GetChildrenImplementation ( null ) ;
7266
7367 IDictionary < string , object > IConfigurationBuilder . Properties => _properties ;
7468
7569 IList < IConfigurationSource > IConfigurationBuilder . Sources => _sources ;
7670
77- IEnumerable < IConfigurationProvider > IConfigurationRoot . Providers
78- {
79- get
80- {
81- lock ( _providerLock )
82- {
83- return new List < IConfigurationProvider > ( _providers ) ;
84- }
85- }
86- }
71+ // We cannot track the duration of the reference to the providers if this property is used.
72+ // If a configuration source is removed after this is accessed but before it's completely enumerated,
73+ // this may allow access to a disposed provider.
74+ IEnumerable < IConfigurationProvider > IConfigurationRoot . Providers => _providerManager . NonReferenceCountedProviders ;
8775
8876 /// <inheritdoc/>
8977 public void Dispose ( )
9078 {
91- lock ( _providerLock )
92- {
93- DisposeRegistrationsAndProvidersUnsynchronized ( ) ;
94- }
79+ DisposeRegistrations ( ) ;
80+ _providerManager . Dispose ( ) ;
9581 }
9682
9783 IConfigurationBuilder IConfigurationBuilder . Add ( IConfigurationSource source )
@@ -106,9 +92,9 @@ IConfigurationBuilder IConfigurationBuilder.Add(IConfigurationSource source)
10692
10793 void IConfigurationRoot . Reload ( )
10894 {
109- lock ( _providerLock )
95+ using ( ReferenceCountedProviders reference = _providerManager . GetReference ( ) )
11096 {
111- foreach ( var provider in _providers )
97+ foreach ( IConfigurationProvider provider in reference . Providers )
11298 {
11399 provider . Load ( ) ;
114100 }
@@ -117,6 +103,8 @@ void IConfigurationRoot.Reload()
117103 RaiseChanged ( ) ;
118104 }
119105
106+ internal ReferenceCountedProviders GetProvidersReference ( ) => _providerManager . GetReference ( ) ;
107+
120108 private void RaiseChanged ( )
121109 {
122110 var previousToken = Interlocked . Exchange ( ref _changeToken , new ConfigurationReloadToken ( ) ) ;
@@ -126,59 +114,49 @@ private void RaiseChanged()
126114 // Don't rebuild and reload all providers in the common case when a source is simply added to the IList.
127115 private void AddSource ( IConfigurationSource source )
128116 {
129- lock ( _providerLock )
130- {
131- var provider = source . Build ( this ) ;
132- _providers . Add ( provider ) ;
117+ IConfigurationProvider provider = source . Build ( this ) ;
133118
134- provider . Load ( ) ;
135- _changeTokenRegistrations . Add ( ChangeToken . OnChange ( ( ) => provider . GetReloadToken ( ) , ( ) => RaiseChanged ( ) ) ) ;
136- }
119+ provider . Load ( ) ;
120+ _changeTokenRegistrations . Add ( ChangeToken . OnChange ( ( ) => provider . GetReloadToken ( ) , ( ) => RaiseChanged ( ) ) ) ;
137121
122+ _providerManager . AddProvider ( provider ) ;
138123 RaiseChanged ( ) ;
139124 }
140125
141126 // Something other than Add was called on IConfigurationBuilder.Sources or IConfigurationBuilder.Properties has changed.
142127 private void ReloadSources ( )
143128 {
144- lock ( _providerLock )
145- {
146- DisposeRegistrationsAndProvidersUnsynchronized ( ) ;
129+ DisposeRegistrations ( ) ;
147130
148- _changeTokenRegistrations . Clear ( ) ;
149- _providers . Clear ( ) ;
131+ _changeTokenRegistrations . Clear ( ) ;
150132
151- foreach ( var source in _sources )
152- {
153- _providers . Add ( source . Build ( this ) ) ;
154- }
133+ var newProvidersList = new List < IConfigurationProvider > ( ) ;
155134
156- foreach ( var p in _providers )
157- {
158- p . Load ( ) ;
159- _changeTokenRegistrations . Add ( ChangeToken . OnChange ( ( ) => p . GetReloadToken ( ) , ( ) => RaiseChanged ( ) ) ) ;
160- }
135+ foreach ( IConfigurationSource source in _sources )
136+ {
137+ newProvidersList . Add ( source . Build ( this ) ) ;
138+ }
139+
140+ foreach ( IConfigurationProvider p in newProvidersList )
141+ {
142+ p . Load ( ) ;
143+ _changeTokenRegistrations . Add ( ChangeToken . OnChange ( ( ) => p . GetReloadToken ( ) , ( ) => RaiseChanged ( ) ) ) ;
161144 }
162145
146+ _providerManager . ReplaceProviders ( newProvidersList ) ;
163147 RaiseChanged ( ) ;
164148 }
165149
166- private void DisposeRegistrationsAndProvidersUnsynchronized ( )
150+ private void DisposeRegistrations ( )
167151 {
168152 // dispose change token registrations
169- foreach ( var registration in _changeTokenRegistrations )
153+ foreach ( IDisposable registration in _changeTokenRegistrations )
170154 {
171155 registration . Dispose ( ) ;
172156 }
173-
174- // dispose providers
175- foreach ( var provider in _providers )
176- {
177- ( provider as IDisposable ) ? . Dispose ( ) ;
178- }
179157 }
180158
181- private class ConfigurationSources : IList < IConfigurationSource >
159+ private sealed class ConfigurationSources : IList < IConfigurationSource >
182160 {
183161 private readonly List < IConfigurationSource > _sources = new ( ) ;
184162 private readonly ConfigurationManager _config ;
@@ -259,7 +237,7 @@ IEnumerator IEnumerable.GetEnumerator()
259237 }
260238 }
261239
262- private class ConfigurationBuilderProperties : IDictionary < string , object >
240+ private sealed class ConfigurationBuilderProperties : IDictionary < string , object >
263241 {
264242 private readonly Dictionary < string , object > _properties = new ( ) ;
265243 private readonly ConfigurationManager _config ;
0 commit comments