-
-
Notifications
You must be signed in to change notification settings - Fork 382
fix(Localizer): makes JsonStringLocalizerFactory thread-safe #7919
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 all commits
340dda5
1a0a935
f405611
8c73013
49f36a0
5cfb505
cd8863e
8a1889e
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 | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
|
|
@@ -330,6 +330,20 @@ public void GetResourcePrefix_Ok() | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| Assert.Equal("test", result.Value); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| [Fact] | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| public void CreateResourceManagerStringLocalizer_UseBaseNameWhenTypeNameIsNull() | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| var factory = Context.Services.GetRequiredService<IStringLocalizerFactory>(); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| var mi = factory.GetType().GetMethod("CreateResourceManagerStringLocalizer", BindingFlags.NonPublic | BindingFlags.Instance)!; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| var baseName = typeof(Foo).FullName!; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| var localizer = Assert.IsType<IStringLocalizer>(mi.Invoke(factory, [typeof(Foo).Assembly, baseName]), exactMatch: false); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| var result = localizer["not-found-key"]; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| Assert.True(result.ResourceNotFound); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| Assert.Equal(baseName, result.SearchedLocation); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| [Fact] | |
| public async Task Create_Ok_InParallel() | |
| { | |
| var factory = Context.Services.GetRequiredService<IStringLocalizerFactory>(); | |
| var expectedNamedSearchedLocation = factory.Create("Lang", "UnitTest")["not-found-key"].SearchedLocation; | |
| var expectedTypedSearchedLocation = factory.Create(typeof(Foo))["not-found-key"].SearchedLocation; | |
| var tasks = Enumerable.Range(0, 50).Select(async index => | |
| { | |
| await Task.Yield(); | |
| if (index % 2 == 0) | |
| { | |
| var localizer = factory.Create("Lang", "UnitTest"); | |
| var result = localizer[$"not-found-key-{index}"]; | |
| Assert.True(result.ResourceNotFound); | |
| Assert.Equal(expectedNamedSearchedLocation, result.SearchedLocation); | |
| } | |
| else | |
| { | |
| var localizer = factory.Create(typeof(Foo)); | |
| var result = localizer[$"not-found-key-{index}"]; | |
| Assert.True(result.ResourceNotFound); | |
| Assert.Equal(expectedTypedSearchedLocation, result.SearchedLocation); | |
| } | |
| }); | |
| await Task.WhenAll(tasks); | |
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
suggestion (testing): Add a test that covers reuse of the factory to ensure the AsyncLocal type name is cleared between invocations.
To also cover the new AsyncLocal behavior and the underlying concurrency bug, please add a test that calls
CreateResourceManagerStringLocalizertwice: once after aGetResourcePrefixcall (so_typeNameis set) and then again without setting_typeName(or after clearing it). The second call should rely onbaseNameand must not reuse the previous type name, confirming that_typeName.Valueis correctly reset and protecting against regressions in the cleanup logic.Suggested implementation:
This test assumes:
GetResourcePrefixthat accepts a singleType(or compatible) parameter. If the actual signature differs (e.g.,TypeInfo), adjust theInvokecall accordingly, for example:new object[] { fooType.GetTypeInfo() }for aTypeInfoparameter.using System.Reflection;andusing Microsoft.Extensions.Localization;are already present at the top of the file.If the method name or binding flags differ in your implementation, update the reflection calls to match the actual factory API.