-
Notifications
You must be signed in to change notification settings - Fork 4.7k
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
Deadlock when enumerating resources with ResourceManager #74868
Comments
Tagging subscribers to this area: @dotnet/area-system-resources Issue DetailsDescriptionEnumerating resources with Reproduction StepsThe following code snippet hangs with high probability on .NET 6. Most commonly, this happens after printing the first entry on each thread. void Main()
{
var resourceType = typeof(MyResource); // standard resx with 15 entries
var resourceManager = new ResourceManager(resourceType.FullName, resourceType.Assembly);
var threads = 10;
using var barrier = new Barrier(threads);
Task.WaitAll(
Enumerable.Range(0, threads)
.Select(_ => Task.Run(() =>
{
barrier.SignalAndWait();
EnumerateResources();
}))
.ToArray()
); // never returns
void EnumerateResources()
{
var set = resourceManager.GetResourceSet(CultureInfo.InvariantCulture, createIfNotExists: true, tryParents: true);
foreach (var entry in set.Cast<DictionaryEntry>())
{
Console.WriteLine(resourceManager.GetString((string)entry.Key));
Thread.Sleep(1);
}
}
} Expected behaviorResourceManager is supposed to be thread safe, so this code should work. Actual behaviorFrequent hangs. Regression?I know that this used to be broken in some early versions of .NET Framework as well and was fixed at some point (perhaps in one of the 4.6 releases?). Testing the same snippet on .NET Framework latest does not hang, so I believe this is a regression. Known WorkaroundsAllocate a new Another sort-of workaround is to use the Configuration.NET 6 (6.0.5) Windows 10 x64. Other informationIn RuntimeResourceSet, we lock the cache first and then lock the In ResourceReader.ResourceEnumerator, we lock the reader first and then the cache. I believe that this is the cause of the deadlock.
|
I'd be happy to work on this as part of working on #74052 |
Concurrently enumerating a ResourceManager while also calling GetString() and similar methods was prone to both transient errors and deadlock. The transient errors were caused by RuntimeResourceSet calling internal methods on ResourceReader that did not properly lock. Now, all exposed methods on the Reader are thread-safe. The deadlock was called by inconsistent lock ordering between ResourceReader.ResourceEnumerator and RuntimeResourceSet which both lock on the RuntimeResourceSet's cache and on the ResourceReader itself. Now, the enumerator does not need to take both locks at the same time. Fix dotnet#74052 Fix dotnet#74868
Thanks for the report @madelson, and also for checking when did this regressed. From your original post though, I see that this issue was present in .NET 6 (meaning it wasn't a regression from .NET 6 to .NET 7), so I'm going to move this issue out of .NET 7 by setting the milestone to .NET 8 for now. Thanks again for working on this! |
@joperezr it isn't a regression from 6->7, but it is a regression from the previous LTS release (3.1) to the current LTS release (6). I imagine that the bar is too high to consider backporting this to 6 but given the severity of the issue if you encounter it (deadlocking the app) and the fact that apps may be unprepared for this given its previous fix, it would be nice to at least consider that. |
That's right, I didn't mean that this shouldn't get fixed in servicing, sorry for not making that clear. What I meant was that the fix is likely not going to hit .NET 7 before we ship, so I just adjusted the milestone to vNext for now. The process to get this regression fixed in servicing would be to:
|
* Fix thread-safety issues with enumerating ResourceManager. Concurrently enumerating a ResourceManager while also calling GetString() and similar methods was prone to both transient errors and deadlock. The transient errors were caused by RuntimeResourceSet calling internal methods on ResourceReader that did not properly lock. Now, all exposed methods on the Reader are thread-safe. The deadlock was called by inconsistent lock ordering between ResourceReader.ResourceEnumerator and RuntimeResourceSet which both lock on the RuntimeResourceSet's cache and on the ResourceReader itself. Now, the enumerator does not need to take both locks at the same time. Fix #74052 Fix #74868 * Remove trailing whitespace * Address feedback from #75054 * Add comment in response to #75054 (comment) * Implement feedback from #75054
Concurrently enumerating a ResourceManager while also calling GetString() and similar methods was prone to both transient errors and deadlock. The transient errors were caused by RuntimeResourceSet calling internal methods on ResourceReader that did not properly lock. Now, all exposed methods on the Reader are thread-safe. The deadlock was called by inconsistent lock ordering between ResourceReader.ResourceEnumerator and RuntimeResourceSet which both lock on the RuntimeResourceSet's cache and on the ResourceReader itself. Now, the enumerator does not need to take both locks at the same time. Fix #74052 Fix #74868
Concurrently enumerating a ResourceManager while also calling GetString() and similar methods was prone to both transient errors and deadlock. The transient errors were caused by RuntimeResourceSet calling internal methods on ResourceReader that did not properly lock. Now, all exposed methods on the Reader are thread-safe. The deadlock was called by inconsistent lock ordering between ResourceReader.ResourceEnumerator and RuntimeResourceSet which both lock on the RuntimeResourceSet's cache and on the ResourceReader itself. Now, the enumerator does not need to take both locks at the same time. Fix #74052 Fix #74868
Concurrently enumerating a ResourceManager while also calling GetString() and similar methods was prone to both transient errors and deadlock. The transient errors were caused by RuntimeResourceSet calling internal methods on ResourceReader that did not properly lock. Now, all exposed methods on the Reader are thread-safe. The deadlock was called by inconsistent lock ordering between ResourceReader.ResourceEnumerator and RuntimeResourceSet which both lock on the RuntimeResourceSet's cache and on the ResourceReader itself. Now, the enumerator does not need to take both locks at the same time. Fix #74052 Fix #74868
…er. (#81283) * Fix thread-safety issues with enumerating ResourceManager. Concurrently enumerating a ResourceManager while also calling GetString() and similar methods was prone to both transient errors and deadlock. The transient errors were caused by RuntimeResourceSet calling internal methods on ResourceReader that did not properly lock. Now, all exposed methods on the Reader are thread-safe. The deadlock was called by inconsistent lock ordering between ResourceReader.ResourceEnumerator and RuntimeResourceSet which both lock on the RuntimeResourceSet's cache and on the ResourceReader itself. Now, the enumerator does not need to take both locks at the same time. Fix #74052 Fix #74868 * Remove trailing whitespace * Address feedback from #75054 * Add comment in response to #75054 (comment) * Implement feedback from #75054 * Increase timeout for TestResourceManagerIsSafeForConcurrentAccessAndEnumeration (#80330) This raises the timeout to 30s, the same as what we have for the equivalent ResourceManager test (https://github.com/dotnet/runtime/blob/15fcb990fe17348ab6ddde0939200b900169920b/src/libraries/System.Resources.ResourceManager/tests/ResourceManagerTests.cs#L255). fix #80277 --------- Co-authored-by: Michael Adelson <mike.adelson314@gmail.com> Co-authored-by: madelson <1269046+madelson@users.noreply.github.com> Co-authored-by: Buyaa Namnan <bunamnan@microsoft.com>
…er. (#81281) * Fix thread-safety issues with enumerating ResourceManager. Concurrently enumerating a ResourceManager while also calling GetString() and similar methods was prone to both transient errors and deadlock. The transient errors were caused by RuntimeResourceSet calling internal methods on ResourceReader that did not properly lock. Now, all exposed methods on the Reader are thread-safe. The deadlock was called by inconsistent lock ordering between ResourceReader.ResourceEnumerator and RuntimeResourceSet which both lock on the RuntimeResourceSet's cache and on the ResourceReader itself. Now, the enumerator does not need to take both locks at the same time. Fix #74052 Fix #74868 * Remove trailing whitespace * Address feedback from #75054 * Add comment in response to #75054 (comment) * Implement feedback from #75054 * Increase timeout for TestResourceManagerIsSafeForConcurrentAccessAndEnumeration (#80330) This raises the timeout to 30s, the same as what we have for the equivalent ResourceManager test (https://github.com/dotnet/runtime/blob/15fcb990fe17348ab6ddde0939200b900169920b/src/libraries/System.Resources.ResourceManager/tests/ResourceManagerTests.cs#L255). fix #80277 --------- Co-authored-by: Michael Adelson <mike.adelson314@gmail.com> Co-authored-by: madelson <1269046+madelson@users.noreply.github.com> Co-authored-by: Carlos Sanchez <1175054+carlossanlop@users.noreply.github.com> Co-authored-by: Buyaa Namnan <bunamnan@microsoft.com>
Description
Enumerating resources with
ResourceManager.GetResourceSet
while concurrently callingResourceManager.GetString
can result in a hang which appears to be due to deadlock.Reproduction Steps
The following code snippet hangs with high probability on .NET 6. Most commonly, this happens after printing the first entry on each thread.
Expected behavior
ResourceManager is supposed to be thread safe, so this code should work.
Actual behavior
Frequent hangs.
Regression?
I know that this used to be broken in some early versions of .NET Framework as well and was fixed at some point (perhaps in one of the 4.6 releases?). Testing the same snippet on .NET Framework latest does not hang, so I believe this is a regression.
EDIT: based on further testing this appears to be a regression vs. .NET Core 3.1 as well.
Known Workarounds
Allocate a new
ResourceManager
instance when planning to callGetResourceSet
rather than re-using a shared instance.Another sort-of workaround is to use the
Key
property on the enumerator instead of theCurrent
property. This avoids the deadlock and makes more progress, but we instead run into #74052 .Configuration
.NET 6 (6.0.5) Windows 10 x64.
Other information
In RuntimeResourceSet, we lock the cache first and then lock the
ResourceReader
.In ResourceReader.ResourceEnumerator, we lock the reader first and then the cache.
I believe that this is the cause of the deadlock.
The text was updated successfully, but these errors were encountered: