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()); }
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) )); }
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); } }
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()); }
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)); }
/// <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; } }
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); } } }
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)));
/// <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; } }
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); }
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); }
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)));
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; } }
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)); }
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)); }
/// <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; } }
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)); }
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); } }
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); }
/// <inheritdoc /> public override void AdvanceTo(SequencePosition consumed, SequencePosition examined) { ThrowIfCompleted(); AdvanceTo((BufferSegment?)consumed.GetObject(), consumed.GetInteger(), (BufferSegment?)examined.GetObject(), examined.GetInteger()); }
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); }
/// <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);
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}"); }