-
Notifications
You must be signed in to change notification settings - Fork 844
Use Utf8JsonReader in DotNetDispatcher #2061
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
|
/cc @ahsonkhan |
|
Numbers? |
| var jsonDocument = JsonDocument.Parse(argsJson); | ||
| var shouldDisposeJsonDocument = true; | ||
| try | ||
| var utf8JsonBytes = Encoding.UTF8.GetBytes(argsJson).AsSpan(); |
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.
You should be able to avoid this allocation by stackalloc/renting and using
https://github.com/dotnet/corefx/blob/7431e4938c461f6e24cf3122c0b9ca4eb27e4dde/src/Common/src/CoreLib/System/Text/Unicode/Utf8.cs#L42
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.
This project targets netstandard2.0. I don't that API exists?
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.
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.
Is cross-compiling/multi-targeting feasible? If perf/allocation is super critical, maybe use the char* overload for Encoding.UTF8 on older platforms and the span one on newer ones.
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.
@pranavkm We can stackalloc on netstandard2.0, it just has to be done inside a method marked as unsafe. I think we'd be OK with the "unsafe" for now (this will move to netstandard2.1 later). However I'm not sure if additional checks are needed to avoid problems if we receive an especially long string. Brennan's other suggestion may be better!
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.
RuntimeHelpers.EnsureSufficientExecutionStack() or one of its friends will do I think
| while (reader.Read() && reader.TokenType != JsonTokenType.EndArray) | ||
| { | ||
| suppliedArgsLength++; | ||
| reader.Skip(); |
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.
nit: I think this can be written a bit differently to avoid the double parsing of the json data.
Currently we are doing one pass to validate, and then the other to do the actual work.
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.
I agree, this does seem a bit odd
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.
Also maybe I'm misunderstanding the APIs, but does reader.Skip skip over complex objects or does it just skip over individual tokens? How many tokens does it think are in the input ["a", ["b1", "b2"]], or is that sort of thing not possible here?
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.
reader.Skip skips over complex objects (not individual tokens).
So, if your reader was positioned at StartArray (or Object), it would skip to EndArray (or Object). If your reader was positioned at String, or other primitive token (like Null, True, False, Number), it wouldn't do anything.
| } | ||
| // Reset the reader | ||
| reader = new Utf8JsonReader(utf8JsonBytes); | ||
| reader.Read(); |
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.
nit: Add Debug.Assert(reader.TokenType == JsonTokenType.StartArray)
| objectRefReader.TokenType == JsonTokenType.PropertyName && | ||
| objectRefReader.ValueTextEquals(DotNetObjectRefKey.EncodedUtf8Bytes)) | ||
| { | ||
| // The JSON payload looks has the expected shape. |
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.
nit: This comment is a bit confusing to read.
| static bool IsIncorrectDotNetObjectRefUse(JsonElement item, Type parameterType) | ||
| return suppliedArgs; | ||
|
|
||
| static bool IsIncorrectDotNetObjectRefUse(Type parameterType, Span<byte> utf8JsonBytes, JsonReaderState readerState) |
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.
Why not pass the reader directly rather than passing in the data/state? If you don't want the mutations to the reader be retained, pass by value (rather than ref).
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.
Also it might be nice to make this not be a local method to make it super clear that we're not expecting to capture any locals in a closure, since that would be a bad perf situation. Oh, never mind, it's static anyway.
javiercn
left a comment
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.
Overall looks good!
Now that we are doing more lower level parsing I would like to see more tests with bad input to ensure that we fail in a reasonable way. Could you add a few tests like those?
| var jsonDocument = JsonDocument.Parse(argsJson); | ||
| var shouldDisposeJsonDocument = true; | ||
| try | ||
| var utf8JsonBytes = Encoding.UTF8.GetBytes(argsJson).AsSpan(); |
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.
RuntimeHelpers.EnsureSufficientExecutionStack() or one of its friends will do I think
| /// </exception> | ||
| public static void EndInvoke(string arguments) | ||
| { | ||
| var parsedArgs = ParseArguments( |
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.
I think the logic here could definitely use some extra comments now. Something to depict what shape of data we're expecting to receive here, like:
// arguments must be a valid JSON string of the form:
// [taskId, success, result]
// where:
// - 'taskId' is a number (parsed as long)
// - 'success' is a boolean
// - 'result' must be JSON-deserializable as whatever .NET type T was originally specified on InvokeAsync<T>
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.
This looks like it will be excellent.
The CR comments from others are pretty high-quality and worth addressing, I'd say.
1178415 to
0cf3c01
Compare
10bbd81 to
545f155
Compare
545f155 to
50ebe23
Compare
|
Some numbers:
The |
50ebe23 to
3444904
Compare
BrennanConroy
left a comment
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.
Looks good, make sure some Blazor folks have looked at it
| else | ||
| { | ||
| assembly = loadedAssemblies.FirstOrDefault(a => new AssemblyKey(a).Equals(assemblyKey)); | ||
|
|
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.
| static bool IsIncorrectDotNetObjectRefUse(JsonElement item, Type parameterType) | ||
| return suppliedArgs; | ||
|
|
||
| // Note that the JsonReader instance is intentionally not passed by ref (or asn in parameter) since we want a copy of the original reader. |
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.
| // Note that the JsonReader instance is intentionally not passed by ref (or asn in parameter) since we want a copy of the original reader. | |
| // Note that the JsonReader instance is intentionally not passed by ref (or an in parameter) since we want a copy of the original reader. |
| } | ||
|
|
||
| [Fact] | ||
| public void ParseArguments_UsesStackForSmallJsonPayloads() |
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.
I don't understand this one
| } | ||
|
|
||
| [Fact] | ||
| public void ParseEndInvokeArguments_UsesStackForSmallPayloads() |
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.
Same question, what does this mean?
Is it a remnant from wanting to test the stackalloc stuff?
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.
Yup. Removed in the update
| return result; | ||
| } | ||
|
|
||
| internal static bool WorkAroundTestBug; |
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.
I don't understand this. Am I missing something, or is its value always true? If so, why do we even have this flag and the if/else case below? Why not just always do L378?
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.
Oh wait, I see how it only gets set to true in tests.
Still, is there any disadvantage in always using the logic on L378?
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.
Not particularly. I didn't think it was necessary to change product code, but in the most ordinary cases, you're unlikely to see more than two instances of the same assembly so this doesn't change much.
|
@SteveSandersonMS \ @javiercn would you like to have another look at this before I merge it in? |
43d6c8c to
278f0e4
Compare
SteveSandersonMS
left a comment
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.
Thanks for the updates!
* Use Utf8JsonReader in DotNetDispatcher Fixes #10988 \n\nCommit migrated from dotnet/extensions@c24711c
* Use Utf8JsonReader in DotNetDispatcher Fixes #10988 \n\nCommit migrated from dotnet/extensions@c24711c





No description provided.