Exemplo n.º 1
0
        public void TryGetSkipDoesNotCrossPastEnd()
        {
            var bufferSegment1 = new BufferSegment <byte>(new byte[100]);
            BufferSegment <byte> bufferSegment2 = bufferSegment1.Append(new byte[0]);
            BufferSegment <byte> bufferSegment3 = bufferSegment2.Append(new byte[0]);
            BufferSegment <byte> bufferSegment4 = bufferSegment3.Append(new byte[100]);

            var buffer          = new ReadOnlySequence <byte>(bufferSegment1, 0, bufferSegment2, 0);
            SequencePosition c1 = buffer.GetPosition(buffer.Start, 0);

            Assert.Equal(0, c1.GetInteger());
            Assert.Equal(bufferSegment1, c1.GetObject());

            ReadOnlyMemory <byte> data;

            Assert.True(buffer.TryGet(ref c1, out data, advance: true));

            Assert.Equal(0, c1.GetInteger());
            Assert.Equal(null, c1.GetObject());

            Assert.False(buffer.TryGet(ref c1, out data, advance: true));
        }
        public void GetPositionDoesNotCrossOutsideBuffer()
        {
            var bufferSegment1 = new BufferSegment <byte>(new byte[100]);
            BufferSegment <byte> bufferSegment2 = bufferSegment1.Append(new byte[100]);
            BufferSegment <byte> bufferSegment3 = bufferSegment2.Append(new byte[0]);

            var buffer = new ReadOnlySequence <byte>(bufferSegment1, 0, bufferSegment2, 100);

            SequencePosition c1 = buffer.GetPosition(buffer.Start, 200);

            Assert.Equal(100, c1.GetInteger());
            Assert.Equal(bufferSegment2, c1.GetObject());
        }
Exemplo n.º 3
0
        private ReadOnlySequence <T> SliceImpl(SequencePosition begin, SequencePosition end)
        {
            // In this method we reset high order bits from indices
            // of positions that were passed in
            // and apply type bits specific for current ReadOnlySequence type

            return(new ReadOnlySequence <T>(
                       begin.GetObject(),
                       begin.GetInteger() & IndexBitMask | (Start.GetInteger() & ~IndexBitMask),
                       end.GetObject(),
                       end.GetInteger() & IndexBitMask | (End.GetInteger() & ~IndexBitMask)
                       ));
        }
Exemplo n.º 4
0
        internal SequencePosition Seek(SequencePosition start, SequencePosition end, int count, bool checkEndReachable = true)
        {
            int          startIndex = start.GetInteger();
            int          endIndex   = end.GetInteger();
            SequenceType type       = GetSequenceType();

            startIndex = GetIndex(startIndex);
            endIndex   = GetIndex(endIndex);

            switch (type)
            {
            case SequenceType.MemoryList:
                if (start.GetObject() == end.GetObject() && endIndex - startIndex >= count)
                {
                    return(new SequencePosition(start.GetObject(), startIndex + count));
                }
                return(SeekMultiSegment((IMemoryList <byte>)start.GetObject(), startIndex, (IMemoryList <byte>)end.GetObject(), endIndex, count, checkEndReachable));

            case SequenceType.OwnedMemory:
            case SequenceType.Array:
                if (start.GetObject() != end.GetObject())
                {
                    ThrowHelper.ThrowInvalidOperationException_EndPositionNotReached();
                }

                if (endIndex - startIndex >= count)
                {
                    return(new SequencePosition(start.GetObject(), startIndex + count));
                }

                ThrowHelper.ThrowArgumentOutOfRangeException_CountOutOfRange();
                return(default);

            default:
                ThrowHelper.ThrowInvalidOperationException_UnexpectedSegmentType();
                return(default);
            }
        }
Exemplo n.º 5
0
        public void SeekSkipsEmptySegments()
        {
            var bufferSegment1 = new BufferSegment <byte>(new byte[100]);
            BufferSegment <byte> bufferSegment2 = bufferSegment1.Append(new byte[0]);
            BufferSegment <byte> bufferSegment3 = bufferSegment2.Append(new byte[0]);
            BufferSegment <byte> bufferSegment4 = bufferSegment3.Append(new byte[100]);

            var buffer = new ReadOnlySequence <byte>(bufferSegment1, 0, bufferSegment4, 100);

            SequencePosition c1 = buffer.GetPosition(buffer.Start, 100);

            Assert.Equal(0, c1.GetInteger());
            Assert.Equal(bufferSegment4, c1.GetObject());
        }
Exemplo n.º 6
0
        public void SeekEmptySkipDoesNotCrossPastEnd()
        {
            var bufferSegment1 = new BufferSegment <T>(new T[100]);
            BufferSegment <T> bufferSegment2 = bufferSegment1.Append(new T[0]);
            BufferSegment <T> bufferSegment3 = bufferSegment2.Append(new T[0]);
            BufferSegment <T> bufferSegment4 = bufferSegment3.Append(new T[100]);

            var buffer = new ReadOnlySequence <T>(bufferSegment1, 0, bufferSegment2, 0);

            SequencePosition c1 = buffer.GetPosition(100);

            Assert.Equal(0, c1.GetInteger());
            Assert.Equal(bufferSegment2, c1.GetObject());

            c1 = buffer.GetPosition(100, buffer.Start);

            Assert.Equal(0, c1.GetInteger());
            Assert.Equal(bufferSegment2, c1.GetObject());

            // Go out of bounds for segment
            Assert.Throws <ArgumentOutOfRangeException>(() => c1 = buffer.GetPosition(150, buffer.Start));
            Assert.Throws <ArgumentOutOfRangeException>(() => c1 = buffer.GetPosition(250, buffer.Start));
        }
Exemplo n.º 7
0
            /// <summary>
            /// Removes all elements from the sequence from its beginning to the specified position,
            /// considering that data to have been fully processed.
            /// </summary>
            /// <param name="position">
            /// The position of the first element that has not yet been processed.
            /// This is typically <see cref="ReadOnlySequence{T}.End"/> after reading all elements from that instance.
            /// </param>
            public void AdvanceTo(SequencePosition position)
            {
                var firstSegment = (SequenceSegment)position.GetObject();
                int firstIndex   = position.GetInteger();

                // Before making any mutations, confirm that the block specified belongs to this sequence.
                var current = this.first;

                while (current != firstSegment && current != null)
                {
                    current = current.Next;
                }

                if (current == null)
                {
                    ThrowCurrentNull();
                }
                void ThrowCurrentNull() => throw new ArgumentException("Position does not represent a valid position in this sequence.", nameof(position));

                // Also confirm that the position is not a prior position in the block.
                if (firstIndex < current.Start)
                {
                    ThrowEarlierPosition();
                }
                void ThrowEarlierPosition() => throw new ArgumentException("Position must not be earlier than current position.", nameof(position));

                // Now repeat the loop, performing the mutations.
                current = this.first;
                while (current != firstSegment)
                {
                    var next = current.Next;
                    current.ResetMemory();
                    current = next;
                }

                firstSegment.AdvanceTo(firstIndex);

                if (firstSegment.Length == 0)
                {
                    firstSegment = this.RecycleAndGetNext(firstSegment);
                }

                this.first = firstSegment;

                if (this.first == null)
                {
                    this.last = null;
                }
            }
Exemplo n.º 8
0
        static internal void GetFirstSpan <T>(this ReadOnlySequence <T> sequence, out ReadOnlySpan <T> first, out SequencePosition next)
        {
            first = default;
            next  = default;
            SequencePosition start = sequence.Start;
            int    startIndex      = start.GetInteger();
            object startObject     = start.GetObject();

            if (startObject != null)
            {
                SequencePosition end     = sequence.End;
                int  endIndex            = end.GetInteger();
                bool hasMultipleSegments = startObject != end.GetObject();

                if (startIndex >= 0)
                {
                    if (endIndex >= 0)
                    {
                        // Positive start and end index == ReadOnlySequenceSegment<T>
                        ReadOnlySequenceSegment <T> segment = (ReadOnlySequenceSegment <T>)startObject;
                        next  = new SequencePosition(segment.Next, 0);
                        first = segment.Memory.Span;
                        if (hasMultipleSegments)
                        {
                            first = first.Slice(startIndex);
                        }
                        else
                        {
                            first = first.Slice(startIndex, endIndex - startIndex);
                        }
                    }
                    else
                    {
                        // Positive start and negative end index == T[]
                        if (hasMultipleSegments)
                        {
                            ThrowHelper.ThrowInvalidOperationException_EndPositionNotReached();
                        }

                        first = new ReadOnlySpan <T>((T[])startObject, startIndex, (endIndex & ReadOnlySequence.IndexBitMask) - startIndex);
                    }
                }
                else
                {
                    first = GetFirstSpanSlow <T>(startObject, startIndex, endIndex, hasMultipleSegments);
                }
            }
        }
Exemplo n.º 9
0
        public ValueTask <ProtocolReadResult <TReadMessage> > ReadAsync <TReadMessage>(IMessageReader <TReadMessage> reader, int?maximumMessageSize, CancellationToken cancellationToken = default)
        {
            if (_disposed)
            {
                throw new ObjectDisposedException(GetType().Name);
            }

            if (_hasMessage)
            {
                throw new InvalidOperationException($"{nameof(Advance)} must be called before calling {nameof(ReadAsync)}");
            }

            // If this is the very first read, then make it go async since we have no data
            if (_consumed.GetObject() == null)
            {
                return(DoAsyncRead(maximumMessageSize, reader, cancellationToken));
            }

            // We have a buffer, test to see if there's any message left in the buffer
            if (TryParseMessage(maximumMessageSize, reader, _buffer, out var protocolMessage))
            {
                _hasMessage = true;
                return(new ValueTask <ProtocolReadResult <TReadMessage> >(new ProtocolReadResult <TReadMessage>(protocolMessage, _isCanceled, isCompleted: false)));
            }
            else
            {
                // We couldn't parse the message so advance the input so we can read
                _reader.AdvanceTo(_consumed, _examined);

                // Reset the state since we're done consuming this buffer
                _buffer   = default;
                _consumed = default;
                _examined = default;
            }

            if (_isCompleted)
            {
                _consumed = default;
                _examined = default;

                // If we're complete then short-circuit
                if (!_buffer.IsEmpty)
                {
                    throw new InvalidDataException("Connection terminated while reading a message.");
                }

                return(new ValueTask <ProtocolReadResult <TReadMessage> >(new ProtocolReadResult <TReadMessage>(default, _isCanceled, _isCompleted)));
Exemplo n.º 10
0
        /// <summary>
        /// Removes all elements from the sequence from its beginning to the specified position,
        /// considering that data to have been fully processed.
        /// </summary>
        /// <param name="position">
        /// The position of the first element that has not yet been processed.
        /// This is typically <see cref="ReadOnlySequence{T}.End"/> after reading all elements from that instance.
        /// </param>
        public void AdvanceTo(SequencePosition position)
        {
            var firstSegment = (SequenceSegment?)position.GetObject();

            if (firstSegment == null)
            {
                // Emulate PipeReader behavior which is to just return for default(SequencePosition)
                return;
            }

            if (ReferenceEquals(firstSegment, SequenceSegment.Empty) && this.Length == 0)
            {
                // We were called with our own empty buffer segment.
                return;
            }

            int firstIndex = position.GetInteger();

            // Before making any mutations, confirm that the block specified belongs to this sequence.
            var current = this.first;

            while (current != firstSegment && current != null)
            {
                current = current.Next;
            }

            Requires.Argument(current != null, nameof(position), "Position does not represent a valid position in this sequence.");

            // Also confirm that the position is not a prior position in the block.
            Requires.Argument(firstIndex >= current.Start, nameof(position), "Position must not be earlier than current position.");

            // Now repeat the loop, performing the mutations.
            current = this.first;
            while (current != firstSegment)
            {
                current = this.RecycleAndGetNext(current !);
            }

            firstSegment.AdvanceTo(firstIndex);

            this.first = firstSegment.Length == 0 ? this.RecycleAndGetNext(firstSegment) : firstSegment;

            if (this.first == null)
            {
                this.last = null;
            }
        }
Exemplo n.º 11
0
        public void CheckEndReachableDoesNotCrossPastEnd()
        {
            var bufferSegment1 = new BufferSegment <byte>(new byte[100]);
            BufferSegment <byte> bufferSegment2 = bufferSegment1.Append(new byte[100]);
            BufferSegment <byte> bufferSegment3 = bufferSegment2.Append(new byte[100]);
            BufferSegment <byte> bufferSegment4 = bufferSegment3.Append(new byte[100]);

            var buffer = new ReadOnlySequence <byte>(bufferSegment1, 0, bufferSegment4, 100);

            SequencePosition c1 = buffer.GetPosition(buffer.Start, 200);

            Assert.Equal(0, c1.GetInteger());
            Assert.Equal(bufferSegment3, c1.GetObject());

            ReadOnlySequence <byte> seq = buffer.Slice(0, c1);

            Assert.Equal(200, seq.Length);
        }
Exemplo n.º 12
0
        public bool HasUnexaminedData(SequencePosition examined)
        {
            if (IsEmpty)
            {
                return(false);
            }

            var examinedObject  = examined.GetObject();
            var examinedInteger = examined.GetInteger();

            if (ReferenceEquals(examinedObject, _lastSegment) &&
                examinedInteger == _lastIndex)
            {
                return(false);
            }

            return(true);
        }
Exemplo n.º 13
0
        public ValueTask <ProtocolReadResult <TReadMessage> > ReadAsync <TReadMessage>(IMessageReader <TReadMessage> reader, CancellationToken cancellationToken = default)
        {
            if (_disposed)
            {
                ThrowHelper.ObjectDisposedException(GetType().Name);
            }

            if (_hasMessage)
            {
                ThrowHelper.MissedAdvance();
            }

            // If this is the very first read, then make it go async since we have no data
            if (_consumed.GetObject() == null)
            {
                return(DoAsyncRead(reader, cancellationToken));
            }

            // We have a buffer, test to see if there's any message left in the buffer
            if (TryParseMessage(reader, _buffer, out var protocolMessage))
            {
                _hasMessage = true;
                return(new ValueTask <ProtocolReadResult <TReadMessage> >(new ProtocolReadResult <TReadMessage>(protocolMessage, _isCanceled, isCompleted: false)));
            }
            else
            {
                // We couldn't parse the message so advance the input so we can read
                _reader.AdvanceTo(_consumed, _examined);
            }

            if (_isCompleted)
            {
                _consumed = default;
                _examined = default;

                // If we're complete then short-circuit
                if (!_buffer.IsEmpty)
                {
                    ThrowHelper.ConnectionTerminated();
                }

                return(new ValueTask <ProtocolReadResult <TReadMessage> >(new ProtocolReadResult <TReadMessage>(default, _isCanceled, _isCompleted)));
Exemplo n.º 14
0
        public void ConsumeTo(SequencePosition consumed)
        {
            var segment = (Segment)consumed.GetObject();
            var index   = consumed.GetInteger();

            if (segment == _endSegment && index == _endIndex)
            {
                // keep the last page; burn anything else
                _startSegment !.RecycleBefore(segment);
                _startSegment = _endSegment;
                _startIndex   = _endIndex = 0;
            }
            else
            {
                // discard any pages that we no longer need
                _startSegment !.RecycleBefore(segment);
                _startSegment = segment;
                _startIndex   = index;
            }
        }
Exemplo n.º 15
0
        public ReadOnlySequence <byte> Slice(SequencePosition markStart, SequencePosition markEnd)
        {
            if (start == null)
            {
                return(ReadOnlySequence <byte> .Empty);
            }

            var a = (_Segment)(markStart.GetObject() ?? start);
            var b = (_Segment)(markEnd.GetObject() ?? start);

            var pa = markStart.GetInteger();
            var pb = markEnd.GetInteger();

            if (a == b && pa == pb)
            {
                return(ReadOnlySequence <byte> .Empty);
            }

            return(new ReadOnlySequence <byte>(a, pa, b, pb));
        }
Exemplo n.º 16
0
        public SequencePosition GetPosition(SequencePosition origin, long offset)
        {
            if (offset < 0)
            {
                throw new InvalidOperationException("cannot seek backwards");
            }
            var node = (Node)origin.GetObject();

            while (offset-- > 0)
            {
                if (node != null)
                {
                    node = node._next;
                }
                else
                {
                    throw new ArgumentOutOfRangeException(nameof(offset));
                }
            }
            return(new SequencePosition(node, 0));
        }
Exemplo n.º 17
0
        /// <summary>
        /// Removes all elements from the sequence from its beginning to the specified position,
        /// considering that data to have been fully processed.
        /// </summary>
        /// <param name="position">
        /// The position of the first element that has not yet been processed.
        /// This is typically <see cref="ReadOnlySequence{T}.End"/> after reading all elements from that instance.
        /// </param>
        public void AdvanceTo(SequencePosition position)
        {
            var firstSegment = (SequenceSegment)position.GetObject();
            int firstIndex   = position.GetInteger();

            // Before making any mutations, confirm that the block specified belongs to this sequence.
            var current = this.first;

            while (current != firstSegment && current != null)
            {
                current = current.Next;
            }

            Requires.Argument(current != null, nameof(position), "Position does not represent a valid position in this sequence.");

            // Also confirm that the position is not a prior position in the block.
            Requires.Argument(firstIndex >= current.Start, nameof(position), "Position must not be earlier than current position.");

            // Now repeat the loop, performing the mutations.
            current = this.first;
            while (current != firstSegment)
            {
                current = this.RecycleAndGetNext(current);
            }

            firstSegment.AdvanceTo(firstIndex);

            if (firstSegment.Length == 0)
            {
                firstSegment = this.RecycleAndGetNext(firstSegment);
            }

            this.first = firstSegment;

            if (this.first == null)
            {
                this.last = null;
            }
        }
Exemplo n.º 18
0
        public async Task ThrowingFromStreamCallsAdvanceToWithStartOfLastReadResult(int throwAfterNWrites)
        {
            var wrappedPipeReader = new TestPipeReader(PipeReader);

            var  stream = new ThrowAfterNWritesStream(throwAfterNWrites);
            Task task   = wrappedPipeReader.CopyToAsync(stream);

            Pipe.Writer.WriteEmpty(10);
            await Pipe.Writer.FlushAsync();

            // Write twice for the test case where the stream throws on the second write.
            Pipe.Writer.WriteEmpty(10);
            await Pipe.Writer.FlushAsync();

            await Assert.ThrowsAsync <InvalidOperationException>(() => task);

            SequencePosition startPosition = wrappedPipeReader.LastReadResult.Buffer.Start;

            Assert.NotNull(startPosition.GetObject());
            Assert.True(startPosition.Equals(wrappedPipeReader.LastConsumed));
            Assert.True(startPosition.Equals(wrappedPipeReader.LastExamined));
        }
Exemplo n.º 19
0
        private long GetLength(SequencePosition start, SequencePosition end)
        {
            int          startIndex = start.GetInteger();
            int          endIndex   = end.GetInteger();
            SequenceType type       = GetSequenceType();

            startIndex = GetIndex(startIndex);
            endIndex   = GetIndex(endIndex);

            switch (type)
            {
            case SequenceType.MemoryList:
                return(GetLength((IMemoryList <T>)start.GetObject(), startIndex, (IMemoryList <T>)end.GetObject(), endIndex));

            case SequenceType.OwnedMemory:
            case SequenceType.Array:
                return(endIndex - startIndex);

            default:
                ThrowHelper.ThrowInvalidOperationException_UnexpectedSegmentType();
                return(default);
            }
        }
Exemplo n.º 20
0
        public async Task PooledSegmentsDontAffectLastExaminedSegmentEmptyGapWithDifferentBlocks()
        {
            _pipe.Writer.WriteEmpty(_pool.MaxBufferSize);
            _pipe.Writer.WriteEmpty(_pool.MaxBufferSize);
            await _pipe.Writer.FlushAsync();

            ReadResult result = await _pipe.Reader.ReadAsync();

            // This gets the end of the first block
            SequencePosition endOfFirstBlock = result.Buffer.Slice(result.Buffer.Start, _pool.MaxBufferSize).End;
            // Start of the next block
            SequencePosition startOfSecondBlock = result.Buffer.GetPosition(_pool.MaxBufferSize);

            Assert.NotSame(endOfFirstBlock.GetObject(), startOfSecondBlock.GetObject());

            // This should return the first segment
            _pipe.Reader.AdvanceTo(startOfSecondBlock, endOfFirstBlock);

            // One block remaining
            Assert.Equal(4096, _pipe.Length);

            // This should use the segment that was returned
            _pipe.Writer.WriteEmpty(_pool.MaxBufferSize);
            await _pipe.Writer.FlushAsync();

            result = await _pipe.Reader.ReadAsync();

            _pipe.Reader.AdvanceTo(result.Buffer.Start);

            Assert.Equal(8192, _pipe.Length);

            result = await _pipe.Reader.ReadAsync();

            _pipe.Reader.AdvanceTo(result.Buffer.End);

            Assert.Equal(0, _pipe.Length);
        }
Exemplo n.º 21
0
        /// <inheritdoc />
        public override void AdvanceTo(SequencePosition consumed, SequencePosition examined)
        {
            ThrowIfCompleted();

            AdvanceTo((BufferSegment?)consumed.GetObject(), consumed.GetInteger(), (BufferSegment?)examined.GetObject(), examined.GetInteger());
        }
Exemplo n.º 22
0
        internal void AdvanceReader(SequencePosition consumed, SequencePosition examined)
        {
            BufferSegment returnStart = null;
            BufferSegment returnEnd   = null;

            Action continuation = null;

            lock (_sync)
            {
                var examinedEverything = false;
                if (examined.GetObject() == _commitHead)
                {
                    examinedEverything = _commitHead != null?examined.GetInteger() == _commitHeadIndex - _commitHead.Start : examined.GetInteger() == 0;
                }

                if (consumed.GetObject() != null)
                {
                    if (_readHead == null)
                    {
                        ThrowHelper.ThrowInvalidOperationException_AdvanceToInvalidCursor();
                        return;
                    }

                    var consumedSegment = (BufferSegment)consumed.GetObject();

                    returnStart = _readHead;
                    returnEnd   = consumedSegment;

                    // Check if we crossed _maximumSizeLow and complete backpressure
                    long consumedBytes = new ReadOnlySequence <byte>(returnStart, _readHeadIndex, consumedSegment, consumed.GetInteger()).Length;
                    long oldLength     = _length;
                    _length -= consumedBytes;

                    if (oldLength >= _resumeWriterThreshold &&
                        _length < _resumeWriterThreshold)
                    {
                        continuation = _writerAwaitable.Complete();
                    }

                    // Check if we consumed entire last segment
                    // if we are going to return commit head we need to check that there is no writing operation that
                    // might be using tailspace
                    if (consumed.GetInteger() == returnEnd.Length && _writingHead != returnEnd)
                    {
                        BufferSegment nextBlock = returnEnd.NextSegment;
                        if (_commitHead == returnEnd)
                        {
                            _commitHead      = nextBlock;
                            _commitHeadIndex = 0;
                        }

                        _readHead      = nextBlock;
                        _readHeadIndex = 0;
                        returnEnd      = nextBlock;
                    }
                    else
                    {
                        _readHead      = consumedSegment;
                        _readHeadIndex = consumed.GetInteger();
                    }
                }

                // We reset the awaitable to not completed if we've examined everything the producer produced so far
                // but only if writer is not completed yet
                if (examinedEverything && !_writerCompletion.IsCompleted)
                {
                    // Prevent deadlock where reader awaits new data and writer await backpressure
                    if (!_writerAwaitable.IsCompleted)
                    {
                        ThrowHelper.ThrowInvalidOperationException_BackpressureDeadlock();
                    }
                    _readerAwaitable.Reset();
                }

                while (returnStart != null && returnStart != returnEnd)
                {
                    returnStart.ResetMemory();
                    ReturnSegmentUnsynchronized(returnStart);
                    returnStart = returnStart.NextSegment;
                }

                _readingState.End();
            }

            TrySchedule(_writerScheduler, continuation);
        }
Exemplo n.º 23
0
        /// <inheritdoc />
        public override void AdvanceTo(SequencePosition consumed, SequencePosition examined)
        {
            ThrowIfCompleted();

            if (_readHead == null || _readTail == null)
            {
                ThrowHelper.ThrowInvalidOperationException_NoDataRead();
            }

            AdvanceTo((BufferSegment)consumed.GetObject(), consumed.GetInteger(), (BufferSegment)examined.GetObject(), examined.GetInteger());
        }
 public static SequencePosition Add(this SequencePosition x, int y)
 => new SequencePosition(x.GetObject(), x.GetInteger() + y);
Exemplo n.º 25
0
        internal bool TryGetBuffer(SequencePosition start, SequencePosition end, out ReadOnlyMemory <T> data, out SequencePosition next)
        {
            if (start.GetObject() == null)
            {
                data = default;
                next = default;
                return(false);
            }

            int          startIndex = start.GetInteger();
            int          endIndex   = end.GetInteger();
            SequenceType type       = GetSequenceType();

            startIndex = GetIndex(startIndex);
            endIndex   = GetIndex(endIndex);

            switch (type)
            {
            case SequenceType.MemoryList:
                var        segment             = (IMemoryList <T>)start.GetObject();
                Memory <T> bufferSegmentMemory = segment.Memory;
                int        currentEndIndex     = bufferSegmentMemory.Length;

                if (segment == end.GetObject())
                {
                    currentEndIndex = endIndex;
                    next            = default;
                }
                else
                {
                    IMemoryList <T> nextSegment = segment.Next;
                    if (nextSegment == null)
                    {
                        if (end.GetObject() != null)
                        {
                            ThrowHelper.ThrowInvalidOperationException_EndPositionNotReached();
                        }

                        next = default;
                    }
                    else
                    {
                        next = new SequencePosition(nextSegment, 0);
                    }
                }

                data = bufferSegmentMemory.Slice(startIndex, currentEndIndex - startIndex);
                return(true);

            case SequenceType.OwnedMemory:
                var ownedMemory = (OwnedMemory <T>)start.GetObject();
                if (ownedMemory != end.GetObject())
                {
                    ThrowHelper.ThrowInvalidOperationException_EndPositionNotReached();
                }

                data = ownedMemory.Memory.Slice(startIndex, endIndex - startIndex);
                next = default;
                return(true);

            case SequenceType.Array:
                var array = (T[])start.GetObject();

                if (array != end.GetObject())
                {
                    ThrowHelper.ThrowInvalidOperationException_EndPositionNotReached();
                }

                data = new Memory <T>(array, startIndex, endIndex - startIndex);
                next = default;
                return(true);

            default:
                ThrowHelper.ThrowInvalidOperationException_UnexpectedSegmentType();
                next = default;
                data = default;
                return(false);
            }
        }
        /// <summary>
        /// Indicates how much data was consumed, and how much examined, from a read operation
        /// </summary>
        public override void AdvanceTo(SequencePosition consumed, SequencePosition examined)
        {
            var cPage = (MappedPage)consumed.GetObject();
            var ePage = (MappedPage)examined.GetObject();

            if (cPage == null || ePage == null)
            {
                if (_first == null)
                {
                    return;                 // that's fine - means they called Advance on an empty EOF
                }
                Throw.Argument("Invalid position; consumed/examined must remain inside the buffer");
            }

            Debug.Assert(ePage != null, "No examined page");
            Debug.Assert(cPage != null, "No consumed page");

            MappedPage newKeep;
            var        cOffset = consumed.GetInteger();

            if (cOffset == cPage.Capacity)
            {
                newKeep = cPage.Next;
            }
            else
            {
                newKeep          = cPage;
                newKeep.Consumed = cOffset;
            }
            if (newKeep == null)
            {
                DebugLog($"Retaining nothing");
                _last = null;
            }
            else
            {
                DebugLog($"Retaining page {newKeep}");
            }

            // now drop any pages we don't need
            if (newKeep != _first)
            {
                var page = _first;
                while (page != null && page != newKeep)
                {
                    DebugLog($"Dropping page {page}");
                    page.Dispose();
                    page = page.Next;
                }
                _first = newKeep;
            }

            // check whether they looked at everything
            if (_last == null)
            {
                _loadMore = true; // definitely
            }
            else
            {
                var eOffset = examined.GetInteger();
                _loadMore = ePage == _last && eOffset == ePage.Capacity;
            }
            DebugLog($"After AdvanceTo, {CountAvailable(_first)} available bytes, {_remaining} remaining unloaded bytes, load more: {_loadMore}");
        }