-
Notifications
You must be signed in to change notification settings - Fork 5.3k
Avoid deadlock with ConfigurationManager #62209
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from 9 commits
df0c920
9a625f9
1766a7c
25da85d
d26846a
55b07b7
f99b2af
228e978
b361192
69ec64d
c785706
30b1c36
1725b2b
9073ebc
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,59 @@ | ||
| // Licensed to the .NET Foundation under one or more agreements. | ||
| // The .NET Foundation licenses this file to you under the MIT license. | ||
|
|
||
| using System; | ||
| using System.Collections.Generic; | ||
| using System.Diagnostics; | ||
| using System.Threading; | ||
|
|
||
| namespace Microsoft.Extensions.Configuration | ||
| { | ||
| // ReferenceCountedProviders is used by ConfigurationManager to wait until all readers unreference it before disposing any providers. | ||
| internal sealed class ReferenceCountedProviders : IDisposable | ||
| { | ||
| private long _refCount = 1; | ||
| // volatile is not strictly necessary because the runtime adds a barrier either way, but volatile indicates that this field has | ||
| // unsynchronized readers meaning the all writes initializing the list must be published before updating the _providers reference. | ||
| private volatile List<IConfigurationProvider> _providers; | ||
|
|
||
| public ReferenceCountedProviders(List<IConfigurationProvider> providers) | ||
| { | ||
| _providers = providers; | ||
| } | ||
|
|
||
| public List<IConfigurationProvider> Providers | ||
| { | ||
| get | ||
| { | ||
| Debug.Assert(_refCount > 0); | ||
| return _providers; | ||
| } | ||
| set | ||
| { | ||
| Debug.Assert(_refCount > 0); | ||
| _providers = value; | ||
| } | ||
| } | ||
|
|
||
| // This is only used to support IConfigurationRoot.Providers because we cannot track the lifetime of that reference. | ||
| public List<IConfigurationProvider> NonReferenceCountedProviders => _providers; | ||
|
|
||
| public void AddReference() | ||
| { | ||
| // AddReference() is always called with a lock to ensure _refCount hasn't already decremented to zero. | ||
| Debug.Assert(_refCount > 0); | ||
| Interlocked.Increment(ref _refCount); | ||
| } | ||
|
|
||
| public void Dispose() | ||
| { | ||
| if (Interlocked.Decrement(ref _refCount) == 0) | ||
| { | ||
| foreach (var provider in _providers) | ||
halter73 marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||
| { | ||
| (provider as IDisposable)?.Dispose(); | ||
| } | ||
| } | ||
| } | ||
| } | ||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,55 @@ | ||
| // Licensed to the .NET Foundation under one or more agreements. | ||
| // The .NET Foundation licenses this file to you under the MIT license. | ||
|
|
||
| using System; | ||
| using System.Collections.Generic; | ||
|
|
||
| namespace Microsoft.Extensions.Configuration | ||
| { | ||
| // ReferenceCountedProviderManager is used by ConfigurationManager to provide copy-on-write references that support concurrently | ||
| // reading config while modifying sources. It waits for readers to unreference the providers before disposing them | ||
| // without blocking on any concurrent operations. | ||
| internal sealed class ReferenceCountedProviderManager : IDisposable | ||
| { | ||
| private readonly object _replaceProvidersLock = new object(); | ||
| private ReferenceCountedProviders _refCountedProviders = new(new List<IConfigurationProvider>()); | ||
|
|
||
| // This is only used to support IConfigurationRoot.Providers because we cannot track the lifetime of that reference. | ||
| public IEnumerable<IConfigurationProvider> NonReferenceCountedProviders => _refCountedProviders.NonReferenceCountedProviders; | ||
|
|
||
| public ReferenceCountedProviders GetReference() | ||
| { | ||
| // Lock to ensure oldRefCountedProviders.Dispose() in ReplaceProviders() doesn't decrement ref count to zero | ||
| // before calling _refCountedProviders.AddRef(). | ||
halter73 marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||
| lock (_replaceProvidersLock) | ||
| { | ||
| _refCountedProviders.AddReference(); | ||
| return _refCountedProviders; | ||
| } | ||
| } | ||
|
|
||
| // Providers should never be concurrently modified. Reading during modification is allowed. | ||
| public void ReplaceProviders(List<IConfigurationProvider> providers) | ||
| { | ||
| ReferenceCountedProviders oldRefCountedProviders = _refCountedProviders; | ||
|
|
||
| lock (_replaceProvidersLock) | ||
| { | ||
| _refCountedProviders = new ReferenceCountedProviders(providers); | ||
| } | ||
|
|
||
| oldRefCountedProviders.Dispose(); | ||
| } | ||
|
|
||
| public void AddProvider(IConfigurationProvider provider) | ||
| { | ||
| // Maintain existing references, but replace list with copy containing new item. | ||
| _refCountedProviders.Providers = new List<IConfigurationProvider>(_refCountedProviders.Providers) | ||
| { | ||
| provider | ||
| }; | ||
| } | ||
|
|
||
| public void Dispose() => _refCountedProviders.Dispose(); | ||
|
||
| } | ||
| } | ||
Uh oh!
There was an error while loading. Please reload this page.