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

String and byte[] converters using partial reads/writes #112129

Draft
wants to merge 2 commits into
base: main
Choose a base branch
from

Conversation

PranavSenthilnathan
Copy link
Member

No description provided.

Copy link
Contributor

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

/// This is used when the Stream-based serialization APIs are used.
/// </summary>
/// <typeparam name="T"></typeparam>
internal abstract class JsonHybridResumableConverter<T> : JsonConverter<T>
Copy link
Member

Choose a reason for hiding this comment

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

How is this different compared to the existing resumable converter base?

Copy link
Member Author

Choose a reason for hiding this comment

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

It exposes WriteWithoutStackFrame which doesn't push/pop stack frames by default but based on the value (if it's long or serialization is done via streaming) there's a method to push/pop the stack frames. This keeps the common case of short strings/byte[] fast by not needing a stack frame but still allows large strings to opt into stack management.

@@ -46,6 +46,8 @@ public ref partial struct Utf8JsonReader
private SequencePosition _currentPosition;
private readonly ReadOnlySequence<byte> _sequence;

internal bool _hasPartialStringValue;
Copy link
Member

Choose a reason for hiding this comment

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

Like with Utf8JsonWriter, couldn't this just use the same special JsonTokenType value to reflect segmented reads?

@@ -1300,6 +1302,7 @@ private bool ConsumeString()
ValueIsEscaped = false;
_tokenType = JsonTokenType.String;
_consumed += idx + 2;
_hasPartialStringValue = false;
Copy link
Member

Choose a reason for hiding this comment

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

This flags is introducing a new state for Utf8JsonReader that could break user-defined converters not expecting it. We should make absolutely sure instances setting this flag should never leak via public APIs. I would recommend adding a few debug asserts e.g. in the Read() method checking that the flag is always unset.

@@ -29,6 +33,176 @@ public override void Write(Utf8JsonWriter writer, byte[]? value, JsonSerializerO
}
}

internal override bool OnTryRead(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options, scoped ref ReadStack state, out byte[]? value)
{
if (state.Current.ObjectState < StackFrameObjectState.CreatedObject)
Copy link
Member

Choose a reason for hiding this comment

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

Like with existing resumable converters, there should be a fast path that runs when SupportContinuation is set to false. In this case we can assume that the string has been fully populated and therefore we can read it using the non-segmenting APIs.

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 is handled in JsonConverter.TryRead where we check ConverterStrategy == ConverterStrategy.SegmentableValue && !state.IsContinuation && !reader._hasPartialStringValue which guarantees that only partially populated buffers will go through this path since fully populated buffers would have reader._hasPartialStringValue == false. It's too late to do the check here since the stack already is pushed at this point so it needs to be in JsonConverter.TryRead or earlier.

Copy link
Member

Choose a reason for hiding this comment

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

JsonConverter.TryRead is a hot path method and historically changing it has been causing a great deal of performance regressions. I would recommend moving as much of the logic as possible inside the resumable converters themselves. I would also recommend keeping as much consistency as possible with the existing resumable converters, since their code is extremely nontrivial and difficult to follow.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants