Combine BufferWriter and CountingBufferWriter
This commit is contained in:
parent
128eefdef3
commit
f179339a79
|
|
@ -78,7 +78,7 @@ namespace PlatformBenchmarks
|
|||
}
|
||||
private static void PlainText(PipeWriter pipeWriter)
|
||||
{
|
||||
var writer = new CountingBufferWriter<PipeWriter>(pipeWriter);
|
||||
var writer = new BufferWriter<PipeWriter>(pipeWriter);
|
||||
// HTTP 1.1 OK
|
||||
writer.Write(_http11OK);
|
||||
|
||||
|
|
@ -105,7 +105,7 @@ namespace PlatformBenchmarks
|
|||
|
||||
private static void Json(PipeWriter pipeWriter)
|
||||
{
|
||||
var writer = new CountingBufferWriter<PipeWriter>(pipeWriter);
|
||||
var writer = new BufferWriter<PipeWriter>(pipeWriter);
|
||||
|
||||
// HTTP 1.1 OK
|
||||
writer.Write(_http11OK);
|
||||
|
|
@ -134,7 +134,7 @@ namespace PlatformBenchmarks
|
|||
|
||||
private static void Default(PipeWriter pipeWriter)
|
||||
{
|
||||
var writer = new CountingBufferWriter<PipeWriter>(pipeWriter);
|
||||
var writer = new BufferWriter<PipeWriter>(pipeWriter);
|
||||
|
||||
// HTTP 1.1 OK
|
||||
writer.Write(_http11OK);
|
||||
|
|
|
|||
|
|
@ -1,25 +1,27 @@
|
|||
// Licensed to the .NET Foundation under one or more agreements.
|
||||
// The .NET Foundation licenses this file to you under the MIT license.
|
||||
// See the LICENSE file in the project root for more information.
|
||||
// Copyright (c) .NET Foundation. All rights reserved.
|
||||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||
|
||||
using System.Runtime.CompilerServices;
|
||||
|
||||
namespace System.Buffers
|
||||
{
|
||||
internal ref struct BufferWriter<T> where T: IBufferWriter<byte>
|
||||
internal ref struct BufferWriter<T> where T : IBufferWriter<byte>
|
||||
{
|
||||
private T _output;
|
||||
private Span<byte> _span;
|
||||
private int _buffered;
|
||||
private long _bytesCommitted;
|
||||
|
||||
public BufferWriter(T output)
|
||||
{
|
||||
_buffered = 0;
|
||||
_bytesCommitted = 0;
|
||||
_output = output;
|
||||
_span = output.GetSpan();
|
||||
}
|
||||
|
||||
public Span<byte> Span => _span;
|
||||
public long BytesCommitted => _bytesCommitted;
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public void Commit()
|
||||
|
|
@ -27,6 +29,7 @@ namespace System.Buffers
|
|||
var buffered = _buffered;
|
||||
if (buffered > 0)
|
||||
{
|
||||
_bytesCommitted += buffered;
|
||||
_buffered = 0;
|
||||
_output.Advance(buffered);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -48,14 +48,14 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http
|
|||
return new ArraySegment<byte>(bytes, offset, 10 - offset);
|
||||
}
|
||||
|
||||
internal static int WriteBeginChunkBytes(ref CountingBufferWriter<PipeWriter> start, int dataCount)
|
||||
internal static int WriteBeginChunkBytes(ref BufferWriter<PipeWriter> start, int dataCount)
|
||||
{
|
||||
var chunkSegment = BeginChunkBytes(dataCount);
|
||||
start.Write(new ReadOnlySpan<byte>(chunkSegment.Array, chunkSegment.Offset, chunkSegment.Count));
|
||||
return chunkSegment.Count;
|
||||
}
|
||||
|
||||
internal static void WriteEndChunkBytes(ref CountingBufferWriter<PipeWriter> start)
|
||||
internal static void WriteEndChunkBytes(ref BufferWriter<PipeWriter> start)
|
||||
{
|
||||
start.Write(new ReadOnlySpan<byte>(_endChunkBytes.Array, _endChunkBytes.Offset, _endChunkBytes.Count));
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,99 +0,0 @@
|
|||
|
||||
// Copyright (c) .NET Foundation. All rights reserved.
|
||||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||
|
||||
using System.Runtime.CompilerServices;
|
||||
|
||||
namespace System.Buffers
|
||||
{
|
||||
// TODO: Once this is public, update the actual CountingBufferWriter in the Common repo,
|
||||
// and go back to using that.
|
||||
internal ref struct CountingBufferWriter<T> where T: IBufferWriter<byte>
|
||||
{
|
||||
private T _output;
|
||||
private Span<byte> _span;
|
||||
private int _buffered;
|
||||
private long _bytesCommitted;
|
||||
|
||||
public CountingBufferWriter(T output)
|
||||
{
|
||||
_buffered = 0;
|
||||
_bytesCommitted = 0;
|
||||
_output = output;
|
||||
_span = output.GetSpan();
|
||||
}
|
||||
|
||||
public Span<byte> Span => _span;
|
||||
public long BytesCommitted => _bytesCommitted;
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public void Commit()
|
||||
{
|
||||
var buffered = _buffered;
|
||||
if (buffered > 0)
|
||||
{
|
||||
_bytesCommitted += buffered;
|
||||
_buffered = 0;
|
||||
_output.Advance(buffered);
|
||||
}
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public void Advance(int count)
|
||||
{
|
||||
_buffered += count;
|
||||
_span = _span.Slice(count);
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public void Write(ReadOnlySpan<byte> source)
|
||||
{
|
||||
if (_span.Length >= source.Length)
|
||||
{
|
||||
source.CopyTo(_span);
|
||||
Advance(source.Length);
|
||||
}
|
||||
else
|
||||
{
|
||||
WriteMultiBuffer(source);
|
||||
}
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public void Ensure(int count = 1)
|
||||
{
|
||||
if (_span.Length < count)
|
||||
{
|
||||
EnsureMore(count);
|
||||
}
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.NoInlining)]
|
||||
private void EnsureMore(int count = 0)
|
||||
{
|
||||
if (_buffered > 0)
|
||||
{
|
||||
Commit();
|
||||
}
|
||||
|
||||
_output.GetMemory(count);
|
||||
_span = _output.GetSpan();
|
||||
}
|
||||
|
||||
private void WriteMultiBuffer(ReadOnlySpan<byte> source)
|
||||
{
|
||||
while (source.Length > 0)
|
||||
{
|
||||
if (_span.Length == 0)
|
||||
{
|
||||
EnsureMore();
|
||||
}
|
||||
|
||||
var writable = Math.Min(source.Length, _span.Length);
|
||||
source.Slice(0, writable).CopyTo(_span);
|
||||
source = source.Slice(writable);
|
||||
Advance(writable);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -128,7 +128,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http
|
|||
}
|
||||
|
||||
var buffer = _pipeWriter;
|
||||
var writer = new CountingBufferWriter<PipeWriter>(buffer);
|
||||
var writer = new BufferWriter<PipeWriter>(buffer);
|
||||
|
||||
writer.Write(_bytesHttpVersion11);
|
||||
var statusBytes = ReasonPhrases.ToStatusBytes(statusCode, reasonPhrase);
|
||||
|
|
@ -210,7 +210,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http
|
|||
}
|
||||
|
||||
writableBuffer = _pipeWriter;
|
||||
var writer = new CountingBufferWriter<PipeWriter>(writableBuffer);
|
||||
var writer = new BufferWriter<PipeWriter>(writableBuffer);
|
||||
if (buffer.Length > 0)
|
||||
{
|
||||
writer.Write(buffer);
|
||||
|
|
|
|||
|
|
@ -7765,7 +7765,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http
|
|||
return true;
|
||||
}
|
||||
|
||||
internal void CopyToFast(ref CountingBufferWriter<PipeWriter> output)
|
||||
internal void CopyToFast(ref BufferWriter<PipeWriter> output)
|
||||
{
|
||||
var tempBits = _bits | (_contentLength.HasValue ? -9223372036854775808L : 0);
|
||||
|
||||
|
|
|
|||
|
|
@ -936,7 +936,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http
|
|||
var bytesWritten = 0L;
|
||||
if (buffer.Length > 0)
|
||||
{
|
||||
var writer = new CountingBufferWriter<PipeWriter>(writableBuffer);
|
||||
var writer = new BufferWriter<PipeWriter>(writableBuffer);
|
||||
|
||||
ChunkWriter.WriteBeginChunkBytes(ref writer, buffer.Length);
|
||||
writer.Write(buffer.Span);
|
||||
|
|
|
|||
|
|
@ -27,7 +27,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http
|
|||
return GetEnumerator();
|
||||
}
|
||||
|
||||
internal void CopyTo(ref CountingBufferWriter<PipeWriter> buffer)
|
||||
internal void CopyTo(ref BufferWriter<PipeWriter> buffer)
|
||||
{
|
||||
CopyToFast(ref buffer);
|
||||
if (MaybeUnknown != null)
|
||||
|
|
|
|||
|
|
@ -40,7 +40,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http
|
|||
return result;
|
||||
}
|
||||
|
||||
internal static unsafe void WriteAsciiNoValidation(ref this CountingBufferWriter<PipeWriter> buffer, string data)
|
||||
internal static unsafe void WriteAsciiNoValidation(ref this BufferWriter<PipeWriter> buffer, string data)
|
||||
{
|
||||
if (string.IsNullOrEmpty(data))
|
||||
{
|
||||
|
|
@ -69,7 +69,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http
|
|||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
internal static unsafe void WriteNumeric(ref this CountingBufferWriter<PipeWriter> buffer, ulong number)
|
||||
internal static unsafe void WriteNumeric(ref this BufferWriter<PipeWriter> buffer, ulong number)
|
||||
{
|
||||
const byte AsciiDigitStart = (byte)'0';
|
||||
|
||||
|
|
@ -119,7 +119,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http
|
|||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.NoInlining)]
|
||||
private static void WriteNumericMultiWrite(ref this CountingBufferWriter<PipeWriter> buffer, ulong number)
|
||||
private static void WriteNumericMultiWrite(ref this BufferWriter<PipeWriter> buffer, ulong number)
|
||||
{
|
||||
const byte AsciiDigitStart = (byte)'0';
|
||||
|
||||
|
|
@ -140,7 +140,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http
|
|||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.NoInlining)]
|
||||
private unsafe static void WriteAsciiMultiWrite(ref this CountingBufferWriter<PipeWriter> buffer, string data)
|
||||
private unsafe static void WriteAsciiMultiWrite(ref this BufferWriter<PipeWriter> buffer, string data)
|
||||
{
|
||||
var remaining = data.Length;
|
||||
|
||||
|
|
|
|||
|
|
@ -4,7 +4,6 @@
|
|||
using System.Buffers;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using Xunit;
|
||||
|
||||
namespace System.IO.Pipelines.Tests
|
||||
|
|
@ -73,15 +72,20 @@ namespace System.IO.Pipelines.Tests
|
|||
[InlineData(1, 2)]
|
||||
[InlineData(2, 1)]
|
||||
[InlineData(1, 1)]
|
||||
public void CanWriteWithOffsetAndLenght(int offset, int length)
|
||||
public void CanWriteWithOffsetAndLength(int offset, int length)
|
||||
{
|
||||
BufferWriter<PipeWriter> writer = new BufferWriter<PipeWriter>(Pipe.Writer);
|
||||
var array = new byte[] { 1, 2, 3 };
|
||||
|
||||
writer.Write(new Span<byte>(array, offset, length));
|
||||
|
||||
Assert.Equal(0, writer.BytesCommitted);
|
||||
|
||||
writer.Commit();
|
||||
|
||||
Assert.Equal(length, writer.BytesCommitted);
|
||||
Assert.Equal(array.Skip(offset).Take(length).ToArray(), Read());
|
||||
Assert.Equal(length, writer.BytesCommitted);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
|
|
@ -94,6 +98,7 @@ namespace System.IO.Pipelines.Tests
|
|||
writer.Write(new Span<byte>(array, 0, array.Length));
|
||||
writer.Commit();
|
||||
|
||||
Assert.Equal(0, writer.BytesCommitted);
|
||||
Assert.Equal(array, Read());
|
||||
}
|
||||
|
||||
|
|
@ -105,6 +110,7 @@ namespace System.IO.Pipelines.Tests
|
|||
writer.Write(new byte[] { 1, 2, 3 });
|
||||
writer.Commit();
|
||||
|
||||
Assert.Equal(3, writer.BytesCommitted);
|
||||
Assert.Equal(new byte[] { 1, 2, 3 }, Read());
|
||||
}
|
||||
|
||||
|
|
@ -118,6 +124,7 @@ namespace System.IO.Pipelines.Tests
|
|||
writer.Write(new byte[] { 3 });
|
||||
writer.Commit();
|
||||
|
||||
Assert.Equal(3, writer.BytesCommitted);
|
||||
Assert.Equal(new byte[] { 1, 2, 3 }, Read());
|
||||
}
|
||||
|
||||
|
|
@ -133,6 +140,7 @@ namespace System.IO.Pipelines.Tests
|
|||
writer.Write(expectedBytes);
|
||||
writer.Commit();
|
||||
|
||||
Assert.Equal(expectedBytes.LongLength, writer.BytesCommitted);
|
||||
Assert.Equal(expectedBytes, Read());
|
||||
}
|
||||
|
||||
|
|
@ -142,6 +150,7 @@ namespace System.IO.Pipelines.Tests
|
|||
BufferWriter<PipeWriter> writer = new BufferWriter<PipeWriter>(Pipe.Writer);
|
||||
writer.Ensure(10);
|
||||
Assert.True(writer.Span.Length > 10);
|
||||
Assert.Equal(0, writer.BytesCommitted);
|
||||
Assert.Equal(new byte[] { }, Read());
|
||||
}
|
||||
|
||||
|
|
@ -164,6 +173,7 @@ namespace System.IO.Pipelines.Tests
|
|||
writer.Write(new byte[] { 1, 2, 3 });
|
||||
writer.Commit();
|
||||
|
||||
Assert.Equal(3, writer.BytesCommitted);
|
||||
Assert.Equal(initialLength - 3, writer.Span.Length);
|
||||
Assert.Equal(Pipe.Writer.GetMemory().Length, writer.Span.Length);
|
||||
Assert.Equal(new byte[] { 1, 2, 3 }, Read());
|
||||
|
|
@ -175,49 +185,17 @@ namespace System.IO.Pipelines.Tests
|
|||
[InlineData(500)]
|
||||
[InlineData(5000)]
|
||||
[InlineData(50000)]
|
||||
public async Task WriteLargeDataBinary(int length)
|
||||
public void WriteLargeDataBinary(int length)
|
||||
{
|
||||
var data = new byte[length];
|
||||
new Random(length).NextBytes(data);
|
||||
PipeWriter output = Pipe.Writer;
|
||||
output.Write(data);
|
||||
await output.FlushAsync();
|
||||
|
||||
ReadResult result = await Pipe.Reader.ReadAsync();
|
||||
ReadOnlySequence<byte> input = result.Buffer;
|
||||
Assert.Equal(data, input.ToArray());
|
||||
Pipe.Reader.AdvanceTo(input.End);
|
||||
}
|
||||
BufferWriter<PipeWriter> writer = new BufferWriter<PipeWriter>(Pipe.Writer);
|
||||
writer.Write(data);
|
||||
writer.Commit();
|
||||
|
||||
[Fact]
|
||||
public async Task CanWriteNothingToBuffer()
|
||||
{
|
||||
PipeWriter buffer = Pipe.Writer;
|
||||
buffer.GetMemory(0);
|
||||
buffer.Advance(0); // doing nothing, the hard way
|
||||
await buffer.FlushAsync();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void EmptyWriteDoesNotThrow()
|
||||
{
|
||||
Pipe.Writer.Write(new byte[0]);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ThrowsOnAdvanceOverMemorySize()
|
||||
{
|
||||
Memory<byte> buffer = Pipe.Writer.GetMemory(1);
|
||||
var exception = Assert.Throws<InvalidOperationException>(() => Pipe.Writer.Advance(buffer.Length + 1));
|
||||
Assert.Equal("Can't advance past buffer size.", exception.Message);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ThrowsOnAdvanceWithNoMemory()
|
||||
{
|
||||
PipeWriter buffer = Pipe.Writer;
|
||||
var exception = Assert.Throws<InvalidOperationException>(() => buffer.Advance(1));
|
||||
Assert.Equal("No writing operation. Make sure GetMemory() was called.", exception.Message);
|
||||
Assert.Equal(length, writer.BytesCommitted);
|
||||
Assert.Equal(data, Read());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -36,7 +36,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests
|
|||
public void WritesNumericToAscii(ulong number)
|
||||
{
|
||||
var writerBuffer = _pipe.Writer;
|
||||
var writer = new CountingBufferWriter<PipeWriter>(writerBuffer);
|
||||
var writer = new BufferWriter<PipeWriter>(writerBuffer);
|
||||
writer.WriteNumeric(number);
|
||||
writer.Commit();
|
||||
writerBuffer.FlushAsync().GetAwaiter().GetResult();
|
||||
|
|
@ -54,7 +54,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests
|
|||
public void WritesNumericAcrossSpanBoundaries(int gapSize)
|
||||
{
|
||||
var writerBuffer = _pipe.Writer;
|
||||
var writer = new CountingBufferWriter<PipeWriter>(writerBuffer);
|
||||
var writer = new BufferWriter<PipeWriter>(writerBuffer);
|
||||
// almost fill up the first block
|
||||
var spacer = new byte[writer.Span.Length - gapSize];
|
||||
writer.Write(spacer);
|
||||
|
|
@ -85,7 +85,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests
|
|||
public void EncodesAsAscii(string input, byte[] expected)
|
||||
{
|
||||
var pipeWriter = _pipe.Writer;
|
||||
var writer = new CountingBufferWriter<PipeWriter>(pipeWriter);
|
||||
var writer = new BufferWriter<PipeWriter>(pipeWriter);
|
||||
writer.WriteAsciiNoValidation(input);
|
||||
writer.Commit();
|
||||
pipeWriter.FlushAsync().GetAwaiter().GetResult();
|
||||
|
|
@ -115,7 +115,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests
|
|||
// WriteAscii doesn't validate if characters are in the ASCII range
|
||||
// but it shouldn't produce more than one byte per character
|
||||
var writerBuffer = _pipe.Writer;
|
||||
var writer = new CountingBufferWriter<PipeWriter>(writerBuffer);
|
||||
var writer = new BufferWriter<PipeWriter>(writerBuffer);
|
||||
writer.WriteAsciiNoValidation(input);
|
||||
writer.Commit();
|
||||
writerBuffer.FlushAsync().GetAwaiter().GetResult();
|
||||
|
|
@ -129,7 +129,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests
|
|||
{
|
||||
const byte maxAscii = 0x7f;
|
||||
var writerBuffer = _pipe.Writer;
|
||||
var writer = new CountingBufferWriter<PipeWriter>(writerBuffer);
|
||||
var writer = new BufferWriter<PipeWriter>(writerBuffer);
|
||||
for (var i = 0; i < maxAscii; i++)
|
||||
{
|
||||
writer.WriteAsciiNoValidation(new string((char)i, 1));
|
||||
|
|
@ -159,7 +159,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests
|
|||
{
|
||||
var testString = new string(' ', stringLength);
|
||||
var writerBuffer = _pipe.Writer;
|
||||
var writer = new CountingBufferWriter<PipeWriter>(writerBuffer);
|
||||
var writer = new BufferWriter<PipeWriter>(writerBuffer);
|
||||
// almost fill up the first block
|
||||
var spacer = new byte[writer.Span.Length - gapSize];
|
||||
writer.Write(spacer);
|
||||
|
|
|
|||
|
|
@ -548,7 +548,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http
|
|||
return true;
|
||||
}}
|
||||
{(loop.ClassName == "HttpResponseHeaders" ? $@"
|
||||
internal void CopyToFast(ref CountingBufferWriter<PipeWriter> output)
|
||||
internal void CopyToFast(ref BufferWriter<PipeWriter> output)
|
||||
{{
|
||||
var tempBits = _bits | (_contentLength.HasValue ? {1L << 63}L : 0);
|
||||
{Each(loop.Headers.Where(header => header.Identifier != "ContentLength").OrderBy(h => !h.PrimaryHeader), header => $@"
|
||||
|
|
|
|||
Loading…
Reference in New Issue