Skip to content

Conversation

@pranavkm
Copy link

No description provided.

@pranavkm
Copy link
Author

/cc @ahsonkhan

@BrennanConroy
Copy link
Member

Numbers?

var jsonDocument = JsonDocument.Parse(argsJson);
var shouldDisposeJsonDocument = true;
try
var utf8JsonBytes = Encoding.UTF8.GetBytes(argsJson).AsSpan();
Copy link
Member

Choose a reason for hiding this comment

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

Copy link
Author

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?

Copy link
Member

@BrennanConroy BrennanConroy Jul 19, 2019

Choose a reason for hiding this comment

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

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.

Copy link
Member

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!

Copy link
Member

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();

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.

Copy link
Member

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

Copy link
Member

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?

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();

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.

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)

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).

Copy link
Member

@SteveSandersonMS SteveSandersonMS Jul 20, 2019

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.

Copy link
Member

@javiercn javiercn left a 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();
Copy link
Member

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(
Copy link
Member

@SteveSandersonMS SteveSandersonMS Jul 20, 2019

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>

@SteveSandersonMS SteveSandersonMS self-requested a review July 20, 2019 17:16
Copy link
Member

@SteveSandersonMS SteveSandersonMS left a 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.

@pranavkm pranavkm force-pushed the prkrishn/json-converter branch from 1178415 to 0cf3c01 Compare July 24, 2019 02:39
@pranavkm pranavkm force-pushed the prkrishn/json-reader branch 2 times, most recently from 10bbd81 to 545f155 Compare July 31, 2019 23:53
@pranavkm pranavkm changed the base branch from prkrishn/json-converter to release/3.0 July 31, 2019 23:53
@pranavkm pranavkm force-pushed the prkrishn/json-reader branch from 545f155 to 50ebe23 Compare July 31, 2019 23:56
@pranavkm
Copy link
Author

pranavkm commented Aug 1, 2019

Some numbers:

Before After
string image image
byte[] image image
object[] image N/A

The byte[] allocation is a wash at this point and using stackalloc saves about 100 bytes \ JS operation (100 kb over 2k operations). It doesn't seem particularly worthwhile considering considering you have to cross-compile. Particularly given that the plan is to transmit the args as a utf8 byte array (dotnet/aspnetcore#12280), the added complexity doesn't seem worthwhile at this point.

@pranavkm pranavkm force-pushed the prkrishn/json-reader branch from 50ebe23 to 3444904 Compare August 1, 2019 00:18
@pranavkm pranavkm added this to the 3.0.0-preview9 milestone Aug 1, 2019
Copy link
Member

@BrennanConroy BrennanConroy left a 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));

Copy link
Member

Choose a reason for hiding this comment

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

Suggested change

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.
Copy link
Member

Choose a reason for hiding this comment

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

Suggested change
// 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()
Copy link
Member

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()
Copy link
Member

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?

Copy link
Author

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;
Copy link
Member

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?

Copy link
Member

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?

Copy link
Author

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.

@pranavkm
Copy link
Author

pranavkm commented Aug 6, 2019

@SteveSandersonMS \ @javiercn would you like to have another look at this before I merge it in?

@pranavkm pranavkm force-pushed the prkrishn/json-reader branch from 43d6c8c to 278f0e4 Compare August 6, 2019 22:10
Copy link
Member

@SteveSandersonMS SteveSandersonMS left a 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!

@pranavkm pranavkm merged commit c24711c into release/3.0 Aug 7, 2019
@ghost ghost deleted the prkrishn/json-reader branch August 7, 2019 16:44
JunTaoLuo pushed a commit to dotnet/aspnetcore that referenced this pull request Feb 12, 2020
* Use Utf8JsonReader in DotNetDispatcher

Fixes #10988
\n\nCommit migrated from dotnet/extensions@c24711c
JunTaoLuo pushed a commit to dotnet/aspnetcore that referenced this pull request Feb 15, 2020
* Use Utf8JsonReader in DotNetDispatcher

Fixes #10988
\n\nCommit migrated from dotnet/extensions@c24711c
@ghost ghost locked as resolved and limited conversation to collaborators May 27, 2023
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

6 participants