Added Write/WriteLine ReadOnlySpan/ReadOnlyMemory overrides to HttpResponseStreamWriter (#18451)

This commit is contained in:
Alessio Franceschelli 2020-02-09 06:04:01 +00:00 committed by GitHub
parent bc6fb44840
commit 6e0a4fcfdd
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
3 changed files with 308 additions and 2 deletions

View File

@ -161,10 +161,14 @@ namespace Microsoft.AspNetCore.WebUtilities
public override System.Threading.Tasks.Task FlushAsync() { throw null; }
public override void Write(char value) { }
public override void Write(char[] values, int index, int count) { }
public override void Write(System.ReadOnlySpan<char> value) { }
public override void Write(string value) { }
public override System.Threading.Tasks.Task WriteAsync(char value) { throw null; }
public override System.Threading.Tasks.Task WriteAsync(char[] values, int index, int count) { throw null; }
public override System.Threading.Tasks.Task WriteAsync(System.ReadOnlyMemory<char> value, System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)) { throw null; }
public override System.Threading.Tasks.Task WriteAsync(string value) { throw null; }
public override void WriteLine(System.ReadOnlySpan<char> value) { }
public override System.Threading.Tasks.Task WriteLineAsync(System.ReadOnlyMemory<char> value, System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)) { throw null; }
}
[System.Runtime.InteropServices.StructLayoutAttribute(System.Runtime.InteropServices.LayoutKind.Sequential)]
public partial struct KeyValueAccumulator

View File

@ -7,6 +7,7 @@ using System.Diagnostics;
using System.IO;
using System.Runtime.CompilerServices;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
namespace Microsoft.AspNetCore.WebUtilities
@ -127,6 +128,30 @@ namespace Microsoft.AspNetCore.WebUtilities
}
}
public override void Write(ReadOnlySpan<char> value)
{
if (_disposed)
{
throw new ObjectDisposedException(nameof(HttpResponseStreamWriter));
}
var written = 0;
while (written < value.Length)
{
if (_charBufferCount == _charBufferSize)
{
FlushInternal(flushEncoder: false);
}
written = CopyToCharBuffer(value);
if (written < value.Length)
{
value = value.Slice(written);
}
};
}
public override void Write(string value)
{
if (_disposed)
@ -152,6 +177,17 @@ namespace Microsoft.AspNetCore.WebUtilities
}
}
public override void WriteLine(ReadOnlySpan<char> value)
{
if (_disposed)
{
throw new ObjectDisposedException(nameof(HttpResponseStreamWriter));
}
Write(value);
Write(NewLine);
}
public override Task WriteAsync(char value)
{
if (_disposed)
@ -268,6 +304,95 @@ namespace Microsoft.AspNetCore.WebUtilities
}
}
public override Task WriteAsync(ReadOnlyMemory<char> value, CancellationToken cancellationToken = default)
{
if (_disposed)
{
return GetObjectDisposedTask();
}
if (cancellationToken.IsCancellationRequested)
{
return Task.FromCanceled(cancellationToken);
}
if (value.IsEmpty)
{
return Task.CompletedTask;
}
var remaining = _charBufferSize - _charBufferCount;
if (remaining >= value.Length)
{
// Enough room in buffer, no need to go async
CopyToCharBuffer(value.Span);
return Task.CompletedTask;
}
else
{
return WriteAsyncAwaited(value);
}
}
private async Task WriteAsyncAwaited(ReadOnlyMemory<char> value)
{
Debug.Assert(value.Length > 0);
Debug.Assert(_charBufferSize - _charBufferCount < value.Length);
int written = 0;
while (written < value.Length)
{
if (_charBufferCount == _charBufferSize)
{
await FlushInternalAsync(flushEncoder: false);
}
written = CopyToCharBuffer(value.Span);
if (written < value.Length)
{
value = value.Slice(written);
}
};
}
public override Task WriteLineAsync(ReadOnlyMemory<char> value, CancellationToken cancellationToken = default)
{
if (_disposed)
{
return GetObjectDisposedTask();
}
if (cancellationToken.IsCancellationRequested)
{
return Task.FromCanceled(cancellationToken);
}
if (value.IsEmpty && NewLine.Length == 0)
{
return Task.CompletedTask;
}
var remaining = _charBufferSize - _charBufferCount;
if (remaining >= value.Length + NewLine.Length)
{
// Enough room in buffer, no need to go async
CopyToCharBuffer(value.Span);
CopyToCharBuffer(NewLine);
return Task.CompletedTask;
}
else
{
return WriteLineAsyncAwaited(value);
}
}
private async Task WriteLineAsyncAwaited(ReadOnlyMemory<char> value)
{
await WriteAsyncAwaited(value);
await WriteAsyncAwaited(NewLine);
}
// We want to flush the stream when Flush/FlushAsync is explicitly
// called by the user (example: from a Razor view).
@ -423,6 +548,19 @@ namespace Microsoft.AspNetCore.WebUtilities
count -= remaining;
}
private int CopyToCharBuffer(ReadOnlySpan<char> value)
{
var remaining = Math.Min(_charBufferSize - _charBufferCount, value.Length);
var source = value.Slice(0, remaining);
var destination = new Span<char>(_charBuffer, _charBufferCount, remaining);
source.CopyTo(destination);
_charBufferCount += remaining;
return remaining;
}
[MethodImpl(MethodImplOptions.NoInlining)]
private static Task GetObjectDisposedTask()
{

View File

@ -253,6 +253,62 @@ namespace Microsoft.AspNetCore.WebUtilities
Assert.Equal(byteLength, stream.Length);
}
[Theory]
[InlineData(1023)]
[InlineData(1024)]
[InlineData(1050)]
[InlineData(2048)]
public void WriteReadOnlySpanChar_WritesToStream(int byteLength)
{
// Arrange
var stream = new TestMemoryStream();
var writer = new HttpResponseStreamWriter(stream, Encoding.UTF8);
// Act
using (writer)
{
var array = new string('a', byteLength).ToCharArray();
var span = new ReadOnlySpan<char>(array);
writer.Write(span);
}
// Assert
Assert.Equal(byteLength, stream.Length);
}
[Theory]
[InlineData(1022, "\n")]
[InlineData(1023, "\n")]
[InlineData(1024, "\n")]
[InlineData(1050, "\n")]
[InlineData(2047, "\n")]
[InlineData(2048, "\n")]
[InlineData(1021, "\r\n")]
[InlineData(1022, "\r\n")]
[InlineData(1023, "\r\n")]
[InlineData(1024, "\r\n")]
[InlineData(1050, "\r\n")]
[InlineData(2046, "\r\n")]
[InlineData(2048, "\r\n")]
public void WriteLineReadOnlySpanChar_WritesToStream(int byteLength, string newLine)
{
// Arrange
var stream = new TestMemoryStream();
var writer = new HttpResponseStreamWriter(stream, Encoding.UTF8);
writer.NewLine = newLine;
// Act
using (writer)
{
var array = new string('a', byteLength).ToCharArray();
var span = new ReadOnlySpan<char>(array);
writer.WriteLine(span);
}
// Assert
Assert.Equal(byteLength + newLine.Length, stream.Length);
}
[Theory]
[InlineData(1023)]
[InlineData(1024)]
@ -298,6 +354,102 @@ namespace Microsoft.AspNetCore.WebUtilities
Assert.Equal(byteLength, stream.Length);
}
[Theory]
[InlineData(0)]
[InlineData(1023)]
[InlineData(1024)]
[InlineData(1050)]
[InlineData(2048)]
public async Task WriteReadOnlyMemoryAsync_WritesToStream(int byteLength)
{
// Arrange
var stream = new TestMemoryStream();
var writer = new HttpResponseStreamWriter(stream, Encoding.UTF8);
// Act
using (writer)
{
var array = new string('a', byteLength).ToCharArray();
var memory = new ReadOnlyMemory<char>(array);
await writer.WriteAsync(memory);
}
// Assert
Assert.Equal(byteLength, stream.Length);
}
[Fact]
public async Task WriteReadOnlyMemoryAsync_TokenCanceled_ReturnsCanceledTask()
{
// Arrange
var stream = new TestMemoryStream();
using var writer = new HttpResponseStreamWriter(stream, Encoding.UTF8);
var memory = new ReadOnlyMemory<char>(new char[] { 'a' });
var cancellationToken = new CancellationToken(true);
// Act
await Assert.ThrowsAsync<TaskCanceledException>(async () => await writer.WriteAsync(memory, cancellationToken));
// Assert
Assert.Equal(0, stream.Length);
}
[Theory]
[InlineData(0, 1)]
[InlineData(1022, 1)]
[InlineData(1023, 1)]
[InlineData(1024, 1)]
[InlineData(1050, 1)]
[InlineData(2047, 1)]
[InlineData(2048, 1)]
[InlineData(1021, 2)]
[InlineData(1022, 2)]
[InlineData(1023, 2)]
[InlineData(1024, 2)]
[InlineData(1024, 1023)]
[InlineData(1024, 1024)]
[InlineData(1024, 1050)]
[InlineData(1050, 2)]
[InlineData(2046, 2)]
[InlineData(2048, 2)]
public async Task WriteLineReadOnlyMemoryAsync_WritesToStream(int byteLength, int newLineLength)
{
// Arrange
var stream = new TestMemoryStream();
var writer = new HttpResponseStreamWriter(stream, Encoding.UTF8);
writer.NewLine = new string('\n', newLineLength);
// Act
using (writer)
{
var array = new string('a', byteLength).ToCharArray();
var memory = new ReadOnlyMemory<char>(array);
await writer.WriteLineAsync(memory);
}
// Assert
Assert.Equal(byteLength + newLineLength, stream.Length);
}
[Fact]
public async Task WriteLineReadOnlyMemoryAsync_TokenCanceled_ReturnsCanceledTask()
{
// Arrange
var stream = new TestMemoryStream();
var writer = new HttpResponseStreamWriter(stream, Encoding.UTF8);
var memory = new ReadOnlyMemory<char>(new char[] { 'a' });
var cancellationToken = new CancellationToken(true);
// Act
using (writer)
{
await Assert.ThrowsAsync<TaskCanceledException>(async () => await writer.WriteLineAsync(memory, cancellationToken));
}
// Assert
Assert.Equal(0, stream.Length);
}
[Theory]
[InlineData("你好世界", "utf-16")]
[InlineData("హలో ప్రపంచ", "iso-8859-1")]
@ -539,11 +691,15 @@ namespace Microsoft.AspNetCore.WebUtilities
{
httpResponseStreamWriter.Write(new char[] { 'a', 'b' }, 0, 1);
})};
yield return new object[] { new Action<HttpResponseStreamWriter>((httpResponseStreamWriter) =>
{
httpResponseStreamWriter.Write("hello");
})};
yield return new object[] { new Action<HttpResponseStreamWriter>((httpResponseStreamWriter) =>
{
httpResponseStreamWriter.Write(new ReadOnlySpan<char>(new char[] { 'a', 'b' }));
})};
yield return new object[] { new Action<HttpResponseStreamWriter>((httpResponseStreamWriter) =>
{
httpResponseStreamWriter.Flush();
@ -560,11 +716,19 @@ namespace Microsoft.AspNetCore.WebUtilities
{
await httpResponseStreamWriter.WriteAsync(new char[] { 'a', 'b' }, 0, 1);
})};
yield return new object[] { new Func<HttpResponseStreamWriter, Task>(async (httpResponseStreamWriter) =>
{
await httpResponseStreamWriter.WriteAsync("hello");
})};
yield return new object[] { new Func<HttpResponseStreamWriter, Task>(async (httpResponseStreamWriter) =>
{
await httpResponseStreamWriter.WriteAsync(new ReadOnlyMemory<char>(new char[] { 'a', 'b' }));
})};
yield return new object[] { new Func<HttpResponseStreamWriter, Task>(async (httpResponseStreamWriter) =>
{
await httpResponseStreamWriter.WriteLineAsync(new ReadOnlyMemory<char>(new char[] { 'a', 'b' }));
})};
yield return new object[] { new Func<HttpResponseStreamWriter, Task>(async (httpResponseStreamWriter) =>
{
await httpResponseStreamWriter.FlushAsync();