Use pooled memory for the streamwriter
This commit is contained in:
parent
306776ff63
commit
4e08eda58d
|
|
@ -16,6 +16,7 @@ using Microsoft.AspNet.Mvc.ModelBinding.Validation;
|
|||
using Microsoft.AspNet.Mvc.Routing;
|
||||
using Microsoft.AspNet.Routing;
|
||||
using Microsoft.Framework.DependencyInjection.Extensions;
|
||||
using Microsoft.Framework.MemoryPool;
|
||||
using Microsoft.Framework.OptionsModel;
|
||||
|
||||
namespace Microsoft.Framework.DependencyInjection
|
||||
|
|
@ -137,6 +138,9 @@ namespace Microsoft.Framework.DependencyInjection
|
|||
services.TryAddSingleton<IActionContextAccessor, ActionContextAccessor>();
|
||||
services.TryAddSingleton<IActionBindingContextAccessor, ActionBindingContextAccessor>();
|
||||
services.TryAddSingleton<IUrlHelper, UrlHelper>();
|
||||
services.TryAddSingleton<IHttpResponseStreamWriterFactory, MemoryPoolHttpResponseStreamWriterFactory>();
|
||||
services.TryAddSingleton<IArraySegmentPool<byte>, DefaultArraySegmentPool<byte>>();
|
||||
services.TryAddSingleton<IArraySegmentPool<char>, DefaultArraySegmentPool<char>>();
|
||||
}
|
||||
|
||||
private static void ConfigureDefaultServices(IServiceCollection services)
|
||||
|
|
|
|||
|
|
@ -5,6 +5,7 @@ using System;
|
|||
using System.IO;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.Framework.MemoryPool;
|
||||
|
||||
namespace Microsoft.AspNet.Mvc
|
||||
{
|
||||
|
|
@ -18,10 +19,13 @@ namespace Microsoft.AspNet.Mvc
|
|||
/// Default buffer size.
|
||||
/// </summary>
|
||||
public const int DefaultBufferSize = 1024;
|
||||
|
||||
private readonly Stream _stream;
|
||||
private Encoder _encoder;
|
||||
private byte[] _byteBuffer;
|
||||
private char[] _charBuffer;
|
||||
private LeasedArraySegment<byte> _leasedByteBuffer;
|
||||
private LeasedArraySegment<char> _leasedCharBuffer;
|
||||
private ArraySegment<byte> _byteBuffer;
|
||||
private ArraySegment<char> _charBuffer;
|
||||
private int _charBufferSize;
|
||||
private int _charBufferCount;
|
||||
|
||||
|
|
@ -44,10 +48,54 @@ namespace Microsoft.AspNet.Mvc
|
|||
|
||||
_stream = stream;
|
||||
Encoding = encoding;
|
||||
_encoder = encoding.GetEncoder();
|
||||
_charBufferSize = bufferSize;
|
||||
_charBuffer = new char[bufferSize];
|
||||
_byteBuffer = new byte[encoding.GetMaxByteCount(bufferSize)];
|
||||
|
||||
_encoder = encoding.GetEncoder();
|
||||
_byteBuffer = new ArraySegment<byte>(new byte[encoding.GetMaxByteCount(bufferSize)]);
|
||||
_charBuffer = new ArraySegment<char>(new char[bufferSize]);
|
||||
}
|
||||
|
||||
public HttpResponseStreamWriter(
|
||||
Stream stream,
|
||||
Encoding encoding,
|
||||
LeasedArraySegment<byte> leasedByteBuffer,
|
||||
LeasedArraySegment<char> leasedCharBuffer)
|
||||
{
|
||||
if (stream == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(stream));
|
||||
}
|
||||
|
||||
if (encoding == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(encoding));
|
||||
}
|
||||
|
||||
if (leasedByteBuffer == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(leasedByteBuffer));
|
||||
}
|
||||
|
||||
if (leasedCharBuffer == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(leasedCharBuffer));
|
||||
}
|
||||
|
||||
_stream = stream;
|
||||
Encoding = encoding;
|
||||
_leasedByteBuffer = leasedByteBuffer;
|
||||
_leasedCharBuffer = leasedCharBuffer;
|
||||
|
||||
_encoder = encoding.GetEncoder();
|
||||
_byteBuffer = leasedByteBuffer.Data;
|
||||
_charBuffer = leasedCharBuffer.Data;
|
||||
|
||||
// We need to compute the usable size of the char buffer based on the size of the byte buffer.
|
||||
// Encoder.GetBytes assumes that the entirety of the byte[] passed in can be used, and that's not the
|
||||
// case with ArraySegments.
|
||||
_charBufferSize = Math.Min(
|
||||
leasedCharBuffer.Data.Count,
|
||||
encoding.GetMaxCharCount(leasedByteBuffer.Data.Count));
|
||||
}
|
||||
|
||||
public override Encoding Encoding { get; }
|
||||
|
|
@ -59,7 +107,7 @@ namespace Microsoft.AspNet.Mvc
|
|||
FlushInternal();
|
||||
}
|
||||
|
||||
_charBuffer[_charBufferCount] = value;
|
||||
_charBuffer.Array[_charBuffer.Offset + _charBufferCount] = value;
|
||||
_charBufferCount++;
|
||||
}
|
||||
|
||||
|
|
@ -108,7 +156,7 @@ namespace Microsoft.AspNet.Mvc
|
|||
await FlushInternalAsync();
|
||||
}
|
||||
|
||||
_charBuffer[_charBufferCount] = value;
|
||||
_charBuffer.Array[_charBuffer.Offset + _charBufferCount] = value;
|
||||
_charBufferCount++;
|
||||
}
|
||||
|
||||
|
|
@ -168,6 +216,16 @@ namespace Microsoft.AspNet.Mvc
|
|||
protected override void Dispose(bool disposing)
|
||||
{
|
||||
FlushInternal(flushStream: false, flushEncoder: true);
|
||||
|
||||
if (_leasedByteBuffer != null)
|
||||
{
|
||||
_leasedByteBuffer.Owner.Return(_leasedByteBuffer);
|
||||
}
|
||||
|
||||
if (_leasedCharBuffer != null)
|
||||
{
|
||||
_leasedCharBuffer.Owner.Return(_leasedCharBuffer);
|
||||
}
|
||||
}
|
||||
|
||||
private void FlushInternal(bool flushStream = false, bool flushEncoder = false)
|
||||
|
|
@ -177,10 +235,17 @@ namespace Microsoft.AspNet.Mvc
|
|||
return;
|
||||
}
|
||||
|
||||
var count = _encoder.GetBytes(_charBuffer, 0, _charBufferCount, _byteBuffer, 0, flushEncoder);
|
||||
var count = _encoder.GetBytes(
|
||||
_charBuffer.Array,
|
||||
_charBuffer.Offset,
|
||||
_charBufferCount,
|
||||
_byteBuffer.Array,
|
||||
_byteBuffer.Offset,
|
||||
flushEncoder);
|
||||
|
||||
if (count > 0)
|
||||
{
|
||||
_stream.Write(_byteBuffer, 0, count);
|
||||
_stream.Write(_byteBuffer.Array, _byteBuffer.Offset, count);
|
||||
}
|
||||
|
||||
_charBufferCount = 0;
|
||||
|
|
@ -198,10 +263,17 @@ namespace Microsoft.AspNet.Mvc
|
|||
return;
|
||||
}
|
||||
|
||||
var count = _encoder.GetBytes(_charBuffer, 0, _charBufferCount, _byteBuffer, 0, flushEncoder);
|
||||
var count = _encoder.GetBytes(
|
||||
_charBuffer.Array,
|
||||
_charBuffer.Offset,
|
||||
_charBufferCount,
|
||||
_byteBuffer.Array,
|
||||
_byteBuffer.Offset,
|
||||
flushEncoder);
|
||||
|
||||
if (count > 0)
|
||||
{
|
||||
await _stream.WriteAsync(_byteBuffer, 0, count);
|
||||
await _stream.WriteAsync(_byteBuffer.Array, _byteBuffer.Offset, count);
|
||||
}
|
||||
|
||||
_charBufferCount = 0;
|
||||
|
|
@ -218,8 +290,8 @@ namespace Microsoft.AspNet.Mvc
|
|||
|
||||
value.CopyTo(
|
||||
sourceIndex: index,
|
||||
destination: _charBuffer,
|
||||
destinationIndex: _charBufferCount,
|
||||
destination: _charBuffer.Array,
|
||||
destinationIndex: _charBuffer.Offset + _charBufferCount,
|
||||
count: remaining);
|
||||
|
||||
_charBufferCount += remaining;
|
||||
|
|
@ -234,8 +306,8 @@ namespace Microsoft.AspNet.Mvc
|
|||
Buffer.BlockCopy(
|
||||
src: values,
|
||||
srcOffset: index * sizeof(char),
|
||||
dst: _charBuffer,
|
||||
dstOffset: _charBufferCount * sizeof(char),
|
||||
dst: _charBuffer.Array,
|
||||
dstOffset: (_charBuffer.Offset + _charBufferCount) * sizeof(char),
|
||||
count: remaining * sizeof(char));
|
||||
|
||||
_charBufferCount += remaining;
|
||||
|
|
@ -244,4 +316,3 @@ namespace Microsoft.AspNet.Mvc
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -0,0 +1,22 @@
|
|||
// 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.IO;
|
||||
using System.Text;
|
||||
|
||||
namespace Microsoft.AspNet.Mvc.Infrastructure
|
||||
{
|
||||
/// <summary>
|
||||
/// Creates <see cref="TextWriter"/> instances for writing to <see cref="Http.HttpResponse.Body"/>.
|
||||
/// </summary>
|
||||
public interface IHttpResponseStreamWriterFactory
|
||||
{
|
||||
/// <summary>
|
||||
/// Creates a new <see cref="TextWriter"/>.
|
||||
/// </summary>
|
||||
/// <param name="stream">The <see cref="Stream"/>, usually <see cref="Http.HttpResponse.Body"/>.</param>
|
||||
/// <param name="encoding">The <see cref="Encoding"/>, usually <see cref="Encoding.UTF8"/>.</param>
|
||||
/// <returns>A <see cref="TextWriter"/>.</returns>
|
||||
TextWriter CreateWriter(Stream stream, Encoding encoding);
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,90 @@
|
|||
// 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.Framework.MemoryPool;
|
||||
|
||||
namespace Microsoft.AspNet.Mvc.Infrastructure
|
||||
{
|
||||
/// <summary>
|
||||
/// An <see cref="IHttpResponseStreamWriterFactory"/> that uses pooled buffers.
|
||||
/// </summary>
|
||||
public class MemoryPoolHttpResponseStreamWriterFactory : IHttpResponseStreamWriterFactory
|
||||
{
|
||||
/// <summary>
|
||||
/// The default size of created buffers.
|
||||
/// </summary>
|
||||
public static readonly int DefaultBufferSize = 4 * 1024; // 4KB
|
||||
|
||||
private readonly IArraySegmentPool<byte> _bytePool;
|
||||
private readonly IArraySegmentPool<char> _charPool;
|
||||
|
||||
/// <summary>
|
||||
/// Creates a new <see cref="MemoryPoolHttpResponseStreamWriterFactory"/>.
|
||||
/// </summary>
|
||||
/// <param name="bytePool">
|
||||
/// The <see cref="IArraySegmentPool{byte}"/> for creating <see cref="byte"/> buffers.
|
||||
/// </param>
|
||||
/// <param name="charPool">
|
||||
/// The <see cref="IArraySegmentPool{char}"/> for creating <see cref="char"/> buffers.
|
||||
/// </param>
|
||||
public MemoryPoolHttpResponseStreamWriterFactory(
|
||||
IArraySegmentPool<byte> bytePool,
|
||||
IArraySegmentPool<char> charPool)
|
||||
{
|
||||
if (bytePool == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(bytePool));
|
||||
}
|
||||
|
||||
if (charPool == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(charPool));
|
||||
}
|
||||
|
||||
_bytePool = bytePool;
|
||||
_charPool = charPool;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public TextWriter CreateWriter(Stream stream, Encoding encoding)
|
||||
{
|
||||
if (stream == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(stream));
|
||||
}
|
||||
|
||||
if (encoding == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(encoding));
|
||||
}
|
||||
|
||||
LeasedArraySegment<byte> bytes = null;
|
||||
LeasedArraySegment<char> chars = null;
|
||||
|
||||
try
|
||||
{
|
||||
bytes = _bytePool.Lease(DefaultBufferSize);
|
||||
chars = _charPool.Lease(DefaultBufferSize);
|
||||
|
||||
return new HttpResponseStreamWriter(stream, encoding, bytes, chars);
|
||||
}
|
||||
catch
|
||||
{
|
||||
if (bytes != null)
|
||||
{
|
||||
bytes.Owner.Return(bytes);
|
||||
}
|
||||
|
||||
if (chars != null)
|
||||
{
|
||||
chars.Owner.Return(chars);
|
||||
}
|
||||
|
||||
throw;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -19,6 +19,7 @@
|
|||
"type": "build"
|
||||
},
|
||||
"Microsoft.Framework.Logging.Abstractions": "1.0.0-*",
|
||||
"Microsoft.Framework.MemoryPool": "1.0.0-*",
|
||||
"Microsoft.Framework.PropertyActivator.Sources": {
|
||||
"version": "1.0.0-*",
|
||||
"type": "build"
|
||||
|
|
|
|||
|
|
@ -4,6 +4,7 @@
|
|||
using System;
|
||||
using System.Diagnostics.Tracing;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.AspNet.Mvc.Infrastructure;
|
||||
using Microsoft.AspNet.Mvc.ViewEngines;
|
||||
using Microsoft.Framework.Logging;
|
||||
using Microsoft.Framework.OptionsModel;
|
||||
|
|
@ -19,15 +20,17 @@ namespace Microsoft.AspNet.Mvc.ViewFeatures
|
|||
/// Creates a new <see cref="PartialViewResultExecutor"/>.
|
||||
/// </summary>
|
||||
/// <param name="viewOptions">The <see cref="IOptions{MvcViewOptions}"/>.</param>
|
||||
/// <param name="writerFactory">The <see cref="IHttpResponseStreamWriterFactory"/>.</param>
|
||||
/// <param name="viewEngine">The <see cref="ICompositeViewEngine"/>.</param>
|
||||
/// <param name="telemetry">The <see cref="TelemetrySource"/>.</param>
|
||||
/// <param name="loggerFactory">The <see cref="ILoggerFactory"/>.</param>
|
||||
public PartialViewResultExecutor(
|
||||
IOptions<MvcViewOptions> viewOptions,
|
||||
IHttpResponseStreamWriterFactory writerFactory,
|
||||
ICompositeViewEngine viewEngine,
|
||||
TelemetrySource telemetry,
|
||||
ILoggerFactory loggerFactory)
|
||||
: base(viewOptions, viewEngine, telemetry)
|
||||
: base(viewOptions, writerFactory, viewEngine, telemetry)
|
||||
{
|
||||
if (loggerFactory == null)
|
||||
{
|
||||
|
|
|
|||
|
|
@ -5,6 +5,7 @@ using System;
|
|||
using System.Diagnostics.Tracing;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.AspNet.Mvc.Infrastructure;
|
||||
using Microsoft.AspNet.Mvc.Rendering;
|
||||
using Microsoft.AspNet.Mvc.ViewEngines;
|
||||
using Microsoft.Framework.OptionsModel;
|
||||
|
|
@ -29,10 +30,12 @@ namespace Microsoft.AspNet.Mvc.ViewFeatures
|
|||
/// Creates a new <see cref="ViewExecutor"/>.
|
||||
/// </summary>
|
||||
/// <param name="viewOptions">The <see cref="IOptions{MvcViewOptions}"/>.</param>
|
||||
/// <param name="writerFactory">The <see cref="IHttpResponseStreamWriterFactory"/>.</param>
|
||||
/// <param name="viewEngine">The <see cref="ICompositeViewEngine"/>.</param>
|
||||
/// <param name="telemetry">The <see cref="TelemetrySource"/>.</param>
|
||||
public ViewExecutor(
|
||||
IOptions<MvcViewOptions> viewOptions,
|
||||
IHttpResponseStreamWriterFactory writerFactory,
|
||||
ICompositeViewEngine viewEngine,
|
||||
TelemetrySource telemetry)
|
||||
{
|
||||
|
|
@ -41,6 +44,11 @@ namespace Microsoft.AspNet.Mvc.ViewFeatures
|
|||
throw new ArgumentNullException(nameof(viewOptions));
|
||||
}
|
||||
|
||||
if (writerFactory == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(writerFactory));
|
||||
}
|
||||
|
||||
if (viewEngine == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(viewEngine));
|
||||
|
|
@ -52,6 +60,7 @@ namespace Microsoft.AspNet.Mvc.ViewFeatures
|
|||
}
|
||||
|
||||
ViewOptions = viewOptions.Value;
|
||||
WriterFactory = writerFactory;
|
||||
ViewEngine = viewEngine;
|
||||
Telemetry = telemetry;
|
||||
}
|
||||
|
|
@ -71,6 +80,11 @@ namespace Microsoft.AspNet.Mvc.ViewFeatures
|
|||
/// </summary>
|
||||
protected MvcViewOptions ViewOptions { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the <see cref="IHttpResponseStreamWriterFactory"/>.
|
||||
/// </summary>
|
||||
protected IHttpResponseStreamWriterFactory WriterFactory { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Executes a view asynchronously.
|
||||
/// </summary>
|
||||
|
|
@ -134,7 +148,7 @@ namespace Microsoft.AspNet.Mvc.ViewFeatures
|
|||
}
|
||||
|
||||
var encoding = contentType?.Encoding ?? DefaultContentType.Encoding;
|
||||
using (var writer = new HttpResponseStreamWriter(response.Body, encoding))
|
||||
using (var writer = WriterFactory.CreateWriter(response.Body, encoding))
|
||||
{
|
||||
var viewContext = new ViewContext(
|
||||
actionContext,
|
||||
|
|
|
|||
|
|
@ -4,6 +4,7 @@
|
|||
using System;
|
||||
using System.Diagnostics.Tracing;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.AspNet.Mvc.Infrastructure;
|
||||
using Microsoft.AspNet.Mvc.ViewEngines;
|
||||
using Microsoft.Framework.Logging;
|
||||
using Microsoft.Framework.OptionsModel;
|
||||
|
|
@ -19,15 +20,17 @@ namespace Microsoft.AspNet.Mvc.ViewFeatures
|
|||
/// Creates a new <see cref="ViewResultExecutor"/>.
|
||||
/// </summary>
|
||||
/// <param name="viewOptions">The <see cref="IOptions{MvcViewOptions}"/>.</param>
|
||||
/// <param name="writerFactory">The <see cref="IHttpResponseStreamWriterFactory"/>.</param>
|
||||
/// <param name="viewEngine">The <see cref="ICompositeViewEngine"/>.</param>
|
||||
/// <param name="telemetry">The <see cref="TelemetrySource"/>.</param>
|
||||
/// <param name="loggerFactory">The <see cref="ILoggerFactory"/>.</param>
|
||||
public ViewResultExecutor(
|
||||
IOptions<MvcViewOptions> viewOptions,
|
||||
IHttpResponseStreamWriterFactory writerFactory,
|
||||
ICompositeViewEngine viewEngine,
|
||||
TelemetrySource telemetry,
|
||||
ILoggerFactory loggerFactory)
|
||||
: base(viewOptions, viewEngine, telemetry)
|
||||
: base(viewOptions, writerFactory, viewEngine, telemetry)
|
||||
{
|
||||
if (loggerFactory == null)
|
||||
{
|
||||
|
|
|
|||
|
|
@ -1,10 +1,12 @@
|
|||
// 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 System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.Framework.MemoryPool;
|
||||
using Xunit;
|
||||
|
||||
namespace Microsoft.AspNet.Mvc
|
||||
|
|
@ -355,6 +357,128 @@ namespace Microsoft.AspNet.Mvc
|
|||
Assert.Equal(expectedBytes, stream.ToArray());
|
||||
}
|
||||
|
||||
// None of the code in HttpResponseStreamWriter differs significantly when using pooled buffers.
|
||||
//
|
||||
// This test effectively verifies that things are correctly constructed and disposed. Pooled buffers
|
||||
// throw on the finalizer thread if not disposed, so that's why it's complicated.
|
||||
[Fact]
|
||||
public void HttpResponseStreamWriter_UsingPooledBuffers()
|
||||
{
|
||||
// Arrange
|
||||
var encoding = Encoding.UTF8;
|
||||
var stream = new MemoryStream();
|
||||
|
||||
var expectedBytes = encoding.GetBytes("Hello, World!");
|
||||
|
||||
using (var bytePool = new DefaultArraySegmentPool<byte>())
|
||||
{
|
||||
using (var charPool = new DefaultArraySegmentPool<char>())
|
||||
{
|
||||
LeasedArraySegment<byte> bytes = null;
|
||||
LeasedArraySegment<char> chars = null;
|
||||
HttpResponseStreamWriter writer;
|
||||
|
||||
try
|
||||
{
|
||||
bytes = bytePool.Lease(4096);
|
||||
chars = charPool.Lease(4096);
|
||||
|
||||
writer = new HttpResponseStreamWriter(stream, encoding, 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.Equal(expectedBytes, stream.ToArray());
|
||||
}
|
||||
|
||||
// This covers the case where we need to limit the usable region of the char buffer
|
||||
// based on the size of the byte buffer. See comments in the constructor.
|
||||
[Fact]
|
||||
public void HttpResponseStreamWriter_UsingPooledBuffers_SmallByteBuffer()
|
||||
{
|
||||
// Arrange
|
||||
var encoding = Encoding.UTF8;
|
||||
var stream = new MemoryStream();
|
||||
|
||||
var charBufferSize = encoding.GetMaxCharCount(1024);
|
||||
|
||||
// This content is bigger than the byte buffer can hold, so it will need to be split
|
||||
// into two separate encoding operations.
|
||||
var content = new string('a', charBufferSize + 1);
|
||||
var expectedBytes = encoding.GetBytes(content);
|
||||
|
||||
using (var bytePool = new DefaultArraySegmentPool<byte>())
|
||||
{
|
||||
using (var charPool = new DefaultArraySegmentPool<char>())
|
||||
{
|
||||
LeasedArraySegment<byte> bytes = null;
|
||||
LeasedArraySegment<char> chars = null;
|
||||
HttpResponseStreamWriter writer;
|
||||
|
||||
try
|
||||
{
|
||||
bytes = bytePool.Lease(1024);
|
||||
chars = charPool.Lease(4096);
|
||||
|
||||
writer = new HttpResponseStreamWriter(stream, encoding, bytes, chars);
|
||||
}
|
||||
catch
|
||||
{
|
||||
if (bytes != null)
|
||||
{
|
||||
bytes.Owner.Return(bytes);
|
||||
}
|
||||
|
||||
if (chars != null)
|
||||
{
|
||||
chars.Owner.Return(chars);
|
||||
}
|
||||
|
||||
throw;
|
||||
}
|
||||
|
||||
// Zero the byte buffer because we're going to examine it.
|
||||
Array.Clear(bytes.Data.Array, 0, bytes.Data.Array.Length);
|
||||
|
||||
// Act
|
||||
using (writer)
|
||||
{
|
||||
writer.Write(content);
|
||||
}
|
||||
|
||||
// Verify that we didn't buffer overflow 'our' region of the underlying array.
|
||||
if (bytes.Data.Array.Length > bytes.Data.Count)
|
||||
{
|
||||
Assert.Equal((byte)0, bytes.Data.Array[bytes.Data.Count]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Assert
|
||||
Assert.Equal(expectedBytes, stream.ToArray());
|
||||
}
|
||||
|
||||
private class TestMemoryStream : MemoryStream
|
||||
{
|
||||
private int _flushCallCount;
|
||||
|
|
|
|||
|
|
@ -0,0 +1,50 @@
|
|||
// 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.Framework.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(MemoryPoolHttpResponseStreamWriterFactory.DefaultBufferSize))
|
||||
.Returns(new LeasedArraySegment<byte>(new ArraySegment<byte>(new byte[0]), 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[0]), charPool.Object));
|
||||
charPool
|
||||
.Setup(p => p.Return(It.IsAny<LeasedArraySegment<char>>()))
|
||||
.Verifiable();
|
||||
|
||||
var encoding = new Mock<Encoding>(MockBehavior.Strict);
|
||||
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();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -86,6 +86,9 @@ namespace Microsoft.AspNet.Mvc.FunctionalTests
|
|||
var assemblyProvider = CreateAssemblyProvider(applicationWebSiteName);
|
||||
services.AddInstance(assemblyProvider);
|
||||
|
||||
// Avoid using pooled memory, we don't have a guarantee that our services will get disposed.
|
||||
services.AddInstance<IHttpResponseStreamWriterFactory>(new TestHttpResponseStreamWriterFactory());
|
||||
|
||||
if (configureServices != null)
|
||||
{
|
||||
configureServices(services);
|
||||
|
|
|
|||
|
|
@ -0,0 +1,17 @@
|
|||
// 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.IO;
|
||||
using System.Text;
|
||||
using Microsoft.AspNet.Mvc.Infrastructure;
|
||||
|
||||
namespace Microsoft.AspNet.Mvc
|
||||
{
|
||||
public class TestHttpResponseStreamWriterFactory : IHttpResponseStreamWriterFactory
|
||||
{
|
||||
public TextWriter CreateWriter(Stream stream, Encoding encoding)
|
||||
{
|
||||
return new HttpResponseStreamWriter(stream, encoding);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -106,6 +106,7 @@ namespace Microsoft.AspNet.Mvc
|
|||
var options = new TestOptionsManager<MvcViewOptions>();
|
||||
var viewExecutor = new PartialViewResultExecutor(
|
||||
options,
|
||||
new TestHttpResponseStreamWriterFactory(),
|
||||
new CompositeViewEngine(options),
|
||||
new TelemetryListener("Microsoft.AspNet"),
|
||||
NullLoggerFactory.Instance);
|
||||
|
|
|
|||
|
|
@ -8,7 +8,6 @@ using Microsoft.AspNet.Mvc.Abstractions;
|
|||
using Microsoft.AspNet.Mvc.ModelBinding;
|
||||
using Microsoft.AspNet.Mvc.ViewEngines;
|
||||
using Microsoft.AspNet.Routing;
|
||||
using Microsoft.Framework.Logging;
|
||||
using Microsoft.Framework.Logging.Testing;
|
||||
using Microsoft.Net.Http.Headers;
|
||||
using Moq;
|
||||
|
|
@ -217,6 +216,7 @@ namespace Microsoft.AspNet.Mvc.ViewFeatures
|
|||
|
||||
var viewExecutor = new PartialViewResultExecutor(
|
||||
options,
|
||||
new TestHttpResponseStreamWriterFactory(),
|
||||
new CompositeViewEngine(options),
|
||||
telemetry,
|
||||
NullLoggerFactory.Instance);
|
||||
|
|
|
|||
|
|
@ -286,6 +286,7 @@ namespace Microsoft.AspNet.Mvc.ViewFeatures
|
|||
|
||||
return new ViewExecutor(
|
||||
new TestOptionsManager<MvcViewOptions>(),
|
||||
new TestHttpResponseStreamWriterFactory(),
|
||||
new Mock<ICompositeViewEngine>(MockBehavior.Strict).Object,
|
||||
listener);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -216,6 +216,7 @@ namespace Microsoft.AspNet.Mvc.ViewFeatures
|
|||
|
||||
var viewExecutor = new ViewResultExecutor(
|
||||
options,
|
||||
new TestHttpResponseStreamWriterFactory(),
|
||||
new CompositeViewEngine(options),
|
||||
telemetry,
|
||||
NullLoggerFactory.Instance);
|
||||
|
|
|
|||
|
|
@ -13,7 +13,6 @@ using Microsoft.AspNet.Mvc.ViewEngines;
|
|||
using Microsoft.AspNet.Mvc.ViewFeatures;
|
||||
using Microsoft.AspNet.Routing;
|
||||
using Microsoft.Framework.DependencyInjection;
|
||||
using Microsoft.Framework.Logging;
|
||||
using Microsoft.Framework.Logging.Testing;
|
||||
using Moq;
|
||||
using Xunit;
|
||||
|
|
@ -107,6 +106,7 @@ namespace Microsoft.AspNet.Mvc
|
|||
var options = new TestOptionsManager<MvcViewOptions>();
|
||||
var viewExecutor = new ViewResultExecutor(
|
||||
options,
|
||||
new TestHttpResponseStreamWriterFactory(),
|
||||
new CompositeViewEngine(options),
|
||||
new TelemetryListener("Microsoft.AspNet"),
|
||||
NullLoggerFactory.Instance);
|
||||
|
|
|
|||
Loading…
Reference in New Issue