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.Mvc.Routing;
|
||||||
using Microsoft.AspNet.Routing;
|
using Microsoft.AspNet.Routing;
|
||||||
using Microsoft.Framework.DependencyInjection.Extensions;
|
using Microsoft.Framework.DependencyInjection.Extensions;
|
||||||
|
using Microsoft.Framework.MemoryPool;
|
||||||
using Microsoft.Framework.OptionsModel;
|
using Microsoft.Framework.OptionsModel;
|
||||||
|
|
||||||
namespace Microsoft.Framework.DependencyInjection
|
namespace Microsoft.Framework.DependencyInjection
|
||||||
|
|
@ -137,6 +138,9 @@ namespace Microsoft.Framework.DependencyInjection
|
||||||
services.TryAddSingleton<IActionContextAccessor, ActionContextAccessor>();
|
services.TryAddSingleton<IActionContextAccessor, ActionContextAccessor>();
|
||||||
services.TryAddSingleton<IActionBindingContextAccessor, ActionBindingContextAccessor>();
|
services.TryAddSingleton<IActionBindingContextAccessor, ActionBindingContextAccessor>();
|
||||||
services.TryAddSingleton<IUrlHelper, UrlHelper>();
|
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)
|
private static void ConfigureDefaultServices(IServiceCollection services)
|
||||||
|
|
|
||||||
|
|
@ -5,6 +5,7 @@ using System;
|
||||||
using System.IO;
|
using System.IO;
|
||||||
using System.Text;
|
using System.Text;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
|
using Microsoft.Framework.MemoryPool;
|
||||||
|
|
||||||
namespace Microsoft.AspNet.Mvc
|
namespace Microsoft.AspNet.Mvc
|
||||||
{
|
{
|
||||||
|
|
@ -18,10 +19,13 @@ namespace Microsoft.AspNet.Mvc
|
||||||
/// Default buffer size.
|
/// Default buffer size.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public const int DefaultBufferSize = 1024;
|
public const int DefaultBufferSize = 1024;
|
||||||
|
|
||||||
private readonly Stream _stream;
|
private readonly Stream _stream;
|
||||||
private Encoder _encoder;
|
private Encoder _encoder;
|
||||||
private byte[] _byteBuffer;
|
private LeasedArraySegment<byte> _leasedByteBuffer;
|
||||||
private char[] _charBuffer;
|
private LeasedArraySegment<char> _leasedCharBuffer;
|
||||||
|
private ArraySegment<byte> _byteBuffer;
|
||||||
|
private ArraySegment<char> _charBuffer;
|
||||||
private int _charBufferSize;
|
private int _charBufferSize;
|
||||||
private int _charBufferCount;
|
private int _charBufferCount;
|
||||||
|
|
||||||
|
|
@ -44,10 +48,54 @@ namespace Microsoft.AspNet.Mvc
|
||||||
|
|
||||||
_stream = stream;
|
_stream = stream;
|
||||||
Encoding = encoding;
|
Encoding = encoding;
|
||||||
_encoder = encoding.GetEncoder();
|
|
||||||
_charBufferSize = bufferSize;
|
_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; }
|
public override Encoding Encoding { get; }
|
||||||
|
|
@ -59,7 +107,7 @@ namespace Microsoft.AspNet.Mvc
|
||||||
FlushInternal();
|
FlushInternal();
|
||||||
}
|
}
|
||||||
|
|
||||||
_charBuffer[_charBufferCount] = value;
|
_charBuffer.Array[_charBuffer.Offset + _charBufferCount] = value;
|
||||||
_charBufferCount++;
|
_charBufferCount++;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -108,7 +156,7 @@ namespace Microsoft.AspNet.Mvc
|
||||||
await FlushInternalAsync();
|
await FlushInternalAsync();
|
||||||
}
|
}
|
||||||
|
|
||||||
_charBuffer[_charBufferCount] = value;
|
_charBuffer.Array[_charBuffer.Offset + _charBufferCount] = value;
|
||||||
_charBufferCount++;
|
_charBufferCount++;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -168,6 +216,16 @@ namespace Microsoft.AspNet.Mvc
|
||||||
protected override void Dispose(bool disposing)
|
protected override void Dispose(bool disposing)
|
||||||
{
|
{
|
||||||
FlushInternal(flushStream: false, flushEncoder: true);
|
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)
|
private void FlushInternal(bool flushStream = false, bool flushEncoder = false)
|
||||||
|
|
@ -177,10 +235,17 @@ namespace Microsoft.AspNet.Mvc
|
||||||
return;
|
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)
|
if (count > 0)
|
||||||
{
|
{
|
||||||
_stream.Write(_byteBuffer, 0, count);
|
_stream.Write(_byteBuffer.Array, _byteBuffer.Offset, count);
|
||||||
}
|
}
|
||||||
|
|
||||||
_charBufferCount = 0;
|
_charBufferCount = 0;
|
||||||
|
|
@ -198,10 +263,17 @@ namespace Microsoft.AspNet.Mvc
|
||||||
return;
|
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)
|
if (count > 0)
|
||||||
{
|
{
|
||||||
await _stream.WriteAsync(_byteBuffer, 0, count);
|
await _stream.WriteAsync(_byteBuffer.Array, _byteBuffer.Offset, count);
|
||||||
}
|
}
|
||||||
|
|
||||||
_charBufferCount = 0;
|
_charBufferCount = 0;
|
||||||
|
|
@ -218,8 +290,8 @@ namespace Microsoft.AspNet.Mvc
|
||||||
|
|
||||||
value.CopyTo(
|
value.CopyTo(
|
||||||
sourceIndex: index,
|
sourceIndex: index,
|
||||||
destination: _charBuffer,
|
destination: _charBuffer.Array,
|
||||||
destinationIndex: _charBufferCount,
|
destinationIndex: _charBuffer.Offset + _charBufferCount,
|
||||||
count: remaining);
|
count: remaining);
|
||||||
|
|
||||||
_charBufferCount += remaining;
|
_charBufferCount += remaining;
|
||||||
|
|
@ -234,8 +306,8 @@ namespace Microsoft.AspNet.Mvc
|
||||||
Buffer.BlockCopy(
|
Buffer.BlockCopy(
|
||||||
src: values,
|
src: values,
|
||||||
srcOffset: index * sizeof(char),
|
srcOffset: index * sizeof(char),
|
||||||
dst: _charBuffer,
|
dst: _charBuffer.Array,
|
||||||
dstOffset: _charBufferCount * sizeof(char),
|
dstOffset: (_charBuffer.Offset + _charBufferCount) * sizeof(char),
|
||||||
count: remaining * sizeof(char));
|
count: remaining * sizeof(char));
|
||||||
|
|
||||||
_charBufferCount += remaining;
|
_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"
|
"type": "build"
|
||||||
},
|
},
|
||||||
"Microsoft.Framework.Logging.Abstractions": "1.0.0-*",
|
"Microsoft.Framework.Logging.Abstractions": "1.0.0-*",
|
||||||
|
"Microsoft.Framework.MemoryPool": "1.0.0-*",
|
||||||
"Microsoft.Framework.PropertyActivator.Sources": {
|
"Microsoft.Framework.PropertyActivator.Sources": {
|
||||||
"version": "1.0.0-*",
|
"version": "1.0.0-*",
|
||||||
"type": "build"
|
"type": "build"
|
||||||
|
|
|
||||||
|
|
@ -4,6 +4,7 @@
|
||||||
using System;
|
using System;
|
||||||
using System.Diagnostics.Tracing;
|
using System.Diagnostics.Tracing;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
|
using Microsoft.AspNet.Mvc.Infrastructure;
|
||||||
using Microsoft.AspNet.Mvc.ViewEngines;
|
using Microsoft.AspNet.Mvc.ViewEngines;
|
||||||
using Microsoft.Framework.Logging;
|
using Microsoft.Framework.Logging;
|
||||||
using Microsoft.Framework.OptionsModel;
|
using Microsoft.Framework.OptionsModel;
|
||||||
|
|
@ -19,15 +20,17 @@ namespace Microsoft.AspNet.Mvc.ViewFeatures
|
||||||
/// Creates a new <see cref="PartialViewResultExecutor"/>.
|
/// Creates a new <see cref="PartialViewResultExecutor"/>.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="viewOptions">The <see cref="IOptions{MvcViewOptions}"/>.</param>
|
/// <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="viewEngine">The <see cref="ICompositeViewEngine"/>.</param>
|
||||||
/// <param name="telemetry">The <see cref="TelemetrySource"/>.</param>
|
/// <param name="telemetry">The <see cref="TelemetrySource"/>.</param>
|
||||||
/// <param name="loggerFactory">The <see cref="ILoggerFactory"/>.</param>
|
/// <param name="loggerFactory">The <see cref="ILoggerFactory"/>.</param>
|
||||||
public PartialViewResultExecutor(
|
public PartialViewResultExecutor(
|
||||||
IOptions<MvcViewOptions> viewOptions,
|
IOptions<MvcViewOptions> viewOptions,
|
||||||
|
IHttpResponseStreamWriterFactory writerFactory,
|
||||||
ICompositeViewEngine viewEngine,
|
ICompositeViewEngine viewEngine,
|
||||||
TelemetrySource telemetry,
|
TelemetrySource telemetry,
|
||||||
ILoggerFactory loggerFactory)
|
ILoggerFactory loggerFactory)
|
||||||
: base(viewOptions, viewEngine, telemetry)
|
: base(viewOptions, writerFactory, viewEngine, telemetry)
|
||||||
{
|
{
|
||||||
if (loggerFactory == null)
|
if (loggerFactory == null)
|
||||||
{
|
{
|
||||||
|
|
|
||||||
|
|
@ -5,6 +5,7 @@ using System;
|
||||||
using System.Diagnostics.Tracing;
|
using System.Diagnostics.Tracing;
|
||||||
using System.Text;
|
using System.Text;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
|
using Microsoft.AspNet.Mvc.Infrastructure;
|
||||||
using Microsoft.AspNet.Mvc.Rendering;
|
using Microsoft.AspNet.Mvc.Rendering;
|
||||||
using Microsoft.AspNet.Mvc.ViewEngines;
|
using Microsoft.AspNet.Mvc.ViewEngines;
|
||||||
using Microsoft.Framework.OptionsModel;
|
using Microsoft.Framework.OptionsModel;
|
||||||
|
|
@ -29,10 +30,12 @@ namespace Microsoft.AspNet.Mvc.ViewFeatures
|
||||||
/// Creates a new <see cref="ViewExecutor"/>.
|
/// Creates a new <see cref="ViewExecutor"/>.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="viewOptions">The <see cref="IOptions{MvcViewOptions}"/>.</param>
|
/// <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="viewEngine">The <see cref="ICompositeViewEngine"/>.</param>
|
||||||
/// <param name="telemetry">The <see cref="TelemetrySource"/>.</param>
|
/// <param name="telemetry">The <see cref="TelemetrySource"/>.</param>
|
||||||
public ViewExecutor(
|
public ViewExecutor(
|
||||||
IOptions<MvcViewOptions> viewOptions,
|
IOptions<MvcViewOptions> viewOptions,
|
||||||
|
IHttpResponseStreamWriterFactory writerFactory,
|
||||||
ICompositeViewEngine viewEngine,
|
ICompositeViewEngine viewEngine,
|
||||||
TelemetrySource telemetry)
|
TelemetrySource telemetry)
|
||||||
{
|
{
|
||||||
|
|
@ -41,6 +44,11 @@ namespace Microsoft.AspNet.Mvc.ViewFeatures
|
||||||
throw new ArgumentNullException(nameof(viewOptions));
|
throw new ArgumentNullException(nameof(viewOptions));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (writerFactory == null)
|
||||||
|
{
|
||||||
|
throw new ArgumentNullException(nameof(writerFactory));
|
||||||
|
}
|
||||||
|
|
||||||
if (viewEngine == null)
|
if (viewEngine == null)
|
||||||
{
|
{
|
||||||
throw new ArgumentNullException(nameof(viewEngine));
|
throw new ArgumentNullException(nameof(viewEngine));
|
||||||
|
|
@ -52,6 +60,7 @@ namespace Microsoft.AspNet.Mvc.ViewFeatures
|
||||||
}
|
}
|
||||||
|
|
||||||
ViewOptions = viewOptions.Value;
|
ViewOptions = viewOptions.Value;
|
||||||
|
WriterFactory = writerFactory;
|
||||||
ViewEngine = viewEngine;
|
ViewEngine = viewEngine;
|
||||||
Telemetry = telemetry;
|
Telemetry = telemetry;
|
||||||
}
|
}
|
||||||
|
|
@ -71,6 +80,11 @@ namespace Microsoft.AspNet.Mvc.ViewFeatures
|
||||||
/// </summary>
|
/// </summary>
|
||||||
protected MvcViewOptions ViewOptions { get; }
|
protected MvcViewOptions ViewOptions { get; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the <see cref="IHttpResponseStreamWriterFactory"/>.
|
||||||
|
/// </summary>
|
||||||
|
protected IHttpResponseStreamWriterFactory WriterFactory { get; }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Executes a view asynchronously.
|
/// Executes a view asynchronously.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
|
@ -134,7 +148,7 @@ namespace Microsoft.AspNet.Mvc.ViewFeatures
|
||||||
}
|
}
|
||||||
|
|
||||||
var encoding = contentType?.Encoding ?? DefaultContentType.Encoding;
|
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(
|
var viewContext = new ViewContext(
|
||||||
actionContext,
|
actionContext,
|
||||||
|
|
|
||||||
|
|
@ -4,6 +4,7 @@
|
||||||
using System;
|
using System;
|
||||||
using System.Diagnostics.Tracing;
|
using System.Diagnostics.Tracing;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
|
using Microsoft.AspNet.Mvc.Infrastructure;
|
||||||
using Microsoft.AspNet.Mvc.ViewEngines;
|
using Microsoft.AspNet.Mvc.ViewEngines;
|
||||||
using Microsoft.Framework.Logging;
|
using Microsoft.Framework.Logging;
|
||||||
using Microsoft.Framework.OptionsModel;
|
using Microsoft.Framework.OptionsModel;
|
||||||
|
|
@ -19,15 +20,17 @@ namespace Microsoft.AspNet.Mvc.ViewFeatures
|
||||||
/// Creates a new <see cref="ViewResultExecutor"/>.
|
/// Creates a new <see cref="ViewResultExecutor"/>.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="viewOptions">The <see cref="IOptions{MvcViewOptions}"/>.</param>
|
/// <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="viewEngine">The <see cref="ICompositeViewEngine"/>.</param>
|
||||||
/// <param name="telemetry">The <see cref="TelemetrySource"/>.</param>
|
/// <param name="telemetry">The <see cref="TelemetrySource"/>.</param>
|
||||||
/// <param name="loggerFactory">The <see cref="ILoggerFactory"/>.</param>
|
/// <param name="loggerFactory">The <see cref="ILoggerFactory"/>.</param>
|
||||||
public ViewResultExecutor(
|
public ViewResultExecutor(
|
||||||
IOptions<MvcViewOptions> viewOptions,
|
IOptions<MvcViewOptions> viewOptions,
|
||||||
|
IHttpResponseStreamWriterFactory writerFactory,
|
||||||
ICompositeViewEngine viewEngine,
|
ICompositeViewEngine viewEngine,
|
||||||
TelemetrySource telemetry,
|
TelemetrySource telemetry,
|
||||||
ILoggerFactory loggerFactory)
|
ILoggerFactory loggerFactory)
|
||||||
: base(viewOptions, viewEngine, telemetry)
|
: base(viewOptions, writerFactory, viewEngine, telemetry)
|
||||||
{
|
{
|
||||||
if (loggerFactory == null)
|
if (loggerFactory == null)
|
||||||
{
|
{
|
||||||
|
|
|
||||||
|
|
@ -1,10 +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;
|
||||||
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.Framework.MemoryPool;
|
||||||
using Xunit;
|
using Xunit;
|
||||||
|
|
||||||
namespace Microsoft.AspNet.Mvc
|
namespace Microsoft.AspNet.Mvc
|
||||||
|
|
@ -355,6 +357,128 @@ namespace Microsoft.AspNet.Mvc
|
||||||
Assert.Equal(expectedBytes, stream.ToArray());
|
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 class TestMemoryStream : MemoryStream
|
||||||
{
|
{
|
||||||
private int _flushCallCount;
|
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);
|
var assemblyProvider = CreateAssemblyProvider(applicationWebSiteName);
|
||||||
services.AddInstance(assemblyProvider);
|
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)
|
if (configureServices != null)
|
||||||
{
|
{
|
||||||
configureServices(services);
|
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 options = new TestOptionsManager<MvcViewOptions>();
|
||||||
var viewExecutor = new PartialViewResultExecutor(
|
var viewExecutor = new PartialViewResultExecutor(
|
||||||
options,
|
options,
|
||||||
|
new TestHttpResponseStreamWriterFactory(),
|
||||||
new CompositeViewEngine(options),
|
new CompositeViewEngine(options),
|
||||||
new TelemetryListener("Microsoft.AspNet"),
|
new TelemetryListener("Microsoft.AspNet"),
|
||||||
NullLoggerFactory.Instance);
|
NullLoggerFactory.Instance);
|
||||||
|
|
|
||||||
|
|
@ -8,7 +8,6 @@ using Microsoft.AspNet.Mvc.Abstractions;
|
||||||
using Microsoft.AspNet.Mvc.ModelBinding;
|
using Microsoft.AspNet.Mvc.ModelBinding;
|
||||||
using Microsoft.AspNet.Mvc.ViewEngines;
|
using Microsoft.AspNet.Mvc.ViewEngines;
|
||||||
using Microsoft.AspNet.Routing;
|
using Microsoft.AspNet.Routing;
|
||||||
using Microsoft.Framework.Logging;
|
|
||||||
using Microsoft.Framework.Logging.Testing;
|
using Microsoft.Framework.Logging.Testing;
|
||||||
using Microsoft.Net.Http.Headers;
|
using Microsoft.Net.Http.Headers;
|
||||||
using Moq;
|
using Moq;
|
||||||
|
|
@ -217,6 +216,7 @@ namespace Microsoft.AspNet.Mvc.ViewFeatures
|
||||||
|
|
||||||
var viewExecutor = new PartialViewResultExecutor(
|
var viewExecutor = new PartialViewResultExecutor(
|
||||||
options,
|
options,
|
||||||
|
new TestHttpResponseStreamWriterFactory(),
|
||||||
new CompositeViewEngine(options),
|
new CompositeViewEngine(options),
|
||||||
telemetry,
|
telemetry,
|
||||||
NullLoggerFactory.Instance);
|
NullLoggerFactory.Instance);
|
||||||
|
|
|
||||||
|
|
@ -286,6 +286,7 @@ namespace Microsoft.AspNet.Mvc.ViewFeatures
|
||||||
|
|
||||||
return new ViewExecutor(
|
return new ViewExecutor(
|
||||||
new TestOptionsManager<MvcViewOptions>(),
|
new TestOptionsManager<MvcViewOptions>(),
|
||||||
|
new TestHttpResponseStreamWriterFactory(),
|
||||||
new Mock<ICompositeViewEngine>(MockBehavior.Strict).Object,
|
new Mock<ICompositeViewEngine>(MockBehavior.Strict).Object,
|
||||||
listener);
|
listener);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -216,6 +216,7 @@ namespace Microsoft.AspNet.Mvc.ViewFeatures
|
||||||
|
|
||||||
var viewExecutor = new ViewResultExecutor(
|
var viewExecutor = new ViewResultExecutor(
|
||||||
options,
|
options,
|
||||||
|
new TestHttpResponseStreamWriterFactory(),
|
||||||
new CompositeViewEngine(options),
|
new CompositeViewEngine(options),
|
||||||
telemetry,
|
telemetry,
|
||||||
NullLoggerFactory.Instance);
|
NullLoggerFactory.Instance);
|
||||||
|
|
|
||||||
|
|
@ -13,7 +13,6 @@ using Microsoft.AspNet.Mvc.ViewEngines;
|
||||||
using Microsoft.AspNet.Mvc.ViewFeatures;
|
using Microsoft.AspNet.Mvc.ViewFeatures;
|
||||||
using Microsoft.AspNet.Routing;
|
using Microsoft.AspNet.Routing;
|
||||||
using Microsoft.Framework.DependencyInjection;
|
using Microsoft.Framework.DependencyInjection;
|
||||||
using Microsoft.Framework.Logging;
|
|
||||||
using Microsoft.Framework.Logging.Testing;
|
using Microsoft.Framework.Logging.Testing;
|
||||||
using Moq;
|
using Moq;
|
||||||
using Xunit;
|
using Xunit;
|
||||||
|
|
@ -107,6 +106,7 @@ namespace Microsoft.AspNet.Mvc
|
||||||
var options = new TestOptionsManager<MvcViewOptions>();
|
var options = new TestOptionsManager<MvcViewOptions>();
|
||||||
var viewExecutor = new ViewResultExecutor(
|
var viewExecutor = new ViewResultExecutor(
|
||||||
options,
|
options,
|
||||||
|
new TestHttpResponseStreamWriterFactory(),
|
||||||
new CompositeViewEngine(options),
|
new CompositeViewEngine(options),
|
||||||
new TelemetryListener("Microsoft.AspNet"),
|
new TelemetryListener("Microsoft.AspNet"),
|
||||||
NullLoggerFactory.Instance);
|
NullLoggerFactory.Instance);
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue