Skip to content

Commit c141b8a

Browse files
Fix JsonDocument thread safety. (#76716)
Co-authored-by: stoub@microsoft.com
1 parent 94d7503 commit c141b8a

File tree

2 files changed

+41
-33
lines changed

2 files changed

+41
-33
lines changed

src/libraries/System.Text.Json/src/System/Text/Json/Document/JsonDocument.cs

Lines changed: 3 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -27,8 +27,6 @@ public sealed partial class JsonDocument : IDisposable
2727
private byte[]? _extraRentedArrayPoolBytes;
2828
private PooledByteBufferWriter? _extraPooledByteBufferWriter;
2929

30-
private (int, string?) _lastIndexAndString = (-1, null);
31-
3230
internal bool IsDisposable { get; }
3331

3432
/// <summary>
@@ -266,14 +264,6 @@ private ReadOnlyMemory<byte> GetPropertyRawValue(int valueIndex)
266264
{
267265
CheckNotDisposed();
268266

269-
(int lastIdx, string? lastString) = _lastIndexAndString;
270-
271-
if (lastIdx == index)
272-
{
273-
Debug.Assert(lastString != null);
274-
return lastString;
275-
}
276-
277267
DbRow row = _parsedData.Get(index);
278268

279269
JsonTokenType tokenType = row.TokenType;
@@ -288,18 +278,9 @@ private ReadOnlyMemory<byte> GetPropertyRawValue(int valueIndex)
288278
ReadOnlySpan<byte> data = _utf8Json.Span;
289279
ReadOnlySpan<byte> segment = data.Slice(row.Location, row.SizeOrLength);
290280

291-
if (row.HasComplexChildren)
292-
{
293-
lastString = JsonReaderHelper.GetUnescapedString(segment);
294-
}
295-
else
296-
{
297-
lastString = JsonReaderHelper.TranscodeHelper(segment);
298-
}
299-
300-
Debug.Assert(lastString != null);
301-
_lastIndexAndString = (index, lastString);
302-
return lastString;
281+
return row.HasComplexChildren
282+
? JsonReaderHelper.GetUnescapedString(segment)
283+
: JsonReaderHelper.TranscodeHelper(segment);
303284
}
304285

305286
internal bool TextEquals(int index, ReadOnlySpan<char> otherText, bool isPropertyName)
@@ -308,13 +289,6 @@ internal bool TextEquals(int index, ReadOnlySpan<char> otherText, bool isPropert
308289

309290
int matchIndex = isPropertyName ? index - DbRow.Size : index;
310291

311-
(int lastIdx, string? lastString) = _lastIndexAndString;
312-
313-
if (lastIdx == matchIndex)
314-
{
315-
return otherText.SequenceEqual(lastString.AsSpan());
316-
}
317-
318292
byte[]? otherUtf8TextArray = null;
319293

320294
int length = checked(otherText.Length * JsonConstants.MaxExpansionFactorWhileTranscoding);

src/libraries/System.Text.Json/tests/System.Text.Json.Tests/JsonDocumentTests.cs

Lines changed: 38 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@
1414
using System.Linq;
1515
using System.Runtime.InteropServices;
1616
using System.Threading.Tasks;
17+
using System.Threading;
1718

1819
namespace System.Text.Json.Tests
1920
{
@@ -2744,11 +2745,9 @@ public static void ObjectEnumeratorIndependentWalk()
27442745
Assert.Equal(test, property.Value.GetInt32());
27452746
test++;
27462747

2747-
// Subsequent read of the same JsonProperty doesn't allocate a new string
2748-
// (if another property is inspected from the same document that guarantee
2749-
// doesn't hold).
2748+
// Subsequent read of the same JsonProperty should return an equal string
27502749
string propertyName2 = property.Name;
2751-
Assert.Same(propertyName, propertyName2);
2750+
Assert.Equal(propertyName, propertyName2);
27522751
}
27532752

27542753
test = 0;
@@ -3607,6 +3606,41 @@ public static void NameEquals_Empty_Throws()
36073606
}
36083607
}
36093608

3609+
[ConditionalFact(typeof(PlatformDetection), nameof(PlatformDetection.IsThreadingSupported))]
3610+
[OuterLoop] // thread-safety / stress test
3611+
public static async Task GetString_ConcurrentUse_ThreadSafe()
3612+
{
3613+
using (JsonDocument doc = JsonDocument.Parse(SR.SimpleObjectJson))
3614+
{
3615+
JsonElement first = doc.RootElement.GetProperty("first");
3616+
JsonElement last = doc.RootElement.GetProperty("last");
3617+
3618+
const int Iters = 10_000;
3619+
using (var gate = new Barrier(2))
3620+
{
3621+
await Task.WhenAll(
3622+
Task.Factory.StartNew(() =>
3623+
{
3624+
gate.SignalAndWait();
3625+
for (int i = 0; i < Iters; i++)
3626+
{
3627+
Assert.Equal("John", first.GetString());
3628+
Assert.True(first.ValueEquals("John"));
3629+
}
3630+
}, CancellationToken.None, TaskCreationOptions.LongRunning, TaskScheduler.Default),
3631+
Task.Factory.StartNew(() =>
3632+
{
3633+
gate.SignalAndWait();
3634+
for (int i = 0; i < Iters; i++)
3635+
{
3636+
Assert.Equal("Smith", last.GetString());
3637+
Assert.True(last.ValueEquals("Smith"));
3638+
}
3639+
}, CancellationToken.None, TaskCreationOptions.LongRunning, TaskScheduler.Default));
3640+
}
3641+
}
3642+
}
3643+
36103644
private static void BuildSegmentedReader(
36113645
out Utf8JsonReader reader,
36123646
ReadOnlyMemory<byte> data,

0 commit comments

Comments
 (0)