Skip to content

Commit 2bf30a0

Browse files
Ensure Sync stream serialization is handling IAsyncEnumerable correctly (#67035)
* Fix #66687 * enable small buffer async tests for collections
1 parent 3c74532 commit 2bf30a0

File tree

10 files changed

+42
-24
lines changed

10 files changed

+42
-24
lines changed

src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Converters/Collection/IAsyncEnumerableOfTConverter.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,7 @@ protected override void CreateCollection(ref Utf8JsonReader reader, ref ReadStac
3434

3535
internal override bool OnTryWrite(Utf8JsonWriter writer, TAsyncEnumerable value, JsonSerializerOptions options, ref WriteStack state)
3636
{
37-
if (!state.SupportContinuation)
37+
if (!state.SupportAsync)
3838
{
3939
ThrowHelper.ThrowNotSupportedException_TypeRequiresAsyncSerialization(TypeToConvert);
4040
}

src/libraries/System.Text.Json/src/System/Text/Json/Serialization/JsonResumableConverterOfT.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,7 @@ public sealed override void Write(Utf8JsonWriter writer, T value, JsonSerializer
2525
// Bridge from resumable to value converters.
2626

2727
WriteStack state = default;
28-
state.Initialize(typeof(T), options, supportContinuation: false);
28+
state.Initialize(typeof(T), options, supportContinuation: false, supportAsync: false);
2929
try
3030
{
3131
TryWrite(writer, value, options, ref state);

src/libraries/System.Text.Json/src/System/Text/Json/Serialization/JsonSerializer.Write.Helpers.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -64,7 +64,7 @@ jsonTypeInfo is not JsonTypeInfo<TValue> ||
6464
"Incorrect method called. WriteUsingGeneratedSerializer() should have been called instead.");
6565

6666
WriteStack state = default;
67-
state.Initialize(jsonTypeInfo, supportContinuation: false);
67+
state.Initialize(jsonTypeInfo, supportContinuation: false, supportAsync: false);
6868

6969
JsonConverter converter = jsonTypeInfo.PropertyInfoForTypeInfo.ConverterBase;
7070
Debug.Assert(converter != null);

src/libraries/System.Text.Json/src/System/Text/Json/Serialization/JsonSerializer.Write.Stream.cs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -256,7 +256,7 @@ private static async Task WriteStreamAsync<TValue>(
256256
using (var writer = new Utf8JsonWriter(bufferWriter, writerOptions))
257257
{
258258
WriteStack state = new WriteStack { CancellationToken = cancellationToken };
259-
JsonConverter converter = state.Initialize(jsonTypeInfo, supportContinuation: true);
259+
JsonConverter converter = state.Initialize(jsonTypeInfo, supportContinuation: true, supportAsync: true);
260260

261261
bool isFinalBlock;
262262

@@ -329,7 +329,7 @@ private static void WriteStream<TValue>(
329329
using (var writer = new Utf8JsonWriter(bufferWriter, writerOptions))
330330
{
331331
WriteStack state = default;
332-
JsonConverter converter = state.Initialize(jsonTypeInfo, supportContinuation: true);
332+
JsonConverter converter = state.Initialize(jsonTypeInfo, supportContinuation: true, supportAsync: false);
333333

334334
bool isFinalBlock;
335335

src/libraries/System.Text.Json/src/System/Text/Json/Serialization/WriteStack.cs

Lines changed: 11 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -78,6 +78,11 @@ internal struct WriteStack
7878
/// </summary>
7979
public bool SupportContinuation;
8080

81+
/// <summary>
82+
/// Internal flag indicating that async serialization is supported. Implies `SupportContinuation`.
83+
/// </summary>
84+
public bool SupportAsync;
85+
8186
/// <summary>
8287
/// Stores a reference id that has been calculated for a newly serialized object.
8388
/// </summary>
@@ -98,14 +103,16 @@ private void EnsurePushCapacity()
98103
/// <summary>
99104
/// Initialize the state without delayed initialization of the JsonTypeInfo.
100105
/// </summary>
101-
public JsonConverter Initialize(Type type, JsonSerializerOptions options, bool supportContinuation)
106+
public JsonConverter Initialize(Type type, JsonSerializerOptions options, bool supportContinuation, bool supportAsync)
102107
{
103108
JsonTypeInfo jsonTypeInfo = options.GetOrAddJsonTypeInfoForRootType(type);
104-
return Initialize(jsonTypeInfo, supportContinuation);
109+
return Initialize(jsonTypeInfo, supportContinuation, supportAsync);
105110
}
106111

107-
internal JsonConverter Initialize(JsonTypeInfo jsonTypeInfo, bool supportContinuation)
112+
internal JsonConverter Initialize(JsonTypeInfo jsonTypeInfo, bool supportContinuation, bool supportAsync)
108113
{
114+
Debug.Assert(!supportAsync || supportContinuation, "supportAsync implies supportContinuation.");
115+
109116
Current.JsonTypeInfo = jsonTypeInfo;
110117
Current.JsonPropertyInfo = jsonTypeInfo.PropertyInfoForTypeInfo;
111118
Current.NumberHandling = Current.JsonPropertyInfo.NumberHandling;
@@ -118,6 +125,7 @@ internal JsonConverter Initialize(JsonTypeInfo jsonTypeInfo, bool supportContinu
118125
}
119126

120127
SupportContinuation = supportContinuation;
128+
SupportAsync = supportAsync;
121129

122130
return jsonTypeInfo.PropertyInfoForTypeInfo.ConverterBase;
123131
}

src/libraries/System.Text.Json/tests/Common/CollectionTests/CollectionTests.AsyncEnumerable.cs

Lines changed: 15 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@ public abstract partial class CollectionTests
1818
[MemberData(nameof(GetAsyncEnumerableSources))]
1919
public async Task WriteRootLevelAsyncEnumerable<TElement>(IEnumerable<TElement> source, int delayInterval, int bufferSize)
2020
{
21-
if (StreamingSerializer is null)
21+
if (StreamingSerializer?.IsAsyncSerializer != true)
2222
{
2323
return;
2424
}
@@ -43,7 +43,7 @@ public async Task WriteRootLevelAsyncEnumerable<TElement>(IEnumerable<TElement>
4343
[MemberData(nameof(GetAsyncEnumerableSources))]
4444
public async Task WriteNestedAsyncEnumerable<TElement>(IEnumerable<TElement> source, int delayInterval, int bufferSize)
4545
{
46-
if (StreamingSerializer is null)
46+
if (StreamingSerializer?.IsAsyncSerializer != true)
4747
{
4848
return;
4949
}
@@ -68,7 +68,7 @@ public async Task WriteNestedAsyncEnumerable<TElement>(IEnumerable<TElement> sou
6868
[MemberData(nameof(GetAsyncEnumerableSources))]
6969
public async Task WriteNestedAsyncEnumerable_DTO<TElement>(IEnumerable<TElement> source, int delayInterval, int bufferSize)
7070
{
71-
if (StreamingSerializer is null)
71+
if (StreamingSerializer?.IsAsyncSerializer != true)
7272
{
7373
return;
7474
}
@@ -93,7 +93,7 @@ public async Task WriteNestedAsyncEnumerable_DTO<TElement>(IEnumerable<TElement>
9393
[MemberData(nameof(GetAsyncEnumerableSources))]
9494
public async Task WriteNestedAsyncEnumerable_Nullable<TElement>(IEnumerable<TElement> source, int delayInterval, int bufferSize)
9595
{
96-
if (StreamingSerializer is null)
96+
if (StreamingSerializer?.IsAsyncSerializer != true)
9797
{
9898
return;
9999
}
@@ -151,7 +151,7 @@ public class AsyncEnumerableDto<TElement>
151151
[MemberData(nameof(GetAsyncEnumerableSources))]
152152
public async Task WriteSequentialNestedAsyncEnumerables<TElement>(IEnumerable<TElement> source, int delayInterval, int bufferSize)
153153
{
154-
if (StreamingSerializer is null)
154+
if (StreamingSerializer?.IsAsyncSerializer != true)
155155
{
156156
return;
157157
}
@@ -176,7 +176,7 @@ public async Task WriteSequentialNestedAsyncEnumerables<TElement>(IEnumerable<TE
176176
[MemberData(nameof(GetAsyncEnumerableSources))]
177177
public async Task WriteAsyncEnumerableOfAsyncEnumerables<TElement>(IEnumerable<TElement> source, int delayInterval, int bufferSize)
178178
{
179-
if (StreamingSerializer is null)
179+
if (StreamingSerializer?.IsAsyncSerializer != true)
180180
{
181181
return;
182182
}
@@ -209,19 +209,21 @@ public void WriteRootLevelAsyncEnumerableSync_ThrowsNotSupportedException()
209209
{
210210
IAsyncEnumerable<int> asyncEnumerable = new MockedAsyncEnumerable<int>(Enumerable.Range(1, 10));
211211
Assert.Throws<NotSupportedException>(() => JsonSerializer.Serialize(asyncEnumerable));
212+
Assert.Throws<NotSupportedException>(() => JsonSerializer.Serialize(new MemoryStream(), asyncEnumerable));
212213
}
213214

214215
[Fact]
215216
public void WriteNestedAsyncEnumerableSync_ThrowsNotSupportedException()
216217
{
217218
IAsyncEnumerable<int> asyncEnumerable = new MockedAsyncEnumerable<int>(Enumerable.Range(1, 10));
218219
Assert.Throws<NotSupportedException>(() => JsonSerializer.Serialize(new { Data = asyncEnumerable }));
220+
Assert.Throws<NotSupportedException>(() => JsonSerializer.Serialize(new MemoryStream(), new { Data = asyncEnumerable }));
219221
}
220222

221223
[Fact]
222224
public async Task WriteAsyncEnumerable_ElementSerializationThrows_ShouldDisposeEnumerator()
223225
{
224-
if (StreamingSerializer is null)
226+
if (StreamingSerializer?.IsAsyncSerializer != true)
225227
{
226228
return;
227229
}
@@ -243,7 +245,7 @@ static IEnumerable<int> ThrowingEnumerable()
243245
[Fact]
244246
public async Task ReadRootLevelAsyncEnumerable()
245247
{
246-
if (StreamingSerializer is null)
248+
if (StreamingSerializer?.IsAsyncSerializer != true)
247249
{
248250
return;
249251
}
@@ -257,7 +259,7 @@ public async Task ReadRootLevelAsyncEnumerable()
257259
[Fact]
258260
public async Task ReadNestedAsyncEnumerable()
259261
{
260-
if (StreamingSerializer is null)
262+
if (StreamingSerializer?.IsAsyncSerializer != true)
261263
{
262264
return;
263265
}
@@ -271,7 +273,7 @@ public async Task ReadNestedAsyncEnumerable()
271273
[Fact]
272274
public async Task ReadAsyncEnumerableOfAsyncEnumerables()
273275
{
274-
if (StreamingSerializer is null)
276+
if (StreamingSerializer?.IsAsyncSerializer != true)
275277
{
276278
return;
277279
}
@@ -289,7 +291,7 @@ public async Task ReadAsyncEnumerableOfAsyncEnumerables()
289291
[Fact]
290292
public async Task ReadRootLevelAsyncEnumerableDerivative_ThrowsNotSupportedException()
291293
{
292-
if (StreamingSerializer is null)
294+
if (StreamingSerializer?.IsAsyncSerializer != true)
293295
{
294296
return;
295297
}
@@ -314,7 +316,7 @@ public static IEnumerable<object[]> GetAsyncEnumerableSources()
314316
[Fact]
315317
public async Task RegressionTest_DisposingEnumeratorOnPendingMoveNextAsyncOperation()
316318
{
317-
if (StreamingSerializer is null)
319+
if (StreamingSerializer?.IsAsyncSerializer != true)
318320
{
319321
return;
320322
}
@@ -338,7 +340,7 @@ static async IAsyncEnumerable<int> GetNumbersAsync()
338340
[Fact]
339341
public async Task RegressionTest_ExceptionOnFirstMoveNextShouldNotFlushBuffer()
340342
{
341-
if (StreamingSerializer is null)
343+
if (StreamingSerializer?.IsAsyncSerializer != true)
342344
{
343345
return;
344346
}

src/libraries/System.Text.Json/tests/Common/StreamingJsonSerializerWrapper.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@ public abstract partial class StreamingJsonSerializerWrapper : JsonSerializerWra
1515
/// <summary>
1616
/// True if the serializer is streaming data synchronously.
1717
/// </summary>
18-
public virtual bool IsBlockingSerializer => false;
18+
public abstract bool IsAsyncSerializer { get; }
1919

2020
public abstract Task SerializeWrapper(Stream stream, object value, Type inputType, JsonSerializerOptions? options = null);
2121
public abstract Task SerializeWrapper<T>(Stream stream, T value, JsonSerializerOptions? options = null);

src/libraries/System.Text.Json/tests/System.Text.Json.SourceGeneration.Tests/Serialization/JsonSerializerWrapper.SourceGen.cs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -84,6 +84,8 @@ internal sealed class AsyncStreamSerializerWrapper : StreamingJsonSerializerWrap
8484
private readonly JsonSerializerContext _defaultContext;
8585
private readonly Func<JsonSerializerOptions, JsonSerializerContext> _customContextCreator;
8686

87+
public override bool IsAsyncSerializer => true;
88+
8789
public AsyncStreamSerializerWrapper(JsonSerializerContext defaultContext!!, Func<JsonSerializerOptions, JsonSerializerContext> customContextCreator!!)
8890
{
8991
_defaultContext = defaultContext;

src/libraries/System.Text.Json/tests/System.Text.Json.Tests/Serialization/CollectionTests.cs

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,11 @@ public sealed partial class CollectionTestsDynamic_AsyncStream : CollectionTests
1515
public CollectionTestsDynamic_AsyncStream() : base(JsonSerializerWrapper.AsyncStreamSerializer) { }
1616
}
1717

18-
[ActiveIssue("https://github.com/dotnet/runtime/issues/66687")]
18+
public sealed partial class CollectionTestsDynamic_AsyncStreamWithSmallBuffer : CollectionTests
19+
{
20+
public CollectionTestsDynamic_AsyncStreamWithSmallBuffer() : base(JsonSerializerWrapper.AsyncStreamSerializerWithSmallBuffer) { }
21+
}
22+
1923
public sealed partial class CollectionTestsDynamic_SyncStream : CollectionTests
2024
{
2125
public CollectionTestsDynamic_SyncStream() : base(JsonSerializerWrapper.SyncStreamSerializer) { }

src/libraries/System.Text.Json/tests/System.Text.Json.Tests/Serialization/JsonSerializerWrapper.Reflection.cs

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -122,6 +122,8 @@ private class AsyncStreamSerializerWrapper : StreamingJsonSerializerWrapper
122122
{
123123
private readonly bool _forceSmallBufferInOptions;
124124

125+
public override bool IsAsyncSerializer => true;
126+
125127
public AsyncStreamSerializerWrapper(bool forceSmallBufferInOptions = false)
126128
{
127129
_forceSmallBufferInOptions = forceSmallBufferInOptions;
@@ -183,7 +185,7 @@ public SyncStreamSerializerWrapper(bool forceSmallBufferInOptions = false)
183185
private JsonSerializerOptions? ResolveOptionsInstance(JsonSerializerOptions? options)
184186
=> _forceSmallBufferInOptions ? JsonSerializerOptionsSmallBufferMapper.ResolveOptionsInstanceWithSmallBuffer(options) : options;
185187

186-
public override bool IsBlockingSerializer => true;
188+
public override bool IsAsyncSerializer => false;
187189

188190
public override Task SerializeWrapper<T>(Stream utf8Json, T value, JsonSerializerOptions options = null)
189191
{

0 commit comments

Comments
 (0)