-
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
Merged
Merged
Changes from all commits
Commits
Show all changes
14 commits
Select commit
Hold shift + click to select a range
df0c920
WIP Add test
halter73 9a625f9
Simplify ProviderCanBlockLoadWaitingOnConcurrentRead
halter73 1766a7c
Remove ConfigurationManager._providerLock
halter73 25da85d
Add RefCountedProviders
halter73 d26846a
Add ProviderDisposeDelayedWaitingOnConcurrentRead test
halter73 55b07b7
fix test
halter73 f99b2af
private sealed class
halter73 228e978
Address PR feedback
halter73 b361192
Always hold providers reference in GetChildren()
halter73 69ec64d
Update src/libraries/Microsoft.Extensions.Configuration/src/Reference…
halter73 c785706
Address PR feedback
halter73 30b1c36
Never dispose providers more than once
halter73 1725b2b
Add DisposingConfigurationManagerCausesOnlySourceChangesToThrow
halter73 9073ebc
Lazily allocate DisposedReferenceCountedProviders
halter73 File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
93 changes: 93 additions & 0 deletions
93
src/libraries/Microsoft.Extensions.Configuration/src/ReferenceCountedProviders.cs
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,93 @@ | ||
| // 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 abstract class ReferenceCountedProviders : IDisposable | ||
| { | ||
| public static ReferenceCountedProviders Create(List<IConfigurationProvider> providers) => new ActiveReferenceCountedProviders(providers); | ||
|
|
||
| // If anything references DisposedReferenceCountedProviders, it indicates something is using the ConfigurationManager after it's been disposed. | ||
| // We could preemptively throw an ODE from ReferenceCountedProviderManager.GetReference() instead of returning this type, but this might | ||
| // break existing apps that are previously able to continue to read configuration after disposing an ConfigurationManager. | ||
| public static ReferenceCountedProviders CreateDisposed(List<IConfigurationProvider> providers) => new DisposedReferenceCountedProviders(providers); | ||
|
|
||
| public abstract List<IConfigurationProvider> Providers { get; set; } | ||
|
|
||
| // NonReferenceCountedProviders is only used to: | ||
| // 1. Support IConfigurationRoot.Providers because we cannot track the lifetime of that reference. | ||
| // 2. Construct DisposedReferenceCountedProviders because the providers are disposed anyway and no longer reference counted. | ||
| public abstract List<IConfigurationProvider> NonReferenceCountedProviders { get; } | ||
|
|
||
| public abstract void AddReference(); | ||
| // This is Dispose() rather than RemoveReference() so we can conveniently release a reference at the end of a using block. | ||
| public abstract void Dispose(); | ||
|
|
||
| private sealed class ActiveReferenceCountedProviders : ReferenceCountedProviders | ||
| { | ||
| 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 ActiveReferenceCountedProviders(List<IConfigurationProvider> providers) | ||
| { | ||
| _providers = providers; | ||
| } | ||
|
|
||
| public override List<IConfigurationProvider> Providers | ||
| { | ||
| get | ||
| { | ||
| Debug.Assert(_refCount > 0); | ||
| return _providers; | ||
| } | ||
| set | ||
| { | ||
| Debug.Assert(_refCount > 0); | ||
| _providers = value; | ||
| } | ||
| } | ||
|
|
||
| public override List<IConfigurationProvider> NonReferenceCountedProviders => _providers; | ||
|
|
||
| public override 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 override void Dispose() | ||
| { | ||
| if (Interlocked.Decrement(ref _refCount) == 0) | ||
| { | ||
| foreach (IConfigurationProvider provider in _providers) | ||
| { | ||
| (provider as IDisposable)?.Dispose(); | ||
| } | ||
| } | ||
| } | ||
| } | ||
|
|
||
| private sealed class DisposedReferenceCountedProviders : ReferenceCountedProviders | ||
| { | ||
| public DisposedReferenceCountedProviders(List<IConfigurationProvider> providers) | ||
| { | ||
| Providers = providers; | ||
| } | ||
|
|
||
| public override List<IConfigurationProvider> Providers { get; set; } | ||
| public override List<IConfigurationProvider> NonReferenceCountedProviders => Providers; | ||
|
|
||
| public override void AddReference() { } | ||
| public override void Dispose() { } | ||
| } | ||
| } | ||
| } |
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.