Combine BufferWriter and CountingBufferWriter

This commit is contained in:
Stephen Halter 2018-07-12 11:58:49 -07:00
parent 128eefdef3
commit f179339a79
12 changed files with 46 additions and 164 deletions

View File

@ -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);

View File

@ -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);
}

View File

@ -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));
}

View File

@ -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);
}
}
}
}

View File

@ -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);

View File

@ -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);

View File

@ -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);

View File

@ -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)

View File

@ -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;

View File

@ -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());
}
}
}

View File

@ -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);

View File

@ -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 => $@"