Skip to content

Fix JsonSerializerOptions.GetConverter recreating built-in converters #65799

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

Conversation

eiriktsarpalis
Copy link
Member

Fix #65770.

@layomia do you think is this the right approach? I changed GetConverter so that it now calls InitializeForReflectionSerializer instead of RootBuiltInConverter since it already has a flag guarding access. The only difference is that additionally roots the reflection-based JsonTypeInfo factory delegate. I suspect this is fine since GetConverter is already marked RequiresUnreferencedCode and might actually fix a broken corner case (see changes in test code).

@ericstj Do you think this meets the bar for 6.0 servicing? I can imagine certain use cases where this could incur a substantial amount of added allocations (and it's a regression from 5.0)

@ghost
Copy link

ghost commented Feb 23, 2022

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

Issue Details

Fix #65770.

@layomia do you think is this the right approach? I changed GetConverter so that it now calls InitializeForReflectionSerializer instead of RootBuiltInConverter since it already has a flag guarding access. The only difference is that additionally roots the reflection-based JsonTypeInfo factory delegate. I suspect this is fine since GetConverter is already marked RequiresUnreferencedCode and might actually fix a broken corner case (see changes in test code).

@ericstj Do you think this meets the bar for 6.0 servicing? I can imagine certain use cases where this could incur a substantial amount of added allocations (and it's a regression from 5.0)

Author: eiriktsarpalis
Assignees: -
Labels:

area-System.Text.Json

Milestone: -

@eiriktsarpalis eiriktsarpalis added this to the 7.0.0 milestone Feb 23, 2022
@@ -99,7 +99,6 @@ private void EnsurePushCapacity()
public JsonConverter Initialize(Type type, JsonSerializerOptions options, bool supportContinuation)
{
JsonTypeInfo jsonTypeInfo = options.GetOrAddJsonTypeInfoForRootType(type);
Debug.Assert(options == jsonTypeInfo.Options);
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 assertion has been invalidated by #64646, however it wasn't being caught by our test suite until now.

Copy link
Contributor

Choose a reason for hiding this comment

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

I assume it would still be true if the new comparer introduced in #64646 were used.

Copy link
Member Author

Choose a reason for hiding this comment

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

== still checks for reference equality, the comparer is internal implementation detail when initializing the caches.

@@ -99,7 +99,6 @@ private void EnsurePushCapacity()
public JsonConverter Initialize(Type type, JsonSerializerOptions options, bool supportContinuation)
{
JsonTypeInfo jsonTypeInfo = options.GetOrAddJsonTypeInfoForRootType(type);
Debug.Assert(options == jsonTypeInfo.Options);
Copy link
Contributor

Choose a reason for hiding this comment

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

I assume it would still be true if the new comparer introduced in #64646 were used.

Comment on lines +162 to +165
if (!IsInitializedForReflectionSerializer)
{
InitializeForReflectionSerializer();
}
Copy link
Contributor

Choose a reason for hiding this comment

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

I don't think calling this method should root the reflection-based metadata gathering code paths, potentially masking future bugs with source-gen.

Copy link
Member Author

Choose a reason for hiding this comment

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

Thanks. What is the distinction between rooting the reflection-based metadata gathering paths and rooting the built-in converter factories that depend on said reflection-based metadata gathering paths? Under the current setup an options instance not configured for reflection will eagerly return converters that do require reflection.

Copy link
Member Author

Choose a reason for hiding this comment

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

Based on your remarks on this PR #51897 I'm guessing that this is only meant as an entrypoint for reflection-based serialization anyways.

Copy link
Contributor

Choose a reason for hiding this comment

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

Under the current setup an options instance not configured for reflection will eagerly return converters that do require reflection.

Hmm what do you mean? The source generator doesn't use this method GetConverter(Type), so we left the reflection to avoid breaking folks that used it pre .NET 6.0.

RunTest<Point_2D>();

void RunTest<TConverterReturn>()
RemoteExecutor.Invoke(static () =>
Copy link
Member

Choose a reason for hiding this comment

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

this feels a bit like a unit test rather than functional test because it tests an implementation detail. I'll suggest to move it to unit tests.

@eiriktsarpalis eiriktsarpalis deleted the getconverter-builtinconverter-reallocation branch February 24, 2022 20:39
@ghost ghost locked as resolved and limited conversation to collaborators Mar 31, 2022
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Projects
None yet
3 participants