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

Assign PropertyCache after it's configured for source gen in attempt to fix JSON LookupProperty assert failing #68480

Conversation

krwq
Copy link
Member

@krwq krwq commented Apr 25, 2022

Contributes to: #67816

Fixing one potential threading issue related to LookupProperty assert failing. This could possibly happen for source gen when two threads start using same JsonTypeInfo at the same time since. For source gen properties are late assigned meaning if one thread has already assigned and configured them the other might reset that state and first thread might start using it before the second thread finishes configuring. This PR changes so that assignment happens after cache is already configured so that we don't start using semi configured cache in neither thread.

I'm not 100% certain this will fix the issue (I don't have a local repro) but it's likely. Once this is merged I'll observe if this repros again for another couple of days and if not we can assume it's fixed.

@ghost
Copy link

ghost commented Apr 25, 2022

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

Issue Details

Contributes to: #67816

Fixing one potential threading issue related to LookupProperty assert failing. This could possibly happen for source gen when two threads start using same JsonTypeInfo at the same time since. For source gen properties are late assigned meaning if one thread has already assigned and configured them the other might reset that state and first thread might start using it before the second thread finishes configuring. This PR changes so that assignment happens after cache is already configured so that we don't start using semi configured cache in neither thread.

I'm not 100% certain this will fix the issue but it's likely. Once this is merged I'll observe if this repros again for another couple of days and if not we can assume it's fixed.

Author: krwq
Assignees: krwq
Labels:

area-System.Text.Json

Milestone: -

{
if (PropertyInfoForTypeInfo.ConverterStrategy != ConverterStrategy.Object)
{
return;
return null;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can we also exit early for typeof(T)?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

likely yes but I'd prefer to keep those changes separate

}

if (PropertyInfoForTypeInfo.ConverterBase.ElementType != null)
{
// Nullable<> or F# optional converter's strategy is set to element's strategy
return;
return null;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Are there some Debug.Asserts that would be relevant to verify that this is a Nullable or F# optional?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This specific check is happening because Nulalble<T> converter currently reports Object as its converter strategy but it actually never uses properties - IMO this is incorrect but I didn't want to do the needed refactoring to change - I was preserving existing behavior here. I'll refrain from doing this change in this PR

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This specific check is happening because Nulalble converter currently reports Object as its converter strategy but it actually never uses properties

It only will report Object if T is a struct. In any case it would make sense to an assertion here in case the ElementType assumption gets invalidated by a different converter in the future.

{
foreach (var jsonPropertyInfoKv in PropertyCache.List)
foreach (var jsonPropertyInfoKv in propertyCache.List)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: should the declaring type's NumberHandling be passed to EnsureConfigured() as an argument?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

more likely this (declaring type JsonTypeInfo) perhaps because we don't really know if other info might be needed here. I'll refrain from changing it in this PR

{
JsonPropertyInfo jsonPropertyInfo = jsonPropertyInfoKv.Value!;
jsonPropertyInfo.DeclaringTypeNumberHandling = NumberHandling;
jsonPropertyInfo.EnsureConfigured();
}

// This code can be run in multiple threads therefore we assign only after all properties has been configured
Copy link
Member

@eiriktsarpalis eiriktsarpalis Apr 27, 2022

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think this change makes the code more complicated without addressing the core issue, which is described in the comment here. It doesn't fix all potential race conditions hiding in the initialization code, nor does it prevent any future races from being introduced as the code gets changed.

The only real solution is to prevent multiple threads from attempting to write to an instance. There are a few approaches we could pursue longer term, but the immediate need is to unblock CI for others. As an interim solution I would recommend adding a double-checked lock in the EnsureConfigured() method.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I was hoping we could achieve same without having to do locks but agreed it will make it harder to prevent from the regression to happen again. Will revert and lock

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

actually let me close this PR and create a new one since it's effectively different change

@krwq krwq closed this Apr 27, 2022
@ghost ghost locked as resolved and limited conversation to collaborators May 27, 2022
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants