diff --git a/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/WriteStack.cs b/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/WriteStack.cs index 8a09458ecc0d81..78414a16d744bf 100644 --- a/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/WriteStack.cs +++ b/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/WriteStack.cs @@ -183,6 +183,8 @@ public readonly JsonTypeInfo PeekNestedJsonTypeInfo() public void Push() { + Debug.Assert(_continuationCount == 0 || _count < _continuationCount); + if (_continuationCount == 0) { Debug.Assert(Current.PolymorphicSerializationState != PolymorphicSerializationState.PolymorphicReEntrySuspended); @@ -234,6 +236,7 @@ public void Push() public void Pop(bool success) { Debug.Assert(_count > 0); + Debug.Assert(_continuationCount == 0 || _count < _continuationCount); if (!success) { @@ -314,11 +317,23 @@ public readonly void DisposePendingDisposablesOnException() Debug.Assert(Current.AsyncDisposable is null); DisposeFrame(Current.CollectionEnumerator, ref exception); - int stackSize = Math.Max(_count, _continuationCount); - for (int i = 0; i < stackSize - 1; i++) + if (_stack is not null) { - Debug.Assert(_stack[i].AsyncDisposable is null); - DisposeFrame(_stack[i].CollectionEnumerator, ref exception); + int currentIndex = _count - _indexOffset; + int stackSize = Math.Max(currentIndex, _continuationCount); + for (int i = 0; i < stackSize; i++) + { + Debug.Assert(_stack[i].AsyncDisposable is null); + + if (i == currentIndex) + { + // Matches the entry in Current, skip to avoid double disposal. + Debug.Assert(_stack[i].CollectionEnumerator is null || ReferenceEquals(Current.CollectionEnumerator, _stack[i].CollectionEnumerator)); + continue; + } + + DisposeFrame(_stack[i].CollectionEnumerator, ref exception); + } } if (exception is not null) @@ -352,10 +367,23 @@ public readonly async ValueTask DisposePendingDisposablesOnExceptionAsync() exception = await DisposeFrame(Current.CollectionEnumerator, Current.AsyncDisposable, exception).ConfigureAwait(false); - int stackSize = Math.Max(_count, _continuationCount); - for (int i = 0; i < stackSize - 1; i++) + if (_stack is not null) { - exception = await DisposeFrame(_stack[i].CollectionEnumerator, _stack[i].AsyncDisposable, exception).ConfigureAwait(false); + Debug.Assert(_continuationCount == 0 || _count < _continuationCount); + int currentIndex = _count - _indexOffset; + int stackSize = Math.Max(currentIndex, _continuationCount); + for (int i = 0; i < stackSize; i++) + { + if (i == currentIndex) + { + // Matches the entry in Current, skip to avoid double disposal. + Debug.Assert(_stack[i].CollectionEnumerator is null || ReferenceEquals(Current.CollectionEnumerator, _stack[i].CollectionEnumerator)); + Debug.Assert(_stack[i].AsyncDisposable is null || ReferenceEquals(Current.AsyncDisposable, _stack[i].AsyncDisposable)); + continue; + } + + exception = await DisposeFrame(_stack[i].CollectionEnumerator, _stack[i].AsyncDisposable, exception).ConfigureAwait(false); + } } if (exception is not null) diff --git a/src/libraries/System.Text.Json/tests/Common/AsyncEnumerableTests.cs b/src/libraries/System.Text.Json/tests/Common/AsyncEnumerableTests.cs index c7643371abbc4b..893ab5bd2aede3 100644 --- a/src/libraries/System.Text.Json/tests/Common/AsyncEnumerableTests.cs +++ b/src/libraries/System.Text.Json/tests/Common/AsyncEnumerableTests.cs @@ -387,6 +387,32 @@ public static IEnumerable GetAsyncEnumerableSources() static object[] WrapArgs(IEnumerable source, int bufferSize, DeserializeAsyncEnumerableOverload overload) => new object[] { source, bufferSize, overload }; } + [Theory] + [InlineData(0)] + [InlineData(1)] + [InlineData(2)] + [InlineData(3)] + [InlineData(5)] + [InlineData(43)] + public async Task SerializeAsyncEnumerable_Cancellation_DisposesEnumerators(int depth) + { + // Regression test for https://github.com/dotnet/runtime/issues/120010 + + using SelfCancellingAsyncEnumerable enumerable = new(); + using MemoryStream stream = new MemoryStream(); + + object wrappingValue = enumerable; + while (depth-- > 0) + { + // Use a LINQ enumerable instead of array/list + // to force use of enumerators in every layer. + wrappingValue = Enumerable.Repeat(wrappingValue, 1); + } + + await Assert.ThrowsAsync(() => StreamingSerializer.SerializeWrapper(stream, wrappingValue, cancellationToken: enumerable.CancellationToken)); + Assert.True(enumerable.IsEnumeratorDisposed); + } + public enum DeserializeAsyncEnumerableOverload { JsonSerializerOptions, JsonTypeInfo }; private IAsyncEnumerable DeserializeAsyncEnumerableWrapper(Stream stream, JsonSerializerOptions options = null, CancellationToken cancellationToken = default, DeserializeAsyncEnumerableOverload overload = DeserializeAsyncEnumerableOverload.JsonSerializerOptions) @@ -457,5 +483,35 @@ public override string Read(ref Utf8JsonReader reader, Type typeToConvert, JsonS public override void Write(Utf8JsonWriter writer, string value, JsonSerializerOptions options) => throw new NotImplementedException(); } + + sealed class SelfCancellingAsyncEnumerable : IAsyncEnumerable, IDisposable + { + private readonly CancellationTokenSource _cts = new(); + public CancellationToken CancellationToken => _cts.Token; + public bool IsEnumeratorDisposed { get; private set; } + public IAsyncEnumerator GetAsyncEnumerator(CancellationToken _) => new Enumerator(this); + private sealed class Enumerator(SelfCancellingAsyncEnumerable parent) : IAsyncEnumerator + { + public int Current { get; private set; } + public async ValueTask MoveNextAsync() + { + await Task.Yield(); + if (++Current == 10) + { + parent._cts.Cancel(); + } + + return true; + } + + public ValueTask DisposeAsync() + { + parent.IsEnumeratorDisposed = true; + return default; + } + } + + public void Dispose() => _cts.Dispose(); + } } } diff --git a/src/libraries/System.Text.Json/tests/Common/CollectionTests/CollectionTests.Generic.Write.cs b/src/libraries/System.Text.Json/tests/Common/CollectionTests/CollectionTests.Generic.Write.cs index 19afcd962c750b..aa0a740819d8ea 100644 --- a/src/libraries/System.Text.Json/tests/Common/CollectionTests/CollectionTests.Generic.Write.cs +++ b/src/libraries/System.Text.Json/tests/Common/CollectionTests/CollectionTests.Generic.Write.cs @@ -1,8 +1,10 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. +using System.Collections; using System.Collections.Generic; using System.Linq; +using System.Threading; using System.Threading.Tasks; using Xunit; @@ -983,6 +985,66 @@ static IEnumerable ThrowingEnumerable() } } + [Theory] + [InlineData(0)] + [InlineData(1)] + [InlineData(2)] + [InlineData(3)] + [InlineData(5)] + [InlineData(43)] + public async Task WriteIEnumerableOfT_Cancellation_DisposesEnumerators(int depth) + { + // Regression test for https://github.com/dotnet/runtime/issues/120010 + + if (StreamingSerializer?.IsAsyncSerializer is not true) + { + return; // require serializers with cancellation support. + } + + JsonSerializerOptions options = Serializer.CreateOptions(opts => opts.DefaultBufferSize = 1); // Force early async writes + using SelfCancellingEnumerable enumerable = new(); + using Utf8MemoryStream stream = new(); + + object wrappingValue = enumerable; + while (depth-- > 0) + { + // Use a LINQ enumerable instead of array/list + // to force use of enumerators in every layer. + wrappingValue = Enumerable.Repeat(wrappingValue, 1); + } + + await Assert.ThrowsAsync(() => StreamingSerializer.SerializeWrapper(stream, wrappingValue, options, enumerable.CancellationToken)); + Assert.True(enumerable.IsEnumeratorDisposed); + } + + public sealed class SelfCancellingEnumerable : IEnumerable, IDisposable + { + private readonly CancellationTokenSource _cts = new(); + public bool IsEnumeratorDisposed { get; private set; } + public CancellationToken CancellationToken => _cts.Token; + public IEnumerator GetEnumerator() => new Enumerator(this); + IEnumerator IEnumerable.GetEnumerator() => GetEnumerator(); + public void Dispose() => _cts.Dispose(); + + private sealed class Enumerator(SelfCancellingEnumerable parent) : IEnumerator + { + public int Current { get; private set; } + object IEnumerator.Current => Current; + public bool MoveNext() + { + if (++Current == 10) + { + parent._cts.Cancel(); + } + + return true; + } + + public void Dispose() => parent.IsEnumeratorDisposed = true; + public void Reset() { } + } + } + public class SimpleClassWithKeyValuePairs { public KeyValuePair KvpWStrVal { get; set; } diff --git a/src/libraries/System.Text.Json/tests/Common/PipeJsonSerializerWrapper.cs b/src/libraries/System.Text.Json/tests/Common/PipeJsonSerializerWrapper.cs index 478081974b64b6..0893c1d89e4bcd 100644 --- a/src/libraries/System.Text.Json/tests/Common/PipeJsonSerializerWrapper.cs +++ b/src/libraries/System.Text.Json/tests/Common/PipeJsonSerializerWrapper.cs @@ -4,6 +4,7 @@ using System.Buffers; using System.IO.Pipelines; using System.Text.Json.Serialization.Metadata; +using System.Threading; using System.Threading.Tasks; namespace System.Text.Json.Serialization.Tests @@ -13,16 +14,16 @@ namespace System.Text.Json.Serialization.Tests /// public abstract partial class PipeJsonSerializerWrapper : StreamingJsonSerializerWrapper { - public abstract Task SerializeWrapper(PipeWriter stream, object value, Type inputType, JsonSerializerOptions? options = null); - public abstract Task SerializeWrapper(PipeWriter stream, T value, JsonSerializerOptions? options = null); - public abstract Task SerializeWrapper(PipeWriter stream, object value, Type inputType, JsonSerializerContext context); - public abstract Task SerializeWrapper(PipeWriter stream, T value, JsonTypeInfo jsonTypeInfo); - public abstract Task SerializeWrapper(PipeWriter stream, object value, JsonTypeInfo jsonTypeInfo); - public abstract Task DeserializeWrapper(PipeReader utf8Json, Type returnType, JsonSerializerOptions? options = null); - public abstract Task DeserializeWrapper(PipeReader utf8Json, JsonSerializerOptions? options = null); - public abstract Task DeserializeWrapper(PipeReader utf8Json, Type returnType, JsonSerializerContext context); - public abstract Task DeserializeWrapper(PipeReader utf8Json, JsonTypeInfo jsonTypeInfo); - public abstract Task DeserializeWrapper(PipeReader utf8Json, JsonTypeInfo jsonTypeInfo); + public abstract Task SerializeWrapper(PipeWriter stream, object value, Type inputType, JsonSerializerOptions? options = null, CancellationToken cancellationToken = default); + public abstract Task SerializeWrapper(PipeWriter stream, T value, JsonSerializerOptions? options = null, CancellationToken cancellationToken = default); + public abstract Task SerializeWrapper(PipeWriter stream, object value, Type inputType, JsonSerializerContext context, CancellationToken cancellationToken = default); + public abstract Task SerializeWrapper(PipeWriter stream, T value, JsonTypeInfo jsonTypeInfo, CancellationToken cancellationToken = default); + public abstract Task SerializeWrapper(PipeWriter stream, object value, JsonTypeInfo jsonTypeInfo, CancellationToken cancellationToken = default); + public abstract Task DeserializeWrapper(PipeReader utf8Json, Type returnType, JsonSerializerOptions? options = null, CancellationToken cancellationToken = default); + public abstract Task DeserializeWrapper(PipeReader utf8Json, JsonSerializerOptions? options = null, CancellationToken cancellationToken = default); + public abstract Task DeserializeWrapper(PipeReader utf8Json, Type returnType, JsonSerializerContext context, CancellationToken cancellationToken = default); + public abstract Task DeserializeWrapper(PipeReader utf8Json, JsonTypeInfo jsonTypeInfo, CancellationToken cancellationToken = default); + public abstract Task DeserializeWrapper(PipeReader utf8Json, JsonTypeInfo jsonTypeInfo, CancellationToken cancellationToken = default); public override async Task SerializeWrapper(object value, Type inputType, JsonSerializerOptions options = null) { diff --git a/src/libraries/System.Text.Json/tests/Common/StreamingJsonSerializerWrapper.cs b/src/libraries/System.Text.Json/tests/Common/StreamingJsonSerializerWrapper.cs index cf09eac91336ef..bff60e04ce0e17 100644 --- a/src/libraries/System.Text.Json/tests/Common/StreamingJsonSerializerWrapper.cs +++ b/src/libraries/System.Text.Json/tests/Common/StreamingJsonSerializerWrapper.cs @@ -3,6 +3,7 @@ using System.IO; using System.Text.Json.Serialization.Metadata; +using System.Threading; using System.Threading.Tasks; namespace System.Text.Json.Serialization.Tests @@ -18,16 +19,16 @@ public abstract partial class StreamingJsonSerializerWrapper : JsonSerializerWra public abstract bool IsAsyncSerializer { get; } public virtual bool ForceSmallBufferInOptions { get; } = false; - public abstract Task SerializeWrapper(Stream stream, object value, Type inputType, JsonSerializerOptions? options = null); - public abstract Task SerializeWrapper(Stream stream, T value, JsonSerializerOptions? options = null); - public abstract Task SerializeWrapper(Stream stream, object value, Type inputType, JsonSerializerContext context); - public abstract Task SerializeWrapper(Stream stream, T value, JsonTypeInfo jsonTypeInfo); - public abstract Task SerializeWrapper(Stream stream, object value, JsonTypeInfo jsonTypeInfo); - public abstract Task DeserializeWrapper(Stream utf8Json, Type returnType, JsonSerializerOptions? options = null); - public abstract Task DeserializeWrapper(Stream utf8Json, JsonSerializerOptions? options = null); - public abstract Task DeserializeWrapper(Stream utf8Json, Type returnType, JsonSerializerContext context); - public abstract Task DeserializeWrapper(Stream utf8Json, JsonTypeInfo jsonTypeInfo); - public abstract Task DeserializeWrapper(Stream utf8Json, JsonTypeInfo jsonTypeInfo); + public abstract Task SerializeWrapper(Stream stream, object value, Type inputType, JsonSerializerOptions? options = null, CancellationToken cancellationToken = default); + public abstract Task SerializeWrapper(Stream stream, T value, JsonSerializerOptions? options = null, CancellationToken cancellationToken = default); + public abstract Task SerializeWrapper(Stream stream, object value, Type inputType, JsonSerializerContext context, CancellationToken cancellationToken = default); + public abstract Task SerializeWrapper(Stream stream, T value, JsonTypeInfo jsonTypeInfo, CancellationToken cancellationToken = default); + public abstract Task SerializeWrapper(Stream stream, object value, JsonTypeInfo jsonTypeInfo, CancellationToken cancellationToken = default); + public abstract Task DeserializeWrapper(Stream utf8Json, Type returnType, JsonSerializerOptions? options = null, CancellationToken cancellationToken = default); + public abstract Task DeserializeWrapper(Stream utf8Json, JsonSerializerOptions? options = null, CancellationToken cancellationToken = default); + public abstract Task DeserializeWrapper(Stream utf8Json, Type returnType, JsonSerializerContext context, CancellationToken cancellationToken = default); + public abstract Task DeserializeWrapper(Stream utf8Json, JsonTypeInfo jsonTypeInfo, CancellationToken cancellationToken = default); + public abstract Task DeserializeWrapper(Stream utf8Json, JsonTypeInfo jsonTypeInfo, CancellationToken cancellationToken = default); public override async Task SerializeWrapper(object value, Type inputType, JsonSerializerOptions options = null) { diff --git a/src/libraries/System.Text.Json/tests/System.Text.Json.SourceGeneration.Tests/Serialization/CollectionTests.cs b/src/libraries/System.Text.Json/tests/System.Text.Json.SourceGeneration.Tests/Serialization/CollectionTests.cs index 6e6068a058f845..cb88437b6b552a 100644 --- a/src/libraries/System.Text.Json/tests/System.Text.Json.SourceGeneration.Tests/Serialization/CollectionTests.cs +++ b/src/libraries/System.Text.Json/tests/System.Text.Json.SourceGeneration.Tests/Serialization/CollectionTests.cs @@ -452,6 +452,7 @@ public async Task DeserializeAsyncEnumerable() [JsonSerializable(typeof(KeyValuePair>))] [JsonSerializable(typeof(KeyValuePair>))] [JsonSerializable(typeof(KeyValuePair>))] + [JsonSerializable(typeof(SelfCancellingEnumerable))] [JsonSerializable(typeof(SimpleClassWithKeyValuePairs))] [JsonSerializable(typeof(KeyNameNullPolicy))] [JsonSerializable(typeof(ValueNameNullPolicy))] @@ -873,6 +874,7 @@ public CollectionTests_Default() [JsonSerializable(typeof(KeyValuePair>))] [JsonSerializable(typeof(KeyValuePair>))] [JsonSerializable(typeof(KeyValuePair>))] + [JsonSerializable(typeof(SelfCancellingEnumerable))] [JsonSerializable(typeof(SimpleClassWithKeyValuePairs))] [JsonSerializable(typeof(KeyNameNullPolicy))] [JsonSerializable(typeof(ValueNameNullPolicy))] diff --git a/src/libraries/System.Text.Json/tests/System.Text.Json.SourceGeneration.Tests/Serialization/JsonSerializerWrapper.SourceGen.cs b/src/libraries/System.Text.Json/tests/System.Text.Json.SourceGeneration.Tests/Serialization/JsonSerializerWrapper.SourceGen.cs index bfe273959c1abb..b54ad5c16db648 100644 --- a/src/libraries/System.Text.Json/tests/System.Text.Json.SourceGeneration.Tests/Serialization/JsonSerializerWrapper.SourceGen.cs +++ b/src/libraries/System.Text.Json/tests/System.Text.Json.SourceGeneration.Tests/Serialization/JsonSerializerWrapper.SourceGen.cs @@ -102,45 +102,45 @@ public AsyncStreamSerializerWrapper(JsonSerializerContext defaultContext) _defaultContext = defaultContext ?? throw new ArgumentNullException(nameof(defaultContext)); } - public override async Task DeserializeWrapper(Stream utf8Json, JsonSerializerOptions? options = null) + public override async Task DeserializeWrapper(Stream utf8Json, JsonSerializerOptions? options = null, CancellationToken cancellationToken = default) { - return await JsonSerializer.DeserializeAsync(utf8Json, GetOptions(options)); + return await JsonSerializer.DeserializeAsync(utf8Json, GetOptions(options), cancellationToken); } - public override async Task DeserializeWrapper(Stream utf8Json, Type returnType, JsonSerializerOptions options = null) + public override async Task DeserializeWrapper(Stream utf8Json, Type returnType, JsonSerializerOptions options = null, CancellationToken cancellationToken = default) { - return await JsonSerializer.DeserializeAsync(utf8Json, returnType, GetOptions(options)); + return await JsonSerializer.DeserializeAsync(utf8Json, returnType, GetOptions(options), cancellationToken); } - public override async Task DeserializeWrapper(Stream utf8Json, JsonTypeInfo jsonTypeInfo) + public override async Task DeserializeWrapper(Stream utf8Json, JsonTypeInfo jsonTypeInfo, CancellationToken cancellationToken = default) { - return await JsonSerializer.DeserializeAsync(utf8Json, jsonTypeInfo); + return await JsonSerializer.DeserializeAsync(utf8Json, jsonTypeInfo, cancellationToken); } - public override async Task DeserializeWrapper(Stream utf8Json, JsonTypeInfo jsonTypeInfo) + public override async Task DeserializeWrapper(Stream utf8Json, JsonTypeInfo jsonTypeInfo, CancellationToken cancellationToken = default) { - return await JsonSerializer.DeserializeAsync(utf8Json, jsonTypeInfo); + return await JsonSerializer.DeserializeAsync(utf8Json, jsonTypeInfo, cancellationToken); } - public override async Task DeserializeWrapper(Stream utf8Json, Type returnType, JsonSerializerContext context) + public override async Task DeserializeWrapper(Stream utf8Json, Type returnType, JsonSerializerContext context, CancellationToken cancellationToken = default) { - return await JsonSerializer.DeserializeAsync(utf8Json, returnType, context); + return await JsonSerializer.DeserializeAsync(utf8Json, returnType, context, cancellationToken); } - public override Task SerializeWrapper(Stream stream, T value, JsonSerializerOptions options = null) - => JsonSerializer.SerializeAsync(stream, value, GetOptions(options)); + public override Task SerializeWrapper(Stream stream, T value, JsonSerializerOptions options = null, CancellationToken cancellationToken = default) + => JsonSerializer.SerializeAsync(stream, value, GetOptions(options), cancellationToken); - public override Task SerializeWrapper(Stream stream, object value, Type inputType, JsonSerializerOptions options = null) - => JsonSerializer.SerializeAsync(stream, value, inputType, GetOptions(options)); + public override Task SerializeWrapper(Stream stream, object value, Type inputType, JsonSerializerOptions options = null, CancellationToken cancellationToken = default) + => JsonSerializer.SerializeAsync(stream, value, inputType, GetOptions(options), cancellationToken); - public override Task SerializeWrapper(Stream stream, T value, JsonTypeInfo jsonTypeInfo) - => JsonSerializer.SerializeAsync(stream, value, jsonTypeInfo); + public override Task SerializeWrapper(Stream stream, T value, JsonTypeInfo jsonTypeInfo, CancellationToken cancellationToken = default) + => JsonSerializer.SerializeAsync(stream, value, jsonTypeInfo, cancellationToken); - public override Task SerializeWrapper(Stream stream, object value, JsonTypeInfo jsonTypeInfo) - => JsonSerializer.SerializeAsync(stream, value, jsonTypeInfo); + public override Task SerializeWrapper(Stream stream, object value, JsonTypeInfo jsonTypeInfo, CancellationToken cancellationToken = default) + => JsonSerializer.SerializeAsync(stream, value, jsonTypeInfo, cancellationToken); - public override Task SerializeWrapper(Stream stream, object value, Type inputType, JsonSerializerContext context) - => JsonSerializer.SerializeAsync(stream, value, inputType, context); + public override Task SerializeWrapper(Stream stream, object value, Type inputType, JsonSerializerContext context, CancellationToken cancellationToken = default) + => JsonSerializer.SerializeAsync(stream, value, inputType, context, cancellationToken); public override JsonTypeInfo GetTypeInfo(Type type, JsonSerializerOptions? options = null, bool mutable = false) => base.GetTypeInfo(type, GetOptions(options), mutable); diff --git a/src/libraries/System.Text.Json/tests/System.Text.Json.Tests/Serialization/JsonSerializerWrapper.Reflection.cs b/src/libraries/System.Text.Json/tests/System.Text.Json.Tests/Serialization/JsonSerializerWrapper.Reflection.cs index e811d951f02cc5..7d642cf755739a 100644 --- a/src/libraries/System.Text.Json/tests/System.Text.Json.Tests/Serialization/JsonSerializerWrapper.Reflection.cs +++ b/src/libraries/System.Text.Json/tests/System.Text.Json.Tests/Serialization/JsonSerializerWrapper.Reflection.cs @@ -186,54 +186,54 @@ public AsyncStreamSerializerWrapper(bool forceSmallBufferInOptions = false, bool private Stream ResolveReadStream(Stream stream) => stream is not null && _forceBomInsertions ? new Utf8BomInsertingStream(stream) : stream; - public override Task SerializeWrapper(Stream utf8Json, T value, JsonSerializerOptions options = null) + public override Task SerializeWrapper(Stream utf8Json, T value, JsonSerializerOptions options = null, CancellationToken cancellationToken = default) { - return JsonSerializer.SerializeAsync(utf8Json, value, ResolveOptionsInstance(options)); + return JsonSerializer.SerializeAsync(utf8Json, value, ResolveOptionsInstance(options), cancellationToken); } - public override Task SerializeWrapper(Stream utf8Json, object value, Type inputType, JsonSerializerOptions options = null) + public override Task SerializeWrapper(Stream utf8Json, object value, Type inputType, JsonSerializerOptions options = null, CancellationToken cancellationToken = default) { - return JsonSerializer.SerializeAsync(utf8Json, value, inputType, ResolveOptionsInstance(options)); + return JsonSerializer.SerializeAsync(utf8Json, value, inputType, ResolveOptionsInstance(options), cancellationToken); } - public override Task SerializeWrapper(Stream stream, T value, JsonTypeInfo jsonTypeInfo) + public override Task SerializeWrapper(Stream stream, T value, JsonTypeInfo jsonTypeInfo, CancellationToken cancellationToken = default) { - return JsonSerializer.SerializeAsync(stream, value, jsonTypeInfo); + return JsonSerializer.SerializeAsync(stream, value, jsonTypeInfo, cancellationToken); } - public override Task SerializeWrapper(Stream stream, object value, JsonTypeInfo jsonTypeInfo) + public override Task SerializeWrapper(Stream stream, object value, JsonTypeInfo jsonTypeInfo, CancellationToken cancellationToken = default) { - return JsonSerializer.SerializeAsync(stream, value, jsonTypeInfo); + return JsonSerializer.SerializeAsync(stream, value, jsonTypeInfo, cancellationToken); } - public override Task SerializeWrapper(Stream stream, object value, Type inputType, JsonSerializerContext context) + public override Task SerializeWrapper(Stream stream, object value, Type inputType, JsonSerializerContext context, CancellationToken cancellationToken = default) { - return JsonSerializer.SerializeAsync(stream, value, inputType, context); + return JsonSerializer.SerializeAsync(stream, value, inputType, context, cancellationToken); } - public override async Task DeserializeWrapper(Stream utf8Json, JsonSerializerOptions options = null) + public override async Task DeserializeWrapper(Stream utf8Json, JsonSerializerOptions options = null, CancellationToken cancellationToken = default) { - return await JsonSerializer.DeserializeAsync(ResolveReadStream(utf8Json), ResolveOptionsInstance(options)); + return await JsonSerializer.DeserializeAsync(ResolveReadStream(utf8Json), ResolveOptionsInstance(options), cancellationToken); } - public override async Task DeserializeWrapper(Stream utf8Json, Type returnType, JsonSerializerOptions options = null) + public override async Task DeserializeWrapper(Stream utf8Json, Type returnType, JsonSerializerOptions options = null, CancellationToken cancellationToken = default) { - return await JsonSerializer.DeserializeAsync(ResolveReadStream(utf8Json), returnType, ResolveOptionsInstance(options)); + return await JsonSerializer.DeserializeAsync(ResolveReadStream(utf8Json), returnType, ResolveOptionsInstance(options), cancellationToken); } - public override async Task DeserializeWrapper(Stream utf8Json, JsonTypeInfo jsonTypeInfo) + public override async Task DeserializeWrapper(Stream utf8Json, JsonTypeInfo jsonTypeInfo, CancellationToken cancellationToken = default) { - return await JsonSerializer.DeserializeAsync(ResolveReadStream(utf8Json), jsonTypeInfo); + return await JsonSerializer.DeserializeAsync(ResolveReadStream(utf8Json), jsonTypeInfo, cancellationToken); } - public override async Task DeserializeWrapper(Stream utf8Json, JsonTypeInfo jsonTypeInfo) + public override async Task DeserializeWrapper(Stream utf8Json, JsonTypeInfo jsonTypeInfo, CancellationToken cancellationToken = default) { - return await JsonSerializer.DeserializeAsync(ResolveReadStream(utf8Json), jsonTypeInfo); + return await JsonSerializer.DeserializeAsync(ResolveReadStream(utf8Json), jsonTypeInfo, cancellationToken); } - public override async Task DeserializeWrapper(Stream utf8Json, Type returnType, JsonSerializerContext context) + public override async Task DeserializeWrapper(Stream utf8Json, Type returnType, JsonSerializerContext context, CancellationToken cancellationToken = default) { - return await JsonSerializer.DeserializeAsync(ResolveReadStream(utf8Json), returnType, context); + return await JsonSerializer.DeserializeAsync(ResolveReadStream(utf8Json), returnType, context, cancellationToken); } public override IAsyncEnumerable DeserializeAsyncEnumerable(Stream utf8Json, JsonSerializerOptions options = null, CancellationToken cancellationToken = default) @@ -278,61 +278,61 @@ public SyncStreamSerializerWrapper(bool forceSmallBufferInOptions = false, bool private Stream ResolveReadStream(Stream stream) => stream is not null && _forceBomInsertions ? new Utf8BomInsertingStream(stream) : stream; - public override Task SerializeWrapper(Stream utf8Json, T value, JsonSerializerOptions options = null) + public override Task SerializeWrapper(Stream utf8Json, T value, JsonSerializerOptions options = null, CancellationToken cancellationToken = default) { - JsonSerializer.Serialize(utf8Json, value, ResolveOptionsInstance(options)); + JsonSerializer.Serialize(utf8Json, value, ResolveOptionsInstance(options)); return Task.CompletedTask; } - public override Task SerializeWrapper(Stream utf8Json, object value, Type inputType, JsonSerializerOptions options = null) + public override Task SerializeWrapper(Stream utf8Json, object value, Type inputType, JsonSerializerOptions options = null, CancellationToken cancellationToken = default) { JsonSerializer.Serialize(utf8Json, value, inputType, ResolveOptionsInstance(options)); return Task.CompletedTask; } - public override Task SerializeWrapper(Stream stream, T value, JsonTypeInfo jsonTypeInfo) + public override Task SerializeWrapper(Stream stream, T value, JsonTypeInfo jsonTypeInfo, CancellationToken cancellationToken = default) { JsonSerializer.Serialize(stream, value, jsonTypeInfo); return Task.CompletedTask; } - public override Task SerializeWrapper(Stream stream, object value, JsonTypeInfo jsonTypeInfo) + public override Task SerializeWrapper(Stream stream, object value, JsonTypeInfo jsonTypeInfo, CancellationToken cancellationToken = default) { JsonSerializer.Serialize(stream, value, jsonTypeInfo); return Task.CompletedTask; } - public override Task SerializeWrapper(Stream stream, object value, Type inputType, JsonSerializerContext context) + public override Task SerializeWrapper(Stream stream, object value, Type inputType, JsonSerializerContext context, CancellationToken cancellationToken = default) { JsonSerializer.Serialize(stream, value, inputType, context); return Task.CompletedTask; } - public override Task DeserializeWrapper(Stream utf8Json, JsonSerializerOptions options = null) + public override Task DeserializeWrapper(Stream utf8Json, JsonSerializerOptions options = null, CancellationToken cancellationToken = default) { T result = JsonSerializer.Deserialize(ResolveReadStream(utf8Json), ResolveOptionsInstance(options)); return Task.FromResult(result); } - public override Task DeserializeWrapper(Stream utf8Json, Type returnType, JsonSerializerOptions options = null) + public override Task DeserializeWrapper(Stream utf8Json, Type returnType, JsonSerializerOptions options = null, CancellationToken cancellationToken = default) { object result = JsonSerializer.Deserialize(ResolveReadStream(utf8Json), returnType, ResolveOptionsInstance(options)); return Task.FromResult(result); } - public override Task DeserializeWrapper(Stream utf8Json, JsonTypeInfo jsonTypeInfo) + public override Task DeserializeWrapper(Stream utf8Json, JsonTypeInfo jsonTypeInfo, CancellationToken cancellationToken = default) { T result = JsonSerializer.Deserialize(ResolveReadStream(utf8Json), jsonTypeInfo); return Task.FromResult(result); } - public override Task DeserializeWrapper(Stream utf8Json, JsonTypeInfo jsonTypeInfo) + public override Task DeserializeWrapper(Stream utf8Json, JsonTypeInfo jsonTypeInfo, CancellationToken cancellationToken = default) { object result = JsonSerializer.Deserialize(ResolveReadStream(utf8Json), jsonTypeInfo); return Task.FromResult(result); } - public override Task DeserializeWrapper(Stream utf8Json, Type returnType, JsonSerializerContext context) + public override Task DeserializeWrapper(Stream utf8Json, Type returnType, JsonSerializerContext context, CancellationToken cancellationToken = default) { object result = JsonSerializer.Deserialize(ResolveReadStream(utf8Json), returnType, context); return Task.FromResult(result); @@ -999,29 +999,29 @@ public override async Task DeserializeWrapper(string json, Type type, Js return await JsonSerializer.DeserializeAsync(pipe.Reader, type, context); } - public override Task DeserializeWrapper(PipeReader utf8Json, Type returnType, JsonSerializerOptions? options = null) + public override Task DeserializeWrapper(PipeReader utf8Json, Type returnType, JsonSerializerOptions? options = null, CancellationToken cancellationToken = default) { - return JsonSerializer.DeserializeAsync(utf8Json, returnType, ResolveOptionsInstance(options)).AsTask(); + return JsonSerializer.DeserializeAsync(utf8Json, returnType, ResolveOptionsInstance(options), cancellationToken).AsTask(); } - public override Task DeserializeWrapper(PipeReader utf8Json, JsonSerializerOptions? options = null) + public override Task DeserializeWrapper(PipeReader utf8Json, JsonSerializerOptions? options = null, CancellationToken cancellationToken = default) { - return JsonSerializer.DeserializeAsync(utf8Json, ResolveOptionsInstance(options)).AsTask(); + return JsonSerializer.DeserializeAsync(utf8Json, ResolveOptionsInstance(options), cancellationToken).AsTask(); } - public override Task DeserializeWrapper(PipeReader utf8Json, Type returnType, JsonSerializerContext context) + public override Task DeserializeWrapper(PipeReader utf8Json, Type returnType, JsonSerializerContext context, CancellationToken cancellationToken = default) { - return JsonSerializer.DeserializeAsync(utf8Json, returnType, context).AsTask(); + return JsonSerializer.DeserializeAsync(utf8Json, returnType, context, cancellationToken).AsTask(); } - public override Task DeserializeWrapper(PipeReader utf8Json, JsonTypeInfo jsonTypeInfo) + public override Task DeserializeWrapper(PipeReader utf8Json, JsonTypeInfo jsonTypeInfo, CancellationToken cancellationToken = default) { - return JsonSerializer.DeserializeAsync(utf8Json, jsonTypeInfo).AsTask(); + return JsonSerializer.DeserializeAsync(utf8Json, jsonTypeInfo, cancellationToken).AsTask(); } - public override Task DeserializeWrapper(PipeReader utf8Json, JsonTypeInfo jsonTypeInfo) + public override Task DeserializeWrapper(PipeReader utf8Json, JsonTypeInfo jsonTypeInfo, CancellationToken cancellationToken = default) { - return JsonSerializer.DeserializeAsync(utf8Json, jsonTypeInfo).AsTask(); + return JsonSerializer.DeserializeAsync(utf8Json, jsonTypeInfo, cancellationToken).AsTask(); } public override async Task SerializeWrapper(object value, Type inputType, JsonSerializerOptions options = null) @@ -1079,63 +1079,63 @@ public override async Task SerializeWrapper(object value, JsonTypeInfo j return stringResult; } - public override async Task SerializeWrapper(PipeWriter utf8Json, object value, Type inputType, JsonSerializerOptions? options = null) + public override async Task SerializeWrapper(PipeWriter utf8Json, object value, Type inputType, JsonSerializerOptions? options = null, CancellationToken cancellationToken = default) { try { - await JsonSerializer.SerializeAsync(utf8Json, value, inputType, ResolveOptionsInstance(options)); + await JsonSerializer.SerializeAsync(utf8Json, value, inputType, ResolveOptionsInstance(options), cancellationToken); } finally { - await utf8Json.FlushAsync(); + await utf8Json.FlushAsync(cancellationToken); } } - public override async Task SerializeWrapper(PipeWriter utf8Json, T value, JsonSerializerOptions? options = null) + public override async Task SerializeWrapper(PipeWriter utf8Json, T value, JsonSerializerOptions? options = null, CancellationToken cancellationToken = default) { try { - await JsonSerializer.SerializeAsync(utf8Json, value, ResolveOptionsInstance(options)); + await JsonSerializer.SerializeAsync(utf8Json, value, ResolveOptionsInstance(options), cancellationToken); } finally { - await utf8Json.FlushAsync(); + await utf8Json.FlushAsync(cancellationToken); } } - public override async Task SerializeWrapper(PipeWriter utf8Json, object value, Type inputType, JsonSerializerContext context) + public override async Task SerializeWrapper(PipeWriter utf8Json, object value, Type inputType, JsonSerializerContext context, CancellationToken cancellationToken = default) { try { - await JsonSerializer.SerializeAsync(utf8Json, value, inputType, context); + await JsonSerializer.SerializeAsync(utf8Json, value, inputType, context, cancellationToken); } finally { - await utf8Json.FlushAsync(); + await utf8Json.FlushAsync(cancellationToken); } } - public override async Task SerializeWrapper(PipeWriter utf8Json, T value, JsonTypeInfo jsonTypeInfo) + public override async Task SerializeWrapper(PipeWriter utf8Json, T value, JsonTypeInfo jsonTypeInfo, CancellationToken cancellationToken = default) { try { - await JsonSerializer.SerializeAsync(utf8Json, value, jsonTypeInfo); + await JsonSerializer.SerializeAsync(utf8Json, value, jsonTypeInfo, cancellationToken); } finally { - await utf8Json.FlushAsync(); + await utf8Json.FlushAsync(cancellationToken); } } - public override async Task SerializeWrapper(PipeWriter utf8Json, object value, JsonTypeInfo jsonTypeInfo) + public override async Task SerializeWrapper(PipeWriter utf8Json, object value, JsonTypeInfo jsonTypeInfo, CancellationToken cancellationToken = default) { try { - await JsonSerializer.SerializeAsync(utf8Json, value, jsonTypeInfo); + await JsonSerializer.SerializeAsync(utf8Json, value, jsonTypeInfo, cancellationToken); } finally { - await utf8Json.FlushAsync(); + await utf8Json.FlushAsync(cancellationToken); } } @@ -1163,13 +1163,13 @@ public override IAsyncEnumerable DeserializeAsyncEnumerable(Stream utf8Jso return JsonSerializer.DeserializeAsyncEnumerable(pipeReader, topLevelValues, ResolveOptionsInstance(options), cancellationToken); } - public override async Task SerializeWrapper(Stream stream, object value, Type inputType, JsonSerializerOptions? options = null) + public override async Task SerializeWrapper(Stream stream, object value, Type inputType, JsonSerializerOptions? options = null, CancellationToken cancellationToken = default) { PipeWriter writer = PipeWriter.Create(stream, new StreamPipeWriterOptions(minimumBufferSize: ResolveOptionsInstance(options)?.DefaultBufferSize ?? -1, leaveOpen: true)); try { - await JsonSerializer.SerializeAsync(writer, value, inputType, ResolveOptionsInstance(options)); + await JsonSerializer.SerializeAsync(writer, value, inputType, ResolveOptionsInstance(options), cancellationToken); } finally { @@ -1177,13 +1177,13 @@ public override async Task SerializeWrapper(Stream stream, object value, Type in } } - public override async Task SerializeWrapper(Stream stream, T value, JsonSerializerOptions? options = null) + public override async Task SerializeWrapper(Stream stream, T value, JsonSerializerOptions? options = null, CancellationToken cancellationToken = default) { PipeWriter writer = PipeWriter.Create(stream, new StreamPipeWriterOptions(minimumBufferSize: ResolveOptionsInstance(options)?.DefaultBufferSize ?? -1, leaveOpen: true)); try { - await JsonSerializer.SerializeAsync(writer, value, ResolveOptionsInstance(options)); + await JsonSerializer.SerializeAsync(writer, value, ResolveOptionsInstance(options), cancellationToken); } finally { @@ -1191,12 +1191,12 @@ public override async Task SerializeWrapper(Stream stream, T value, JsonSeria } } - public override async Task SerializeWrapper(Stream stream, object value, Type inputType, JsonSerializerContext context) + public override async Task SerializeWrapper(Stream stream, object value, Type inputType, JsonSerializerContext context, CancellationToken cancellationToken = default) { PipeWriter writer = PipeWriter.Create(stream, new StreamPipeWriterOptions(leaveOpen: true)); try { - await JsonSerializer.SerializeAsync(writer, value, inputType, context); + await JsonSerializer.SerializeAsync(writer, value, inputType, context, cancellationToken); } finally { @@ -1204,12 +1204,12 @@ public override async Task SerializeWrapper(Stream stream, object value, Type in } } - public override async Task SerializeWrapper(Stream stream, T value, JsonTypeInfo jsonTypeInfo) + public override async Task SerializeWrapper(Stream stream, T value, JsonTypeInfo jsonTypeInfo, CancellationToken cancellationToken = default) { PipeWriter writer = PipeWriter.Create(stream, new StreamPipeWriterOptions(leaveOpen: true)); try { - await JsonSerializer.SerializeAsync(writer, value, jsonTypeInfo); + await JsonSerializer.SerializeAsync(writer, value, jsonTypeInfo, cancellationToken); } finally { @@ -1217,12 +1217,12 @@ public override async Task SerializeWrapper(Stream stream, T value, JsonTypeI } } - public override async Task SerializeWrapper(Stream stream, object value, JsonTypeInfo jsonTypeInfo) + public override async Task SerializeWrapper(Stream stream, object value, JsonTypeInfo jsonTypeInfo, CancellationToken cancellationToken = default) { PipeWriter writer = PipeWriter.Create(stream, new StreamPipeWriterOptions(leaveOpen: true)); try { - await JsonSerializer.SerializeAsync(writer, value, jsonTypeInfo); + await JsonSerializer.SerializeAsync(writer, value, jsonTypeInfo, cancellationToken); } finally { @@ -1230,36 +1230,36 @@ public override async Task SerializeWrapper(Stream stream, object value, JsonTyp } } - public override Task DeserializeWrapper(Stream utf8Json, Type returnType, JsonSerializerOptions? options = null) + public override Task DeserializeWrapper(Stream utf8Json, Type returnType, JsonSerializerOptions? options = null, CancellationToken cancellationToken = default) { var pipeReader = PipeReader.Create(utf8Json, new StreamPipeReaderOptions(leaveOpen: true, bufferSize: ResolveOptionsInstance(options)?.DefaultBufferSize ?? -1)); - return JsonSerializer.DeserializeAsync(pipeReader, returnType, ResolveOptionsInstance(options)).AsTask(); + return JsonSerializer.DeserializeAsync(pipeReader, returnType, ResolveOptionsInstance(options), cancellationToken).AsTask(); } - public override Task DeserializeWrapper(Stream utf8Json, JsonSerializerOptions? options = null) + public override Task DeserializeWrapper(Stream utf8Json, JsonSerializerOptions? options = null, CancellationToken cancellationToken = default) { var pipeReader = PipeReader.Create(utf8Json, new StreamPipeReaderOptions(leaveOpen: true, bufferSize: ResolveOptionsInstance(options)?.DefaultBufferSize ?? -1)); - return JsonSerializer.DeserializeAsync(pipeReader, ResolveOptionsInstance(options)).AsTask(); + return JsonSerializer.DeserializeAsync(pipeReader, ResolveOptionsInstance(options), cancellationToken).AsTask(); } - public override Task DeserializeWrapper(Stream utf8Json, Type returnType, JsonSerializerContext context) + public override Task DeserializeWrapper(Stream utf8Json, Type returnType, JsonSerializerContext context, CancellationToken cancellationToken = default) { var pipeReader = PipeReader.Create(utf8Json, new StreamPipeReaderOptions(leaveOpen: true)); - return JsonSerializer.DeserializeAsync(pipeReader, returnType, context).AsTask(); + return JsonSerializer.DeserializeAsync(pipeReader, returnType, context, cancellationToken).AsTask(); } - public override Task DeserializeWrapper(Stream utf8Json, JsonTypeInfo jsonTypeInfo) + public override Task DeserializeWrapper(Stream utf8Json, JsonTypeInfo jsonTypeInfo, CancellationToken cancellationToken = default) { var pipeReader = PipeReader.Create(utf8Json, new StreamPipeReaderOptions(leaveOpen: true)); - return JsonSerializer.DeserializeAsync(pipeReader, jsonTypeInfo).AsTask(); + return JsonSerializer.DeserializeAsync(pipeReader, jsonTypeInfo, cancellationToken).AsTask(); } - public override Task DeserializeWrapper(Stream utf8Json, JsonTypeInfo jsonTypeInfo) + public override Task DeserializeWrapper(Stream utf8Json, JsonTypeInfo jsonTypeInfo, CancellationToken cancellationToken = default) { var pipeReader = PipeReader.Create(utf8Json, new StreamPipeReaderOptions(leaveOpen: true)); - return JsonSerializer.DeserializeAsync(pipeReader, jsonTypeInfo).AsTask(); + return JsonSerializer.DeserializeAsync(pipeReader, jsonTypeInfo, cancellationToken).AsTask(); } } }