Use system.buffers for our reader/writer

This commit is contained in:
Ryan Nowak 2015-12-17 17:50:34 -08:00
parent 5f66403248
commit 8fb187bf09
12 changed files with 202 additions and 368 deletions

View File

@ -2,8 +2,7 @@
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System; using System;
using Microsoft.AspNet.Http; using System.Buffers;
using Microsoft.AspNet.Http.Internal;
using Microsoft.AspNet.Mvc; using Microsoft.AspNet.Mvc;
using Microsoft.AspNet.Mvc.Abstractions; using Microsoft.AspNet.Mvc.Abstractions;
using Microsoft.AspNet.Mvc.ActionConstraints; using Microsoft.AspNet.Mvc.ActionConstraints;
@ -18,7 +17,6 @@ using Microsoft.AspNet.Mvc.ModelBinding.Validation;
using Microsoft.AspNet.Mvc.Routing; using Microsoft.AspNet.Mvc.Routing;
using Microsoft.AspNet.Routing; using Microsoft.AspNet.Routing;
using Microsoft.Extensions.DependencyInjection.Extensions; using Microsoft.Extensions.DependencyInjection.Extensions;
using Microsoft.Extensions.MemoryPool;
using Microsoft.Extensions.OptionsModel; using Microsoft.Extensions.OptionsModel;
using Microsoft.Extensions.PlatformAbstractions; using Microsoft.Extensions.PlatformAbstractions;
@ -144,8 +142,8 @@ namespace Microsoft.Extensions.DependencyInjection
services.TryAddSingleton<IUrlHelperFactory, UrlHelperFactory>(); services.TryAddSingleton<IUrlHelperFactory, UrlHelperFactory>();
services.TryAddSingleton<IHttpRequestStreamReaderFactory, MemoryPoolHttpRequestStreamReaderFactory>(); services.TryAddSingleton<IHttpRequestStreamReaderFactory, MemoryPoolHttpRequestStreamReaderFactory>();
services.TryAddSingleton<IHttpResponseStreamWriterFactory, MemoryPoolHttpResponseStreamWriterFactory>(); services.TryAddSingleton<IHttpResponseStreamWriterFactory, MemoryPoolHttpResponseStreamWriterFactory>();
services.TryAddSingleton<IArraySegmentPool<byte>, DefaultArraySegmentPool<byte>>(); services.TryAddSingleton(ArrayPool<byte>.Shared);
services.TryAddSingleton<IArraySegmentPool<char>, DefaultArraySegmentPool<char>>(); services.TryAddSingleton(ArrayPool<char>.Shared);
services.TryAddSingleton<ObjectResultExecutor>(); services.TryAddSingleton<ObjectResultExecutor>();
} }

View File

@ -2,11 +2,11 @@
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System; using System;
using System.Buffers;
using System.IO; using System.IO;
using System.Text; using System.Text;
using System.Threading.Tasks; using System.Threading.Tasks;
using Microsoft.AspNet.Mvc.Core; using Microsoft.AspNet.Mvc.Core;
using Microsoft.Extensions.MemoryPool;
namespace Microsoft.AspNet.Mvc namespace Microsoft.AspNet.Mvc
{ {
@ -23,13 +23,15 @@ namespace Microsoft.AspNet.Mvc
/// </summary> /// </summary>
public const int DefaultBufferSize = 1024; public const int DefaultBufferSize = 1024;
private readonly Stream _stream; private Stream _stream;
private Encoder _encoder; private readonly Encoder _encoder;
private LeasedArraySegment<byte> _leasedByteBuffer; private readonly ArrayPool<byte> _bytePool;
private LeasedArraySegment<char> _leasedCharBuffer; private readonly ArrayPool<char> _charPool;
private ArraySegment<byte> _byteBuffer; private readonly int _charBufferSize;
private ArraySegment<char> _charBuffer;
private int _charBufferSize; private byte[] _byteBuffer;
private char[] _charBuffer;
private int _charBufferCount; private int _charBufferCount;
public HttpResponseStreamWriter(Stream stream, Encoding encoding) public HttpResponseStreamWriter(Stream stream, Encoding encoding)
@ -64,16 +66,16 @@ namespace Microsoft.AspNet.Mvc
} }
_encoder = encoding.GetEncoder(); _encoder = encoding.GetEncoder();
_byteBuffer = new ArraySegment<byte>(new byte[encoding.GetMaxByteCount(bufferSize)]); _byteBuffer = new byte[encoding.GetMaxByteCount(bufferSize)];
_charBuffer = new ArraySegment<char>(new char[bufferSize]); _charBuffer = new char[bufferSize];
} }
public HttpResponseStreamWriter( public HttpResponseStreamWriter(
Stream stream, Stream stream,
Encoding encoding, Encoding encoding,
int bufferSize, int bufferSize,
LeasedArraySegment<byte> leasedByteBuffer, ArrayPool<byte> bytePool,
LeasedArraySegment<char> leasedCharBuffer) ArrayPool<char> charPool)
{ {
if (stream == null) if (stream == null)
{ {
@ -90,59 +92,71 @@ namespace Microsoft.AspNet.Mvc
throw new ArgumentNullException(nameof(encoding)); throw new ArgumentNullException(nameof(encoding));
} }
if (leasedByteBuffer == null) if (bytePool == null)
{ {
throw new ArgumentNullException(nameof(leasedByteBuffer)); throw new ArgumentNullException(nameof(bytePool));
} }
if (leasedCharBuffer == null) if (charPool == null)
{ {
throw new ArgumentNullException(nameof(leasedCharBuffer)); throw new ArgumentNullException(nameof(charPool));
}
if (bufferSize <= 0 || bufferSize > leasedCharBuffer.Data.Count)
{
throw new ArgumentOutOfRangeException(nameof(bufferSize));
}
var requiredLength = encoding.GetMaxByteCount(bufferSize);
if (requiredLength > leasedByteBuffer.Data.Count)
{
var message = Resources.FormatHttpResponseStreamWriter_InvalidBufferSize(
requiredLength,
bufferSize,
encoding.EncodingName,
typeof(Encoding).FullName,
nameof(Encoding.GetMaxByteCount));
throw new ArgumentException(message, nameof(leasedByteBuffer));
} }
_stream = stream; _stream = stream;
Encoding = encoding; Encoding = encoding;
_charBufferSize = bufferSize; _charBufferSize = bufferSize;
_leasedByteBuffer = leasedByteBuffer;
_leasedCharBuffer = leasedCharBuffer;
_encoder = encoding.GetEncoder(); _encoder = encoding.GetEncoder();
_byteBuffer = leasedByteBuffer.Data; _bytePool = bytePool;
_charBuffer = leasedCharBuffer.Data; _charPool = charPool;
_charBuffer = charPool.Rent(bufferSize);
try
{
var requiredLength = encoding.GetMaxByteCount(bufferSize);
_byteBuffer = bytePool.Rent(requiredLength);
}
catch
{
charPool.Return(_charBuffer);
_charBuffer = null;
if (_byteBuffer != null)
{
bytePool.Return(_byteBuffer);
_byteBuffer = null;
}
throw;
}
} }
public override Encoding Encoding { get; } public override Encoding Encoding { get; }
public override void Write(char value) public override void Write(char value)
{ {
if (_stream == null)
{
throw new ObjectDisposedException("stream");
}
if (_charBufferCount == _charBufferSize) if (_charBufferCount == _charBufferSize)
{ {
FlushInternal(); FlushInternal();
} }
_charBuffer.Array[_charBuffer.Offset + _charBufferCount] = value; _charBuffer[_charBufferCount] = value;
_charBufferCount++; _charBufferCount++;
} }
public override void Write(char[] values, int index, int count) public override void Write(char[] values, int index, int count)
{ {
if (_stream == null)
{
throw new ObjectDisposedException("stream");
}
if (values == null) if (values == null)
{ {
return; return;
@ -161,6 +175,11 @@ namespace Microsoft.AspNet.Mvc
public override void Write(string value) public override void Write(string value)
{ {
if (_stream == null)
{
throw new ObjectDisposedException("stream");
}
if (value == null) if (value == null)
{ {
return; return;
@ -181,17 +200,27 @@ namespace Microsoft.AspNet.Mvc
public override async Task WriteAsync(char value) public override async Task WriteAsync(char value)
{ {
if (_stream == null)
{
throw new ObjectDisposedException("stream");
}
if (_charBufferCount == _charBufferSize) if (_charBufferCount == _charBufferSize)
{ {
await FlushInternalAsync(); await FlushInternalAsync();
} }
_charBuffer.Array[_charBuffer.Offset + _charBufferCount] = value; _charBuffer[_charBufferCount] = value;
_charBufferCount++; _charBufferCount++;
} }
public override async Task WriteAsync(char[] values, int index, int count) public override async Task WriteAsync(char[] values, int index, int count)
{ {
if (_stream == null)
{
throw new ObjectDisposedException("stream");
}
if (values == null) if (values == null)
{ {
return; return;
@ -210,6 +239,11 @@ namespace Microsoft.AspNet.Mvc
public override async Task WriteAsync(string value) public override async Task WriteAsync(string value)
{ {
if (_stream == null)
{
throw new ObjectDisposedException("stream");
}
if (value == null) if (value == null)
{ {
return; return;
@ -233,11 +267,21 @@ namespace Microsoft.AspNet.Mvc
public override void Flush() public override void Flush()
{ {
if (_stream == null)
{
throw new ObjectDisposedException("stream");
}
FlushInternal(true, true); FlushInternal(true, true);
} }
public override Task FlushAsync() public override Task FlushAsync()
{ {
if (_stream == null)
{
throw new ObjectDisposedException("stream");
}
return FlushInternalAsync(flushStream: true, flushEncoder: true); return FlushInternalAsync(flushStream: true, flushEncoder: true);
} }
@ -245,7 +289,7 @@ namespace Microsoft.AspNet.Mvc
// sent in chunked encoding in case of Helios. // sent in chunked encoding in case of Helios.
protected override void Dispose(bool disposing) protected override void Dispose(bool disposing)
{ {
if (disposing) if (disposing && _stream != null)
{ {
try try
{ {
@ -253,14 +297,18 @@ namespace Microsoft.AspNet.Mvc
} }
finally finally
{ {
if (_leasedByteBuffer != null) _stream = null;
if (_bytePool != null)
{ {
_leasedByteBuffer.Owner.Return(_leasedByteBuffer); _bytePool.Return(_byteBuffer);
_byteBuffer = null;
} }
if (_leasedCharBuffer != null) if (_charPool != null)
{ {
_leasedCharBuffer.Owner.Return(_leasedCharBuffer); _charPool.Return(_charBuffer);
_charBuffer = null;
} }
} }
} }
@ -274,16 +322,16 @@ namespace Microsoft.AspNet.Mvc
} }
var count = _encoder.GetBytes( var count = _encoder.GetBytes(
_charBuffer.Array, _charBuffer,
_charBuffer.Offset, 0,
_charBufferCount, _charBufferCount,
_byteBuffer.Array, _byteBuffer,
_byteBuffer.Offset, 0,
flushEncoder); flushEncoder);
if (count > 0) if (count > 0)
{ {
_stream.Write(_byteBuffer.Array, _byteBuffer.Offset, count); _stream.Write(_byteBuffer, 0, count);
} }
_charBufferCount = 0; _charBufferCount = 0;
@ -302,16 +350,16 @@ namespace Microsoft.AspNet.Mvc
} }
var count = _encoder.GetBytes( var count = _encoder.GetBytes(
_charBuffer.Array, _charBuffer,
_charBuffer.Offset, 0,
_charBufferCount, _charBufferCount,
_byteBuffer.Array, _byteBuffer,
_byteBuffer.Offset, 0,
flushEncoder); flushEncoder);
if (count > 0) if (count > 0)
{ {
await _stream.WriteAsync(_byteBuffer.Array, _byteBuffer.Offset, count); await _stream.WriteAsync(_byteBuffer, 0, count);
} }
_charBufferCount = 0; _charBufferCount = 0;
@ -328,8 +376,8 @@ namespace Microsoft.AspNet.Mvc
value.CopyTo( value.CopyTo(
sourceIndex: index, sourceIndex: index,
destination: _charBuffer.Array, destination: _charBuffer,
destinationIndex: _charBuffer.Offset + _charBufferCount, destinationIndex: _charBufferCount,
count: remaining); count: remaining);
_charBufferCount += remaining; _charBufferCount += remaining;
@ -344,8 +392,8 @@ namespace Microsoft.AspNet.Mvc
Buffer.BlockCopy( Buffer.BlockCopy(
src: values, src: values,
srcOffset: index * sizeof(char), srcOffset: index * sizeof(char),
dst: _charBuffer.Array, dst: _charBuffer,
dstOffset: (_charBuffer.Offset + _charBufferCount) * sizeof(char), dstOffset: _charBufferCount * sizeof(char),
count: remaining * sizeof(char)); count: remaining * sizeof(char));
_charBufferCount += remaining; _charBufferCount += remaining;

View File

@ -2,12 +2,12 @@
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System; using System;
using System.Buffers;
using System.Diagnostics; using System.Diagnostics;
using System.IO; using System.IO;
using System.Text; using System.Text;
using System.Threading.Tasks; using System.Threading.Tasks;
using Microsoft.AspNet.Mvc.Core; using Microsoft.AspNet.Mvc.Core;
using Microsoft.Extensions.MemoryPool;
namespace Microsoft.AspNet.Mvc.Infrastructure namespace Microsoft.AspNet.Mvc.Infrastructure
{ {
@ -21,12 +21,12 @@ namespace Microsoft.AspNet.Mvc.Infrastructure
private readonly Encoding _encoding; private readonly Encoding _encoding;
private readonly Decoder _decoder; private readonly Decoder _decoder;
private readonly LeasedArraySegment<byte> _leasedByteBuffer; private readonly ArrayPool<byte> _bytePool;
private readonly LeasedArraySegment<char> _leasedCharBuffer; private readonly ArrayPool<char> _charPool;
private readonly int _byteBufferSize; private readonly int _byteBufferSize;
private readonly ArraySegment<byte> _byteBuffer; private byte[] _byteBuffer;
private readonly ArraySegment<char> _charBuffer; private char[] _charBuffer;
private int _charBufferIndex; private int _charBufferIndex;
private int _charsRead; private int _charsRead;
@ -66,17 +66,17 @@ namespace Microsoft.AspNet.Mvc.Infrastructure
} }
_byteBufferSize = bufferSize; _byteBufferSize = bufferSize;
_byteBuffer = new ArraySegment<byte>(new byte[bufferSize]); _byteBuffer = new byte[bufferSize];
var maxCharsPerBuffer = encoding.GetMaxCharCount(bufferSize); var maxCharsPerBuffer = encoding.GetMaxCharCount(bufferSize);
_charBuffer = new ArraySegment<char>(new char[maxCharsPerBuffer]); _charBuffer = new char[maxCharsPerBuffer];
} }
public HttpRequestStreamReader( public HttpRequestStreamReader(
Stream stream, Stream stream,
Encoding encoding, Encoding encoding,
int bufferSize, int bufferSize,
LeasedArraySegment<byte> leasedByteBuffer, ArrayPool<byte> bytePool,
LeasedArraySegment<char> leasedCharBuffer) ArrayPool<char> charPool)
{ {
if (stream == null) if (stream == null)
{ {
@ -93,42 +93,47 @@ namespace Microsoft.AspNet.Mvc.Infrastructure
throw new ArgumentNullException(nameof(encoding)); throw new ArgumentNullException(nameof(encoding));
} }
if (leasedByteBuffer == null) if (bytePool == null)
{ {
throw new ArgumentNullException(nameof(leasedByteBuffer)); throw new ArgumentNullException(nameof(bytePool));
} }
if (leasedCharBuffer == null) if (charPool == null)
{ {
throw new ArgumentNullException(nameof(leasedCharBuffer)); throw new ArgumentNullException(nameof(charPool));
} }
if (bufferSize <= 0 || bufferSize > leasedByteBuffer.Data.Count) if (bufferSize <= 0)
{ {
throw new ArgumentOutOfRangeException(nameof(bufferSize)); throw new ArgumentOutOfRangeException(nameof(bufferSize));
} }
var requiredLength = encoding.GetMaxCharCount(bufferSize);
if (requiredLength > leasedCharBuffer.Data.Count)
{
var message = Resources.FormatHttpRequestStreamReader_InvalidBufferSize(
requiredLength,
bufferSize,
encoding.EncodingName,
typeof(Encoding).FullName,
nameof(Encoding.GetMaxCharCount));
throw new ArgumentException(message, nameof(leasedCharBuffer));
}
_stream = stream; _stream = stream;
_encoding = encoding; _encoding = encoding;
_byteBufferSize = bufferSize; _byteBufferSize = bufferSize;
_leasedByteBuffer = leasedByteBuffer; _bytePool = bytePool;
_leasedCharBuffer = leasedCharBuffer; _charPool = charPool;
_decoder = encoding.GetDecoder(); _decoder = encoding.GetDecoder();
_byteBuffer = _leasedByteBuffer.Data;
_charBuffer = _leasedCharBuffer.Data; _byteBuffer = _bytePool.Rent(bufferSize);
try
{
var requiredLength = encoding.GetMaxCharCount(bufferSize);
_charBuffer = _charPool.Rent(requiredLength);
}
catch
{
_bytePool.Return(_byteBuffer);
_byteBuffer = null;
if (_charBuffer != null)
{
_charPool.Return(_charBuffer);
_charBuffer = null;
}
}
} }
#if dnx451 #if dnx451
@ -144,14 +149,16 @@ namespace Microsoft.AspNet.Mvc.Infrastructure
{ {
_stream = null; _stream = null;
if (_leasedByteBuffer != null) if (_bytePool != null)
{ {
_leasedByteBuffer.Owner.Return(_leasedByteBuffer); _bytePool.Return(_byteBuffer);
_byteBuffer = null;
} }
if (_leasedCharBuffer != null) if (_charPool != null)
{ {
_leasedCharBuffer.Owner.Return(_leasedCharBuffer); _charPool.Return(_charBuffer);
_charBuffer = null;
} }
} }
@ -173,7 +180,7 @@ namespace Microsoft.AspNet.Mvc.Infrastructure
} }
} }
return _charBuffer.Array[_charBuffer.Offset + _charBufferIndex]; return _charBuffer[_charBufferIndex];
} }
public override int Read() public override int Read()
@ -191,7 +198,7 @@ namespace Microsoft.AspNet.Mvc.Infrastructure
} }
} }
return _charBuffer.Array[_charBuffer.Offset + _charBufferIndex++]; return _charBuffer[_charBufferIndex++];
} }
public override int Read(char[] buffer, int index, int count) public override int Read(char[] buffer, int index, int count)
@ -236,8 +243,8 @@ namespace Microsoft.AspNet.Mvc.Infrastructure
} }
Buffer.BlockCopy( Buffer.BlockCopy(
_charBuffer.Array, _charBuffer,
(_charBuffer.Offset + _charBufferIndex) * 2, _charBufferIndex * 2,
buffer, buffer,
(index + charsRead) * 2, (index + charsRead) * 2,
charsRemaining * 2); charsRemaining * 2);
@ -303,8 +310,8 @@ namespace Microsoft.AspNet.Mvc.Infrastructure
{ {
Debug.Assert(n == 0); Debug.Assert(n == 0);
_bytesRead = await _stream.ReadAsync( _bytesRead = await _stream.ReadAsync(
_byteBuffer.Array, _byteBuffer,
_byteBuffer.Offset, 0,
_byteBufferSize); _byteBufferSize);
if (_bytesRead == 0) // EOF if (_bytesRead == 0) // EOF
{ {
@ -319,12 +326,12 @@ namespace Microsoft.AspNet.Mvc.Infrastructure
_charBufferIndex = 0; _charBufferIndex = 0;
n = _decoder.GetChars( n = _decoder.GetChars(
_byteBuffer.Array, _byteBuffer,
_byteBuffer.Offset, 0,
_bytesRead, _bytesRead,
_charBuffer.Array, _charBuffer,
_charBuffer.Offset); 0);
Debug.Assert(n > 0); Debug.Assert(n > 0);
_charsRead += n; // Number of chars in StreamReader's buffer. _charsRead += n; // Number of chars in StreamReader's buffer.
@ -344,8 +351,8 @@ namespace Microsoft.AspNet.Mvc.Infrastructure
} }
Buffer.BlockCopy( Buffer.BlockCopy(
_charBuffer.Array, _charBuffer,
(_charBuffer.Offset + _charBufferIndex) * 2, _charBufferIndex * 2,
buffer, buffer,
(index + charsRead) * 2, (index + charsRead) * 2,
n * 2); n * 2);
@ -375,7 +382,7 @@ namespace Microsoft.AspNet.Mvc.Infrastructure
do do
{ {
_bytesRead = _stream.Read(_byteBuffer.Array, _byteBuffer.Offset, _byteBufferSize); _bytesRead = _stream.Read(_byteBuffer, 0, _byteBufferSize);
if (_bytesRead == 0) // We're at EOF if (_bytesRead == 0) // We're at EOF
{ {
return _charsRead; return _charsRead;
@ -383,11 +390,11 @@ namespace Microsoft.AspNet.Mvc.Infrastructure
_isBlocked = (_bytesRead < _byteBufferSize); _isBlocked = (_bytesRead < _byteBufferSize);
_charsRead += _decoder.GetChars( _charsRead += _decoder.GetChars(
_byteBuffer.Array, _byteBuffer,
_byteBuffer.Offset, 0,
_bytesRead, _bytesRead,
_charBuffer.Array, _charBuffer,
_charBuffer.Offset + _charsRead); _charsRead);
} }
while (_charsRead == 0); while (_charsRead == 0);
@ -404,8 +411,8 @@ namespace Microsoft.AspNet.Mvc.Infrastructure
{ {
_bytesRead = await _stream.ReadAsync( _bytesRead = await _stream.ReadAsync(
_byteBuffer.Array, _byteBuffer,
_byteBuffer.Offset, 0,
_byteBufferSize).ConfigureAwait(false); _byteBufferSize).ConfigureAwait(false);
if (_bytesRead == 0) if (_bytesRead == 0)
{ {
@ -417,11 +424,11 @@ namespace Microsoft.AspNet.Mvc.Infrastructure
_isBlocked = (_bytesRead < _byteBufferSize); _isBlocked = (_bytesRead < _byteBufferSize);
_charsRead += _decoder.GetChars( _charsRead += _decoder.GetChars(
_byteBuffer.Array, _byteBuffer,
_byteBuffer.Offset, 0,
_bytesRead, _bytesRead,
_charBuffer.Array, _charBuffer,
_charBuffer.Offset + _charsRead); _charsRead);
} }
while (_charsRead == 0); while (_charsRead == 0);

View File

@ -2,9 +2,9 @@
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System; using System;
using System.Buffers;
using System.IO; using System.IO;
using System.Text; using System.Text;
using Microsoft.Extensions.MemoryPool;
namespace Microsoft.AspNet.Mvc.Infrastructure namespace Microsoft.AspNet.Mvc.Infrastructure
{ {
@ -18,21 +18,21 @@ namespace Microsoft.AspNet.Mvc.Infrastructure
/// </summary> /// </summary>
public static readonly int DefaultBufferSize = 1024; // 1KB - results in a 4KB byte array for UTF8. public static readonly int DefaultBufferSize = 1024; // 1KB - results in a 4KB byte array for UTF8.
private readonly IArraySegmentPool<byte> _bytePool; private readonly ArrayPool<byte> _bytePool;
private readonly IArraySegmentPool<char> _charPool; private readonly ArrayPool<char> _charPool;
/// <summary> /// <summary>
/// Creates a new <see cref="MemoryPoolHttpRequestStreamReaderFactory"/>. /// Creates a new <see cref="MemoryPoolHttpRequestStreamReaderFactory"/>.
/// </summary> /// </summary>
/// <param name="bytePool"> /// <param name="bytePool">
/// The <see cref="IArraySegmentPool{byte}"/> for creating <see cref="byte"/> buffers. /// The <see cref="ArrayPool{byte}"/> for creating <see cref="byte"/> buffers.
/// </param> /// </param>
/// <param name="charPool"> /// <param name="charPool">
/// The <see cref="IArraySegmentPool{char}"/> for creating <see cref="char"/> buffers. /// The <see cref="ArrayPool{char}"/> for creating <see cref="char"/> buffers.
/// </param> /// </param>
public MemoryPoolHttpRequestStreamReaderFactory( public MemoryPoolHttpRequestStreamReaderFactory(
IArraySegmentPool<byte> bytePool, ArrayPool<byte> bytePool,
IArraySegmentPool<char> charPool) ArrayPool<char> charPool)
{ {
if (bytePool == null) if (bytePool == null)
{ {
@ -61,34 +61,7 @@ namespace Microsoft.AspNet.Mvc.Infrastructure
throw new ArgumentNullException(nameof(encoding)); throw new ArgumentNullException(nameof(encoding));
} }
LeasedArraySegment<byte> bytes = null; return new HttpRequestStreamReader(stream, encoding, DefaultBufferSize, _bytePool, _charPool);
LeasedArraySegment<char> chars = null;
try
{
bytes = _bytePool.Lease(DefaultBufferSize);
// We need to compute the minimum size of the char buffer based on the size of the byte buffer,
// so that we have enough room to encode the buffer in one shot.
var minimumSize = encoding.GetMaxCharCount(DefaultBufferSize);
chars = _charPool.Lease(minimumSize);
return new HttpRequestStreamReader(stream, encoding, DefaultBufferSize, bytes, chars);
}
catch
{
if (bytes != null)
{
bytes.Owner.Return(bytes);
}
if (chars != null)
{
chars.Owner.Return(chars);
}
throw;
}
} }
} }
} }

View File

@ -2,9 +2,9 @@
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System; using System;
using System.Buffers;
using System.IO; using System.IO;
using System.Text; using System.Text;
using Microsoft.Extensions.MemoryPool;
namespace Microsoft.AspNet.Mvc.Infrastructure namespace Microsoft.AspNet.Mvc.Infrastructure
{ {
@ -18,21 +18,21 @@ namespace Microsoft.AspNet.Mvc.Infrastructure
/// </summary> /// </summary>
public static readonly int DefaultBufferSize = 1024; // 1KB - results in a 4KB byte array for UTF8. public static readonly int DefaultBufferSize = 1024; // 1KB - results in a 4KB byte array for UTF8.
private readonly IArraySegmentPool<byte> _bytePool; private readonly ArrayPool<byte> _bytePool;
private readonly IArraySegmentPool<char> _charPool; private readonly ArrayPool<char> _charPool;
/// <summary> /// <summary>
/// Creates a new <see cref="MemoryPoolHttpResponseStreamWriterFactory"/>. /// Creates a new <see cref="MemoryPoolHttpResponseStreamWriterFactory"/>.
/// </summary> /// </summary>
/// <param name="bytePool"> /// <param name="bytePool">
/// The <see cref="IArraySegmentPool{byte}"/> for creating <see cref="byte"/> buffers. /// The <see cref="ArrayPool{byte}"/> for creating <see cref="byte"/> buffers.
/// </param> /// </param>
/// <param name="charPool"> /// <param name="charPool">
/// The <see cref="IArraySegmentPool{char}"/> for creating <see cref="char"/> buffers. /// The <see cref="ArrayPool{char}"/> for creating <see cref="char"/> buffers.
/// </param> /// </param>
public MemoryPoolHttpResponseStreamWriterFactory( public MemoryPoolHttpResponseStreamWriterFactory(
IArraySegmentPool<byte> bytePool, ArrayPool<byte> bytePool,
IArraySegmentPool<char> charPool) ArrayPool<char> charPool)
{ {
if (bytePool == null) if (bytePool == null)
{ {
@ -61,34 +61,7 @@ namespace Microsoft.AspNet.Mvc.Infrastructure
throw new ArgumentNullException(nameof(encoding)); throw new ArgumentNullException(nameof(encoding));
} }
LeasedArraySegment<byte> bytes = null; return new HttpResponseStreamWriter(stream, encoding, DefaultBufferSize, _bytePool, _charPool);
LeasedArraySegment<char> chars = null;
try
{
chars = _charPool.Lease(DefaultBufferSize);
// We need to compute the minimum size of the byte buffer based on the size of the char buffer,
// so that we have enough room to encode the buffer in one shot.
var minimumSize = encoding.GetMaxByteCount(DefaultBufferSize);
bytes = _bytePool.Lease(minimumSize);
return new HttpResponseStreamWriter(stream, encoding, DefaultBufferSize, bytes, chars);
}
catch
{
if (bytes != null)
{
bytes.Owner.Return(bytes);
}
if (chars != null)
{
chars.Owner.Return(chars);
}
throw;
}
} }
} }
} }

View File

@ -970,22 +970,6 @@ namespace Microsoft.AspNet.Mvc.Core
return string.Format(CultureInfo.CurrentCulture, GetString("ValueProviderResult_NoConverterExists"), p0, p1); return string.Format(CultureInfo.CurrentCulture, GetString("ValueProviderResult_NoConverterExists"), p0, p1);
} }
/// <summary>
/// The byte buffer must have a length of at least '{0}' to be used with a char buffer of size '{1}' and encoding '{2}'. Use '{3}.{4}' to compute the correct size for the byte buffer.
/// </summary>
internal static string HttpResponseStreamWriter_InvalidBufferSize
{
get { return GetString("HttpResponseStreamWriter_InvalidBufferSize"); }
}
/// <summary>
/// The byte buffer must have a length of at least '{0}' to be used with a char buffer of size '{1}' and encoding '{2}'. Use '{3}.{4}' to compute the correct size for the byte buffer.
/// </summary>
internal static string FormatHttpResponseStreamWriter_InvalidBufferSize(object p0, object p1, object p2, object p3, object p4)
{
return string.Format(CultureInfo.CurrentCulture, GetString("HttpResponseStreamWriter_InvalidBufferSize"), p0, p1, p2, p3, p4);
}
/// <summary> /// <summary>
/// Path '{0}' was not rooted. /// Path '{0}' was not rooted.
/// </summary> /// </summary>
@ -1018,22 +1002,6 @@ namespace Microsoft.AspNet.Mvc.Core
return GetString("UrlNotLocal"); return GetString("UrlNotLocal");
} }
/// <summary>
/// The char buffer must have a length of at least '{0}' to be used with a byte buffer of size '{1}' and encoding '{2}'. Use '{3}.{4}' to compute the correct size for the char buffer.
/// </summary>
internal static string HttpRequestStreamReader_InvalidBufferSize
{
get { return GetString("HttpRequestStreamReader_InvalidBufferSize"); }
}
/// <summary>
/// The char buffer must have a length of at least '{0}' to be used with a byte buffer of size '{1}' and encoding '{2}'. Use '{3}.{4}' to compute the correct size for the char buffer.
/// </summary>
internal static string FormatHttpRequestStreamReader_InvalidBufferSize(object p0, object p1, object p2, object p3, object p4)
{
return string.Format(CultureInfo.CurrentCulture, GetString("HttpRequestStreamReader_InvalidBufferSize"), p0, p1, p2, p3, p4);
}
/// <summary> /// <summary>
/// The stream must support reading. /// The stream must support reading.
/// </summary> /// </summary>

View File

@ -306,9 +306,6 @@
<data name="ValueProviderResult_NoConverterExists" xml:space="preserve"> <data name="ValueProviderResult_NoConverterExists" xml:space="preserve">
<value>The parameter conversion from type '{0}' to type '{1}' failed because no type converter can convert between these types.</value> <value>The parameter conversion from type '{0}' to type '{1}' failed because no type converter can convert between these types.</value>
</data> </data>
<data name="HttpResponseStreamWriter_InvalidBufferSize" xml:space="preserve">
<value>The byte buffer must have a length of at least '{0}' to be used with a char buffer of size '{1}' and encoding '{2}'. Use '{3}.{4}' to compute the correct size for the byte buffer.</value>
</data>
<data name="FileResult_PathNotRooted" xml:space="preserve"> <data name="FileResult_PathNotRooted" xml:space="preserve">
<value>Path '{0}' was not rooted.</value> <value>Path '{0}' was not rooted.</value>
<comment>{0} is the path which wasn't rooted</comment> <comment>{0} is the path which wasn't rooted</comment>
@ -316,9 +313,6 @@
<data name="UrlNotLocal" xml:space="preserve"> <data name="UrlNotLocal" xml:space="preserve">
<value>The supplied URL is not local. A URL with an absolute path is considered local if it does not have a host/authority part. URLs using virtual paths ('~/') are also local.</value> <value>The supplied URL is not local. A URL with an absolute path is considered local if it does not have a host/authority part. URLs using virtual paths ('~/') are also local.</value>
</data> </data>
<data name="HttpRequestStreamReader_InvalidBufferSize" xml:space="preserve">
<value>The char buffer must have a length of at least '{0}' to be used with a byte buffer of size '{1}' and encoding '{2}'. Use '{3}.{4}' to compute the correct size for the char buffer.</value>
</data>
<data name="HttpRequestStreamReader_StreamNotReadable" xml:space="preserve"> <data name="HttpRequestStreamReader_StreamNotReadable" xml:space="preserve">
<value>The stream must support reading.</value> <value>The stream must support reading.</value>
</data> </data>

View File

@ -26,7 +26,6 @@
"type": "build" "type": "build"
}, },
"Microsoft.Extensions.Logging.Abstractions": "1.0.0-*", "Microsoft.Extensions.Logging.Abstractions": "1.0.0-*",
"Microsoft.Extensions.MemoryPool": "1.0.0-*",
"Microsoft.Extensions.PropertyActivator.Sources": { "Microsoft.Extensions.PropertyActivator.Sources": {
"version": "1.0.0-*", "version": "1.0.0-*",
"type": "build" "type": "build"
@ -39,6 +38,7 @@
"version": "1.0.0-*", "version": "1.0.0-*",
"type": "build" "type": "build"
}, },
"System.Buffers": "4.0.0-*",
"System.Diagnostics.DiagnosticSource": "4.0.0-*" "System.Diagnostics.DiagnosticSource": "4.0.0-*"
}, },
"frameworks": { "frameworks": {

View File

@ -26,7 +26,8 @@
"type": "build" "type": "build"
}, },
"Microsoft.Dnx.Compilation.CSharp.Common": "1.0.0-*", "Microsoft.Dnx.Compilation.CSharp.Common": "1.0.0-*",
"Microsoft.Dnx.Compilation.CSharp.Abstractions": "1.0.0-*" "Microsoft.Dnx.Compilation.CSharp.Abstractions": "1.0.0-*",
"Microsoft.Extensions.MemoryPool": "1.0.0-*"
}, },
"frameworks": { "frameworks": {
"net451": { "net451": {

View File

@ -13,7 +13,6 @@ using Microsoft.AspNet.Mvc.ViewFeatures;
using Microsoft.AspNet.Mvc.ViewFeatures.Buffer; using Microsoft.AspNet.Mvc.ViewFeatures.Buffer;
using Microsoft.AspNet.Mvc.ViewFeatures.Internal; using Microsoft.AspNet.Mvc.ViewFeatures.Internal;
using Microsoft.Extensions.DependencyInjection.Extensions; using Microsoft.Extensions.DependencyInjection.Extensions;
using Microsoft.Extensions.MemoryPool;
using Microsoft.Extensions.OptionsModel; using Microsoft.Extensions.OptionsModel;
namespace Microsoft.Extensions.DependencyInjection namespace Microsoft.Extensions.DependencyInjection

View File

@ -1,12 +1,12 @@
// Copyright (c) .NET Foundation. All rights reserved. // 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. // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System.Buffers;
using System.IO; using System.IO;
using System.Text; using System.Text;
using System.Threading; using System.Threading;
using System.Threading.Tasks; using System.Threading.Tasks;
using Microsoft.AspNet.Testing; using Microsoft.AspNet.Testing;
using Microsoft.Extensions.MemoryPool;
using Xunit; using Xunit;
namespace Microsoft.AspNet.Mvc namespace Microsoft.AspNet.Mvc
@ -384,98 +384,21 @@ namespace Microsoft.AspNet.Mvc
var expectedBytes = encoding.GetBytes("Hello, World!"); var expectedBytes = encoding.GetBytes("Hello, World!");
using (var bytePool = new DefaultArraySegmentPool<byte>()) using (var writer = new HttpResponseStreamWriter(
stream,
encoding,
1024,
ArrayPool<byte>.Shared,
ArrayPool<char>.Shared))
{ {
using (var charPool = new DefaultArraySegmentPool<char>()) // Act
{ writer.Write("Hello, World!");
LeasedArraySegment<byte> bytes = null;
LeasedArraySegment<char> chars = null;
HttpResponseStreamWriter writer;
try
{
bytes = bytePool.Lease(4096);
chars = charPool.Lease(1024);
writer = new HttpResponseStreamWriter(stream, encoding, 1024, bytes, chars);
}
catch
{
if (bytes != null)
{
bytes.Owner.Return(bytes);
}
if (chars != null)
{
chars.Owner.Return(chars);
}
throw;
}
// Act
using (writer)
{
writer.Write("Hello, World!");
}
}
} }
// Assert // Assert
Assert.Equal(expectedBytes, stream.ToArray()); Assert.Equal(expectedBytes, stream.ToArray());
} }
// This covers the error case where the byte buffer is too small. This is a safeguard, and shouldn't happen
// if we're using the writer factory.
[Fact]
public void HttpResponseStreamWriter_UsingPooledBuffers_SmallByteBuffer()
{
// Arrange
var encoding = Encoding.UTF8;
var stream = new MemoryStream();
var message =
"The byte buffer must have a length of at least '12291' to be used with a char buffer of " +
"size '4096' and encoding 'Unicode (UTF-8)'. Use 'System.Text.Encoding.GetMaxByteCount' " +
"to compute the correct size for the byte buffer.";
using (var bytePool = new DefaultArraySegmentPool<byte>())
{
using (var charPool = new DefaultArraySegmentPool<char>())
{
LeasedArraySegment<byte> bytes = null;
LeasedArraySegment<char> chars = null;
HttpResponseStreamWriter writer = null;
try
{
bytes = bytePool.Lease(1024);
chars = charPool.Lease(4096);
// Act & Assert
ExceptionAssert.ThrowsArgument(
() => writer = new HttpResponseStreamWriter(stream, encoding, chars.Data.Count, bytes, chars),
"byteBuffer",
message);
writer.Dispose();
}
catch
{
if (bytes != null)
{
bytes.Owner.Return(bytes);
}
if (chars != null)
{
chars.Owner.Return(chars);
}
}
}
}
}
private class TestMemoryStream : MemoryStream private class TestMemoryStream : MemoryStream
{ {
private int _flushCallCount; private int _flushCallCount;

View File

@ -1,50 +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;
using System.IO;
using System.Text;
using Microsoft.Extensions.MemoryPool;
using Moq;
using Xunit;
namespace Microsoft.AspNet.Mvc.Infrastructure
{
public class MemoryPoolHttpResponseStreamWriterFactoryTest
{
[Fact]
public void CreateWriter_BuffersReturned_OnException()
{
// Arrange
var bytePool = new Mock<IArraySegmentPool<byte>>(MockBehavior.Strict);
bytePool
.Setup(p => p.Lease(It.IsAny<int>()))
.Returns(new LeasedArraySegment<byte>(new ArraySegment<byte>(new byte[4096]), bytePool.Object));
bytePool
.Setup(p => p.Return(It.IsAny<LeasedArraySegment<byte>>()))
.Verifiable();
var charPool = new Mock<IArraySegmentPool<char>>(MockBehavior.Strict);
charPool
.Setup(p => p.Lease(MemoryPoolHttpResponseStreamWriterFactory.DefaultBufferSize))
.Returns(new LeasedArraySegment<char>(new ArraySegment<char>(new char[4096]), charPool.Object));
charPool
.Setup(p => p.Return(It.IsAny<LeasedArraySegment<char>>()))
.Verifiable();
var encoding = new Mock<Encoding>();
encoding
.Setup(e => e.GetEncoder())
.Throws(new InvalidOperationException());
var factory = new MemoryPoolHttpResponseStreamWriterFactory(bytePool.Object, charPool.Object);
// Act
Assert.Throws<InvalidOperationException>(() => factory.CreateWriter(new MemoryStream(), encoding.Object));
// Assert
bytePool.Verify();
charPool.Verify();
}
}
}