Skip to content

Deadlock when enumerating resources with ResourceManager #74868

@madelson

Description

@madelson

Description

Enumerating resources with ResourceManager.GetResourceSet while concurrently calling ResourceManager.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.

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 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 call GetResourceSet rather than re-using a shared instance.

Another sort-of workaround is to use the Key property on the enumerator instead of the Current 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.

Metadata

Metadata

Assignees

Type

No type

Projects

No projects

Milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions