Skip to content
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

System.Resources.ResourceManager is not thread safe when ignore case is enabled #74052

Closed
longnlp opened this issue Aug 17, 2022 · 9 comments · Fixed by #75054
Closed

System.Resources.ResourceManager is not thread safe when ignore case is enabled #74052

longnlp opened this issue Aug 17, 2022 · 9 comments · Fixed by #75054
Assignees
Milestone

Comments

@longnlp
Copy link

longnlp commented Aug 17, 2022

Description

I'm using ResrouceManager to access the resources file in satellite assemblies, and the function is working fine as expected, however I cannot get expected value if I turn on the ignore case for the Resrouce Manager.

Reproduction Steps

  1. I prepared a resource file which has 26 strings, the name is capital letter, and the value is lowercase letter. for example: A -> a; B -> b; ...; Y -> y; Z -> z
  2. I created 52 tasks to call the GetString method to get value for all alphabet ( including capital letters and lower case letters)

Following is the validation code to verify the value

   static void TestResource(ResourceManager manager, string key, Flags flags)
    {
        try
        {
            var value = manager.GetString(key);

            if(!key.ToLower().Equals(value))
            {
                flags.Exception = true;
                Console.WriteLine($"{key} -> {value}");
            }
        }
        catch (Exception ex)
        {
            Console.WriteLine(ex.ToString());
        }

    }

Expected behavior

The expected value should be the lower case of the key.

Actual behavior

Below is the output, you can see the the value for E is not e, it's ☺, and some tasks have exception to get value.

E -> ☺
System.FormatException: Corrupt .resources file. Invalid offset '23593217' into data section.
at System.Resources.ResourceReader.AllocateStringForNameIndex(Int32 index, Int32& dataOffset)
at System.Resources.ResourceReader.ResourceEnumerator.get_Key()
at System.Resources.RuntimeResourceSet.GetObject(String key, Boolean ignoreCase, Boolean isString)
at System.Resources.ResourceManager.GetString(String name, CultureInfo culture)
at System.Resources.ResourceManager.GetString(String name)
at MyApp.Program.TestResource(ResourceManager manager, String key, Flags flags)

Regression?

No response

Known Workarounds

The workaround is to add lock for the GetString method to prevent concurrent access.

Configuration

  • Which version of .NET is the code running on?
    .Net 6
  • What OS and version, and what distro if applicable?
    Windows 11, [Version 10.0.22000.856]
  • What is the architecture (x64, x86, ARM, ARM64)?
    x64
  • Do you know whether it is specific to that configuration?
    I don't think so.
  • If you're using Blazor, which web browser(s) do you see this issue in?
    N/A

Other information

The GetObject method in RuntimeResourceSet has two lock, and I believe we should use one lock for all cases because the second lock cannot lock it when multiple threads call this method.

@dotnet-issue-labeler
Copy link

I couldn't figure out the best area label to add to this issue. If you have write-permissions please help me learn by adding exactly one area label.

@ghost ghost added the untriaged New issue has not been triaged by the area owner label Aug 17, 2022
@ghost
Copy link

ghost commented Aug 17, 2022

Tagging subscribers to this area: @dotnet/area-system-resources
See info in area-owners.md if you want to be subscribed.

Issue Details

Description

I'm using ResrouceManager to access the resources file in satellite assemblies, and the function is working fine as expected, however I cannot get expected value if I turn on the ignore case for the Resrouce Manager.

Reproduction Steps

  1. I prepared a resource file which has 26 strings, the name is capital letter, and the value is lowercase letter. for example: A -> a; B -> b; ...; Y -> y; Z -> z
  2. I created 52 tasks to call the GetString method to get value for all alphabet ( including capital letters and lower case letters)

Following is the validation code to verify the value

   static void TestResource(ResourceManager manager, string key, Flags flags)
    {
        try
        {
            var value = manager.GetString(key);

            if(!key.ToLower().Equals(value))
            {
                flags.Exception = true;
                Console.WriteLine($"{key} -> {value}");
            }
        }
        catch (Exception ex)
        {
            Console.WriteLine(ex.ToString());
        }

    }

Expected behavior

The expected value should be the lower case of the key.

Actual behavior

Below is the output, you can see the the value for E is not e, it's ☺, and some tasks have exception to get value.

E -> ☺
System.FormatException: Corrupt .resources file. Invalid offset '23593217' into data section.
at System.Resources.ResourceReader.AllocateStringForNameIndex(Int32 index, Int32& dataOffset)
at System.Resources.ResourceReader.ResourceEnumerator.get_Key()
at System.Resources.RuntimeResourceSet.GetObject(String key, Boolean ignoreCase, Boolean isString)
at System.Resources.ResourceManager.GetString(String name, CultureInfo culture)
at System.Resources.ResourceManager.GetString(String name)
at MyApp.Program.TestResource(ResourceManager manager, String key, Flags flags)

Regression?

No response

Known Workarounds

The workaround is to add lock for the GetString method to prevent concurrent access.

Configuration

  • Which version of .NET is the code running on?
    .Net 6
  • What OS and version, and what distro if applicable?
    Windows 11, [Version 10.0.22000.856]
  • What is the architecture (x64, x86, ARM, ARM64)?
    x64
  • Do you know whether it is specific to that configuration?
    I don't think so.
  • If you're using Blazor, which web browser(s) do you see this issue in?
    N/A

Other information

The GetObject method in RuntimeResourceSet has two lock, and I believe we should use one lock for all cases because the second lock cannot lock it when multiple threads call this method.

Author: longnlp
Assignees: -
Labels:

area-System.Resources, untriaged

Milestone: -

@danmoseley
Copy link
Member

That seems like a bug as GetString should be thread safe.
It would be interesting to know whether this bug exists for .NET Framework.

@tarekgh
Copy link
Member

tarekgh commented Aug 17, 2022

@danmoseley looks you are correct. Some fields in the ResourceReader should be accessed inside a lock but this does not happen in all cases. _store is a good example of that.

@danmoseley
Copy link
Member

The locking in this and its related classes needs some auditing, and it would probably be helpful to add some Debug.Assert(Monitor.IsEntered(..)) and comments to make clear where protection is expected, what is shared state etc so it can be reasoned about more easily.

@buyaa-n
Copy link
Contributor

buyaa-n commented Aug 17, 2022

Doesn't seem regression, adding to future milestone for now, @danmoseley @tarekgh feel free to prioritize as needed

@buyaa-n buyaa-n added this to the Future milestone Aug 17, 2022
@ghost ghost removed the untriaged New issue has not been triaged by the area owner label Aug 17, 2022
@danmoseley
Copy link
Member

should we mark help wanted?

@buyaa-n buyaa-n added the help wanted [up-for-grabs] Good issue for external contributors label Aug 18, 2022
@madelson
Copy link
Contributor

I'd be interested in working on this.

@buyaa-n buyaa-n removed the help wanted [up-for-grabs] Good issue for external contributors label Aug 30, 2022
@danmoseley
Copy link
Member

It's yours @madelson

madelson added a commit to madelson/runtime that referenced this issue Sep 3, 2022
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
@ghost ghost added the in-pr There is an active PR which will close this issue when it is merged label Sep 3, 2022
stephentoub pushed a commit that referenced this issue Jan 3, 2023
* 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
@ghost ghost removed the in-pr There is an active PR which will close this issue when it is merged label Jan 3, 2023
github-actions bot pushed a commit that referenced this issue Jan 27, 2023
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
github-actions bot pushed a commit that referenced this issue Jan 27, 2023
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
@ghost ghost locked as resolved and limited conversation to collaborators Feb 2, 2023
carlossanlop pushed a commit that referenced this issue Feb 10, 2023
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
carlossanlop pushed a commit that referenced this issue Mar 10, 2023
…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 <[email protected]>
Co-authored-by: madelson <[email protected]>
Co-authored-by: Buyaa Namnan <[email protected]>
carlossanlop added a commit that referenced this issue Mar 10, 2023
…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 <[email protected]>
Co-authored-by: madelson <[email protected]>
Co-authored-by: Carlos Sanchez <[email protected]>
Co-authored-by: Buyaa Namnan <[email protected]>
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Projects
None yet
Development

Successfully merging a pull request may close this issue.

5 participants