Skip to content
This repository was archived by the owner on Jan 23, 2023. It is now read-only.
/ corefx Public archive

Commit 1719a3f

Browse files
authored
Fix writer depth issue on large stream async (#39417) (#39444)
1 parent 9c3c40a commit 1719a3f

File tree

4 files changed

+112
-5
lines changed

4 files changed

+112
-5
lines changed

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

+1-1
Original file line numberDiff line numberDiff line change
@@ -134,7 +134,7 @@ private static void WriteCore(Utf8JsonWriter writer, PooledByteBufferWriter outp
134134
state.Current.Initialize(type, options);
135135
state.Current.CurrentValue = value;
136136

137-
Write(writer, -1, options, ref state);
137+
Write(writer, writer.CurrentDepth, flushThreshold: -1, options, ref state);
138138
}
139139

140140
writer.Flush();

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

+2-2
Original file line numberDiff line numberDiff line change
@@ -74,13 +74,13 @@ private static async Task WriteAsyncCore(Stream utf8Json, object value, Type typ
7474
state.Current.CurrentValue = value;
7575

7676
bool isFinalBlock;
77-
7877
int flushThreshold;
78+
7979
do
8080
{
8181
flushThreshold = (int)(bufferWriter.Capacity * .9); //todo: determine best value here
8282

83-
isFinalBlock = Write(writer, flushThreshold, options, ref state);
83+
isFinalBlock = Write(writer, originalWriterDepth: 0, flushThreshold, options, ref state);
8484
writer.Flush();
8585

8686
await bufferWriter.WriteToStreamAsync(utf8Json, cancellationToken).ConfigureAwait(false);

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

+2-2
Original file line numberDiff line numberDiff line change
@@ -14,12 +14,12 @@ public static partial class JsonSerializer
1414
// 3) The object is an element in an enumerable.
1515
private static bool Write(
1616
Utf8JsonWriter writer,
17+
int originalWriterDepth,
1718
int flushThreshold,
1819
JsonSerializerOptions options,
1920
ref WriteStack state)
2021
{
2122
bool finishedSerializing;
22-
int currentDepth = writer.CurrentDepth;
2323

2424
try
2525
{
@@ -53,7 +53,7 @@ private static bool Write(
5353

5454
if (finishedSerializing)
5555
{
56-
if (writer.CurrentDepth == 0 || writer.CurrentDepth == currentDepth)
56+
if (writer.CurrentDepth == 0 || writer.CurrentDepth == originalWriterDepth)
5757
{
5858
break;
5959
}

src/System.Text.Json/tests/Serialization/Stream.WriteTests.cs

+107
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
// The .NET Foundation licenses this file to you under the MIT license.
33
// See the LICENSE file in the project root for more information.
44

5+
using System.Collections.Generic;
56
using System.IO;
67
using System.Threading.Tasks;
78
using Xunit;
@@ -149,6 +150,112 @@ public static async Task WritePrimitivesAsync()
149150
int i = await JsonSerializer.DeserializeAsync<int>(stream, options);
150151
Assert.Equal(1, i);
151152
}
153+
154+
private class Session
155+
{
156+
public int Id { get; set; }
157+
public string Title { get; set; }
158+
public virtual string Abstract { get; set; }
159+
public virtual DateTimeOffset? StartTime { get; set; }
160+
public virtual DateTimeOffset? EndTime { get; set; }
161+
public TimeSpan Duration => EndTime?.Subtract(StartTime ?? EndTime ?? DateTimeOffset.MinValue) ?? TimeSpan.Zero;
162+
public int? TrackId { get; set; }
163+
}
164+
165+
private class SessionResponse : Session
166+
{
167+
public Track Track { get; set; }
168+
public List<Speaker> Speakers { get; set; } = new List<Speaker>();
169+
}
170+
171+
private class Track
172+
{
173+
public int Id { get; set; }
174+
public string Name { get; set; }
175+
}
176+
177+
private class Speaker
178+
{
179+
public int Id { get; set; }
180+
public string Name { get; set; }
181+
public string Bio { get; set; }
182+
public virtual string WebSite { get; set; }
183+
}
184+
185+
[Theory]
186+
[InlineData(0)]
187+
[InlineData(10)]
188+
[InlineData(1000)]
189+
[InlineData(4000)]
190+
[InlineData(8000)]
191+
[InlineData(16000)]
192+
public static async Task LargeJsonFile(int bufferSize)
193+
{
194+
// Build up a large list to serialize.
195+
var list = new List<SessionResponse>();
196+
for (int i = 0; i < 100; i++)
197+
{
198+
SessionResponse response = new SessionResponse
199+
{
200+
Id = i,
201+
Abstract = new string('A', i * 2),
202+
Title = new string('T', i),
203+
StartTime = new DateTime(i),
204+
EndTime = new DateTime(i * 10000),
205+
TrackId = i,
206+
Track = new Track()
207+
{
208+
Id = i,
209+
Name = new string('N', i),
210+
},
211+
};
212+
213+
for (int j = 0; j < 5; j++)
214+
{
215+
response.Speakers.Add(new Speaker()
216+
{
217+
Bio = new string('B', 50),
218+
Id = j,
219+
Name = new string('N', i),
220+
WebSite = new string('W', 20),
221+
});
222+
}
223+
224+
list.Add(response);
225+
}
226+
227+
// Adjust buffer length to encourage buffer flusing at several levels.
228+
JsonSerializerOptions options = new JsonSerializerOptions();
229+
if (bufferSize != 0)
230+
{
231+
options.DefaultBufferSize = bufferSize;
232+
}
233+
234+
string json = JsonSerializer.Serialize(list, options);
235+
Assert.True(json.Length > 100_000); // Verify data is large and will cause buffer flushing.
236+
Assert.True(json.Length < 200_000); // But not too large for memory considerations.
237+
238+
// Sync case.
239+
{
240+
List<SessionResponse> deserializedList = JsonSerializer.Deserialize<List<SessionResponse>>(json, options);
241+
Assert.Equal(100, deserializedList.Count);
242+
243+
string jsonSerialized = JsonSerializer.Serialize(deserializedList, options);
244+
Assert.Equal(json, jsonSerialized);
245+
}
246+
247+
// Async case.
248+
using (var memoryStream = new MemoryStream())
249+
{
250+
await JsonSerializer.SerializeAsync(memoryStream, list, options);
251+
string jsonSerialized = Encoding.UTF8.GetString(memoryStream.ToArray());
252+
Assert.Equal(json, jsonSerialized);
253+
254+
memoryStream.Position = 0;
255+
List<SessionResponse> deserializedList = await JsonSerializer.DeserializeAsync<List<SessionResponse>>(memoryStream, options);
256+
Assert.Equal(100, deserializedList.Count);
257+
}
258+
}
152259
}
153260

154261
public sealed class TestStream : Stream

0 commit comments

Comments
 (0)