-
Notifications
You must be signed in to change notification settings - Fork 4.8k
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
Rewrite System.Text.Json stream tests to be async friendly and enable on WASM #38663
Conversation
We can skip fewer now, right? :) |
Yeah I'm working on it :) |
src/libraries/System.Text.Json/tests/Utf8JsonReaderTests.MultiSegment.cs
Show resolved
Hide resolved
… on WASM The tests dealing are using a (De)SerializationWrapper so the same code can be used both for String and Stream types. It does that by wrapping the async Stream serialization calls in `Task.Run().GetAwaiter().GetResult()` to turn them into sync calls. However that doesn't work on WebAssembly since we can't wait on tasks as there's only a single thread. To fix this inverse the wrapper so the synchronous String calls are turned into async and use normal awaits for the Stream calls. This allows the test suite to pass on WebAssembly: `Tests run: 8349, Errors: 0, Failures: 0, Skipped: 11. Time: 475.528706s`
a560a84
to
99192c0
Compare
@stephentoub this is ready for review now :) |
@steveharter @layomia PTAL |
@@ -3650,7 +3647,7 @@ private static string GetCompactJson(TestCaseType testCaseType, string jsonStrin | |||
return s_compactJson[testCaseType] = existing; | |||
} | |||
|
|||
[Fact] | |||
[ConditionalFact(typeof(PlatformDetection), nameof(PlatformDetection.IsThreadingSupported))] |
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.
It wouldn't be testing the exact same thing, but it seems like this test would "just work" if you changed the Task.WaitAll(tasks)
to instead be await Task.WhenAll(tasks)
and made the method async, no?
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.
Yeah that does indeed work. I can send a followup PR but I'd like to first get this one in while CI is green :)
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.
Reenabled these tests with your suggestion in the latest iteration.
src/libraries/System.Text.Json/tests/Serialization/ConstructorTests/ConstructorTests.Cache.cs
Outdated
Show resolved
Hide resolved
.../System.Text.Json/tests/Serialization/ConstructorTests/ConstructorTests.AttributePresence.cs
Outdated
Show resolved
Hide resolved
src/libraries/System.Text.Json/tests/Serialization/ConstructorTests/ConstructorTests.Cache.cs
Outdated
Show resolved
Hide resolved
src/libraries/System.Text.Json/tests/Serialization/ConstructorTests/ConstructorTests.Cache.cs
Outdated
Show resolved
Hide resolved
src/libraries/System.Text.Json/tests/Serialization/ConstructorTests/ConstructorTests.Cache.cs
Outdated
Show resolved
Hide resolved
.../System.Text.Json/tests/Serialization/ConstructorTests/ConstructorTests.ParameterMatching.cs
Outdated
Show resolved
Hide resolved
tasks[12] = Task.Run(async () => await RunTest<Point_With_Dictionary>(Point_With_Dictionary.s_data)); | ||
tasks[13] = Task.Run(async () => await RunTest<Point_With_Object>(Point_With_Object.s_data)); | ||
|
||
Task.WaitAll(tasks); |
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.
Can you also apply the pattern suggested in https://github.com/dotnet/runtime/pull/38663/files#r452481795 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.
Isn't the intent of this test to detect race conditions? By awaiting one-by-one doesn't defy said purpose?
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.
It didn't look to me like this test is about race conditions, but I changed it to the Task.WhenAll()
pattern nonetheless.
src/libraries/System.Text.Json/tests/Serialization/ConstructorTests/ConstructorTests.Stream.cs
Show resolved
Hide resolved
src/libraries/System.Text.Json/tests/Serialization/ConstructorTests/ConstructorTests.Stream.cs
Show resolved
Hide resolved
@@ -1609,7 +1610,7 @@ public static void TestPartialJsonReaderSlicesSpecialNumbers(TestCaseType type, | |||
} | |||
} | |||
|
|||
[Theory] | |||
[ConditionalTheory] |
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.
What's the point of a ConditionalTheory
without parameters?
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 is necessary so throwing SkipTestException works, otherwise the test would fail with an "unexpected" exception.
src/libraries/System.Text.Json/tests/Serialization/ConstructorTests/ConstructorTests.Cache.cs
Outdated
Show resolved
Hide resolved
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.
Other than my comments, LGTM.
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.
@jozkee @stephentoub I addressed your feedback, please take another look :)
@@ -3650,7 +3647,7 @@ private static string GetCompactJson(TestCaseType testCaseType, string jsonStrin | |||
return s_compactJson[testCaseType] = existing; | |||
} | |||
|
|||
[Fact] | |||
[ConditionalFact(typeof(PlatformDetection), nameof(PlatformDetection.IsThreadingSupported))] |
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.
Reenabled these tests with your suggestion in the latest iteration.
src/libraries/System.Text.Json/tests/Serialization/ConstructorTests/ConstructorTests.Cache.cs
Outdated
Show resolved
Hide resolved
tasks[12] = Task.Run(async () => await RunTest<Point_With_Dictionary>(Point_With_Dictionary.s_data)); | ||
tasks[13] = Task.Run(async () => await RunTest<Point_With_Object>(Point_With_Object.s_data)); | ||
|
||
Task.WaitAll(tasks); |
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.
It didn't look to me like this test is about race conditions, but I changed it to the Task.WhenAll()
pattern nonetheless.
@@ -1609,7 +1610,7 @@ public static void TestPartialJsonReaderSlicesSpecialNumbers(TestCaseType type, | |||
} | |||
} | |||
|
|||
[Theory] | |||
[ConditionalTheory] |
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 is necessary so throwing SkipTestException works, otherwise the test would fail with an "unexpected" exception.
…json # Conflicts: # src/libraries/tests.proj
tasks[0] = Task.Run(async () => await RunTestAsync<Class_With_Ctor_With_65_Params>()); | ||
tasks[1] = Task.Run(async () => await RunTestAsync<Struct_With_Ctor_With_65_Params>()); | ||
|
||
await Task.WhenAll(tasks); |
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 made these run in parallel just for fun? :)
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.
These were actually already running in parallel before this PR.
src/libraries/System.Text.Json/tests/Serialization/ConstructorTests/ConstructorTests.Stream.cs
Show resolved
Hide resolved
src/libraries/System.Text.Json/tests/Serialization/ConstructorTests/ConstructorTests.Stream.cs
Show resolved
Hide resolved
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.
Other than not being clear on why you added concurrency into some of the tests, LGTM.
@@ -121,7 +121,7 @@ void Test(int i) | |||
tasks[i + 1] = Task.Run(() => Test(TestClassCount - 2)); | |||
} | |||
|
|||
Task.WaitAll(tasks); | |||
await Task.WhenAll(tasks); |
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.
FYI this area of code really does care about threading, and the changes shouldn't affect the multi-threading aspects.
{ | ||
var obj = Serializer.Deserialize(json, type, options); | ||
var obj = await Serializer.DeserializeAsync(json, type, options); |
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 was this changed to use the Async version of the API? The original intent was to test non-async APIs for thread safety.
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.
The original test was calling a synchronous method which was then overridden by two different implementations, one that used the underlying sync API and one that used the underlying async API and then blocked waiting on it. This change inverts that, so it's an async API that then uses the underlying sync API and wraps it in a task or the underlying async API. It's done to avoid sync-over-async, which blows up on wasm.
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.
Correct. I've tried to capture this in the PR description but please let me know if I could make this clearer.
{ | ||
if (options == null) | ||
{ | ||
options = _optionsWithSmallBuffer; | ||
} | ||
|
||
return Task.Run(async () => | ||
using (MemoryStream stream = new MemoryStream(Encoding.UTF8.GetBytes(json))) |
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.
So this is the key area of code where we switch the wrapper to use await...
await JsonSerializer.SerializeAsync(stream, value, inputType, options); | ||
return Encoding.UTF8.GetString(stream.ToArray()); | ||
}).GetAwaiter().GetResult(); | ||
using var stream = new MemoryStream(); |
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.
Again, this is the key area of code where we switch the wrapper to use await without Task.Run.
One global comment is that the renaming of |
@steveharter I switched to using |
The tests are using a (De)SerializationWrapper so the same code can be used both for String and Stream types.
It does that by wrapping the async Stream serialization calls in
Task.Run().GetAwaiter().GetResult()
to turn them into sync calls.However that doesn't work on WebAssembly since we can't wait on tasks as there's only a single thread.
To fix this inverse the wrapper so the synchronous String calls are turned into async and use normal awaits for the Stream calls.
This allows the test suite to pass on WebAssembly:
Tests run: 8349, Errors: 0, Failures: 0, Skipped: 11. Time: 475.528706s