< Summary

Information
Line coverage
61%
Covered lines: 55
Uncovered lines: 35
Coverable lines: 90
Total lines: 200
Line coverage: 61.1%
Branch coverage
50%
Covered branches: 14
Total branches: 28
Branch coverage: 50%
Method coverage

Feature is only available for sponsors

Upgrade to PRO version

Metrics

MethodBranch coverage Cyclomatic complexity NPath complexity Sequence coverage
.ctor(...)100%11100%
ReadAsync()75%88100%
Read(...)0%440%
Advance(...)30%101032%
GetReader(...)100%11100%
ProcessReadBytes()83.33%6675%
Dispose()100%11100%

File(s)

C:\h\w\B31A098C\w\BB5A0A33\e\runtime-utils\Runner\runtime\src\libraries\System.Text.Json\src\System\Text\Json\Serialization\StreamReadBufferState.cs

#LineLine coverage
 1// Licensed to the .NET Foundation under one or more agreements.
 2// The .NET Foundation licenses this file to you under the MIT license.
 3
 4using System.Buffers;
 5using System.Diagnostics;
 6using System.IO;
 7using System.Runtime.InteropServices;
 8using System.Threading;
 9using System.Threading.Tasks;
 10
 11namespace System.Text.Json.Serialization
 12{
 13    [StructLayout(LayoutKind.Auto)]
 14    internal struct StreamReadBufferState : IReadBufferState<StreamReadBufferState, Stream>
 15    {
 16        private byte[] _buffer;
 17        private byte _offset; // Read bytes offset typically used when skipping the UTF-8 BOM.
 18        private int _count; // Number of read bytes yet to be consumed by the serializer.
 19        private int _maxCount; // Number of bytes we need to clear before returning the buffer.
 20        private bool _isFirstBlock;
 21        private bool _isFinalBlock;
 22
 23        // An "unsuccessful read" in this context refers to a buffer read operation that
 24        // wasn't sufficient to advance the reader to the next token. This occurs primarily
 25        // when consuming large JSON strings (which don't support streaming today) but is
 26        // also possible with other token types such as numbers, booleans, or nulls.
 27        //
 28        // The JsonSerializer.DeserializeAsyncEnumerable methods employ a special buffering
 29        // strategy where rather than attempting to fill the entire buffer, the deserializer
 30        // will be invoked as soon as the first chunk of data is read from the stream.
 31        // This is to ensure liveness: data should be surfaced on the IAE as soon as they
 32        // are streamed from the server. On the other hand, this can create performance
 33        // problems in cases where the underlying stream uses extremely fine-grained buffering.
 34        // For this reason, we employ a threshold that will revert to buffer filling once crossed.
 35        // The counter is reset to zero whenever the JSON reader has been advanced successfully.
 36        //
 37        // The threshold is set to 5 unsuccessful reads. This is a relatively conservative threshold
 38        // but should still make fallback unlikely in most scenarios. It should ensure that fallback
 39        // isn't triggered in null or boolean tokens even in the worst-case scenario where they are
 40        // streamed one byte at a time.
 41        private const int UnsuccessfulReadCountThreshold = 5;
 42        private int _unsuccessfulReadCount;
 43
 44        public StreamReadBufferState(int initialBufferSize)
 2796245        {
 2796246            _buffer = ArrayPool<byte>.Shared.Rent(Math.Max(initialBufferSize, JsonConstants.Utf8Bom.Length));
 2796247            _maxCount = _count = _offset = 0;
 2796248            _isFirstBlock = true;
 2796249            _isFinalBlock = false;
 2796250        }
 51
 2804952        public readonly bool IsFinalBlock => _isFinalBlock;
 53
 54#if DEBUG
 17455        public readonly ReadOnlySequence<byte> Bytes => new(_buffer.AsMemory(_offset, _count));
 56#endif
 57
 58        /// <summary>
 59        /// Read from the stream until either our buffer is filled or we hit EOF.
 60        /// Calling ReadCore is relatively expensive, so we minimize the number of times
 61        /// we need to call it.
 62        /// </summary>
 63        public readonly async ValueTask<StreamReadBufferState> ReadAsync(Stream stream,
 64            CancellationToken cancellationToken,
 65            bool fillBuffer = true)
 2796266        {
 67            // Since mutable structs don't work well with async state machines,
 68            // make all updates on a copy which is returned once complete.
 2796269            StreamReadBufferState bufferState = this;
 70
 2796271            int minBufferCount = fillBuffer || _unsuccessfulReadCount > UnsuccessfulReadCountThreshold ? bufferState._bu
 72            do
 5592473            {
 5592474                int bytesRead = await stream.ReadAsync(bufferState._buffer.AsMemory(bufferState._count), cancellationTok
 75
 5592476                if (bytesRead == 0)
 2796277                {
 2796278                    bufferState._isFinalBlock = true;
 2796279                    break;
 80                }
 81
 2796282                bufferState._count += bytesRead;
 2796283            }
 2796284            while (bufferState._count < minBufferCount);
 85
 2796286            bufferState.ProcessReadBytes();
 2796287            return bufferState;
 2796288        }
 89
 90        /// <summary>
 91        /// Read from the stream until either our buffer is filled or we hit EOF.
 92        /// Calling ReadCore is relatively expensive, so we minimize the number of times
 93        /// we need to call it.
 94        /// </summary>
 95        public void Read(Stream stream)
 096        {
 97            do
 098            {
 099                int bytesRead = stream.Read(
 0100#if NET
 0101                    _buffer.AsSpan(_count));
 102#else
 103                    _buffer, _count, _buffer.Length - _count);
 104#endif
 105
 0106                if (bytesRead == 0)
 0107                {
 0108                    _isFinalBlock = true;
 0109                    break;
 110                }
 111
 0112                _count += bytesRead;
 0113            }
 0114            while (_count < _buffer.Length);
 115
 0116            ProcessReadBytes();
 0117        }
 118
 119        /// <summary>
 120        /// Advances the buffer in anticipation of a subsequent read operation.
 121        /// </summary>
 122        public void Advance(long bytesConsumed)
 27962123        {
 27962124            Debug.Assert(bytesConsumed <= _count);
 125
 27962126            int bytesConsumedInt = (int)bytesConsumed;
 127
 27962128            _unsuccessfulReadCount = bytesConsumedInt == 0 ? _unsuccessfulReadCount + 1 : 0;
 27962129            _count -= bytesConsumedInt;
 130
 27962131            if (!_isFinalBlock)
 0132            {
 133                // Check if we need to shift or expand the buffer because there wasn't enough data to complete deseriali
 0134                if ((uint)_count > ((uint)_buffer.Length / 2))
 0135                {
 136                    // We have less than half the buffer available, double the buffer size.
 0137                    byte[] oldBuffer = _buffer;
 0138                    int oldMaxCount = _maxCount;
 0139                    byte[] newBuffer = ArrayPool<byte>.Shared.Rent((_buffer.Length < (int.MaxValue / 2)) ? _buffer.Lengt
 140
 141                    // Copy the unprocessed data to the new buffer while shifting the processed bytes.
 0142                    Buffer.BlockCopy(oldBuffer, _offset + bytesConsumedInt, newBuffer, 0, _count);
 0143                    _buffer = newBuffer;
 0144                    _maxCount = _count;
 145
 146                    // Clear and return the old buffer
 0147                    new Span<byte>(oldBuffer, 0, oldMaxCount).Clear();
 0148                    ArrayPool<byte>.Shared.Return(oldBuffer);
 0149                }
 0150                else if (_count != 0)
 0151                {
 152                    // Shift the processed bytes to the beginning of buffer to make more room.
 0153                    Buffer.BlockCopy(_buffer, _offset + bytesConsumedInt, _buffer, 0, _count);
 0154                }
 0155            }
 156
 27962157            _offset = 0;
 27962158        }
 159
 160        public void GetReader(JsonReaderState jsonReaderState, out Utf8JsonReader reader)
 27962161        {
 27962162            reader = new Utf8JsonReader(
 27962163                _buffer.AsSpan(_offset, _count),
 27962164                IsFinalBlock,
 27962165                jsonReaderState);
 27962166        }
 167
 168        private void ProcessReadBytes()
 27962169        {
 27962170            if (_count > _maxCount)
 27962171            {
 27962172                _maxCount = _count;
 27962173            }
 174
 27962175            if (_isFirstBlock)
 27962176            {
 27962177                _isFirstBlock = false;
 178
 179                // Handle the UTF-8 BOM if present
 27962180                Debug.Assert(_buffer.Length >= JsonConstants.Utf8Bom.Length);
 27962181                if (_buffer.AsSpan(0, _count).StartsWith(JsonConstants.Utf8Bom))
 0182                {
 0183                    _offset = (byte)JsonConstants.Utf8Bom.Length;
 0184                    _count -= JsonConstants.Utf8Bom.Length;
 0185                }
 27962186            }
 27962187        }
 188
 189        public void Dispose()
 27962190        {
 191            // Clear only what we used and return the buffer to the pool
 27962192            new Span<byte>(_buffer, 0, _maxCount).Clear();
 193
 27962194            byte[] toReturn = _buffer;
 27962195            _buffer = null!;
 196
 27962197            ArrayPool<byte>.Shared.Return(toReturn);
 27962198        }
 199    }
 200}