| File: RecyclableSequenceBuilder.cs | Web Access |
| Project: src\aspnetcore\src\Middleware\OutputCaching\src\Microsoft.AspNetCore.OutputCaching.csproj (Microsoft.AspNetCore.OutputCaching) |
// Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. using System.Buffers; using System.Diagnostics; using System.Runtime.InteropServices; namespace Microsoft.AspNetCore.OutputCaching; // allows capture of written payloads into a ReadOnlySequence<byte> based on RecyclableReadOnlySequenceSegment internal sealed class RecyclableSequenceBuilder : IDisposable { private RecyclableReadOnlySequenceSegment? _firstSegment, _currentSegment; private int _currentSegmentIndex; private readonly int _segmentSize; private bool _closed; public long Length { get; private set; } internal RecyclableSequenceBuilder(int segmentSize) { ArgumentOutOfRangeException.ThrowIfNegativeOrZero(segmentSize); _segmentSize = segmentSize; } // Extracting the buffered segments closes the stream for writing internal ReadOnlySequence<byte> DetachAndReset() { _closed = true; if (_firstSegment is null) { return default; } if (ReferenceEquals(_firstSegment, _currentSegment)) { // single segment; use a simple sequence (no segments) ReadOnlyMemory<byte> memory = _firstSegment.Memory.Slice(0, _currentSegmentIndex); // we *can* recycle our single segment, just: keep the buffers RecyclableReadOnlySequenceSegment.RecycleChain(_firstSegment, recycleBuffers: false); // reset local state _firstSegment = _currentSegment = null; _currentSegmentIndex = 0; return new(memory); } // use a segmented sequence var payload = new ReadOnlySequence<byte>(_firstSegment, 0, _currentSegment!, _currentSegmentIndex); // reset our local state for an abundance of caution _firstSegment = _currentSegment = null; _currentSegmentIndex = 0; return payload; } public void Dispose() => RecyclableReadOnlySequenceSegment.RecycleChain(DetachAndReset(), recycleBuffers: true); private Span<byte> GetBuffer() { if (_closed) { Throw(); } static void Throw() => throw new ObjectDisposedException(nameof(RecyclableSequenceBuilder), "The stream has been closed for writing."); if (_firstSegment is null) { _currentSegment = _firstSegment = RecyclableReadOnlySequenceSegment.Create(_segmentSize, null); _currentSegmentIndex = 0; } Debug.Assert(_currentSegment is not null); var current = _currentSegment.Memory; Debug.Assert(_currentSegmentIndex >= 0 && _currentSegmentIndex <= current.Length); if (_currentSegmentIndex == current.Length) { _currentSegment = RecyclableReadOnlySequenceSegment.Create(_segmentSize, _currentSegment); _currentSegmentIndex = 0; current = _currentSegment.Memory; } // have capacity in current chunk return MemoryMarshal.AsMemory(current).Span.Slice(_currentSegmentIndex); } public void Write(ReadOnlySpan<byte> buffer) { while (!buffer.IsEmpty) { var available = GetBuffer(); if (available.Length >= buffer.Length) { buffer.CopyTo(available); Advance(buffer.Length); return; // all done } else { var toWrite = Math.Min(buffer.Length, available.Length); if (toWrite <= 0) { Throw(); } buffer.Slice(0, toWrite).CopyTo(available); Advance(toWrite); buffer = buffer.Slice(toWrite); } } static void Throw() => throw new InvalidOperationException("Unable to acquire non-empty write buffer"); } private void Advance(int count) { _currentSegmentIndex += count; Length += count; } public void WriteByte(byte value) { GetBuffer()[0] = value; Advance(1); } }