Pool JsonSerializer instances

Fixes #3550
This commit is contained in:
Pranav K 2016-01-14 18:03:48 -08:00
parent fa8c2eac3e
commit cf9221df07
6 changed files with 164 additions and 25 deletions

View File

@ -6,6 +6,7 @@ using Microsoft.AspNet.Mvc;
using Microsoft.AspNet.Mvc.Formatters.Json.Internal;
using Microsoft.AspNet.Mvc.Infrastructure;
using Microsoft.Extensions.DependencyInjection.Extensions;
using Microsoft.Extensions.ObjectPool;
using Microsoft.Extensions.Options;
using Newtonsoft.Json;
@ -54,6 +55,7 @@ namespace Microsoft.Extensions.DependencyInjection
services.TryAddEnumerable(
ServiceDescriptor.Transient<IConfigureOptions<MvcOptions>, MvcJsonMvcOptionsSetup>());
services.TryAddSingleton<JsonResultExecutor>();
services.TryAddSingleton<ObjectPoolProvider, DefaultObjectPoolProvider>();
}
}
}

View File

@ -0,0 +1,32 @@
// 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 Microsoft.Extensions.ObjectPool;
using Newtonsoft.Json;
namespace Microsoft.AspNet.Mvc.Formatters.Internal
{
/// <summary>
/// <see cref="IPooledObjectPolicy{T}"/> for <see cref="JsonSerializer"/>.
/// </summary>
public class JsonSerializerObjectPolicy : IPooledObjectPolicy<JsonSerializer>
{
private readonly JsonSerializerSettings _serializerSettings;
/// <summary>
/// Initializes a new instance of <see cref="JsonSerializerObjectPolicy"/>.
/// </summary>
/// <param name="serializerSettings">The <see cref="JsonSerializerSettings"/> used to instantiate
/// <see cref="JsonSerializer"/> instances.</param>
public JsonSerializerObjectPolicy(JsonSerializerSettings serializerSettings)
{
_serializerSettings = serializerSettings;
}
/// <inheritdoc />
public JsonSerializer Create() => JsonSerializer.Create(_serializerSettings);
/// <inheritdoc />
public bool Return(JsonSerializer serializer) => true;
}
}

View File

@ -4,6 +4,7 @@
using System.Buffers;
using Microsoft.AspNet.Mvc.ModelBinding.Validation;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.ObjectPool;
using Microsoft.Extensions.Options;
using Microsoft.Net.Http.Headers;
using Newtonsoft.Json;
@ -11,13 +12,29 @@ using Newtonsoft.Json.Linq;
namespace Microsoft.AspNet.Mvc.Formatters.Json.Internal
{
/// <summary>
/// Sets up JSON formatter options for <see cref="MvcOptions"/>.
/// </summary>
public class MvcJsonMvcOptionsSetup : ConfigureOptions<MvcOptions>
{
/// <summary>
/// Intiailizes a new instance of <see cref="MvcJsonMvcOptionsSetup"/>.
/// </summary>
/// <param name="loggerFactory">The <see cref="ILoggerFactory"/>.</param>
/// <param name="jsonOptions"></param>
/// <param name="charPool"></param>
/// <param name="objectPoolProvider"></param>
public MvcJsonMvcOptionsSetup(
ILoggerFactory loggerFactory,
IOptions<MvcJsonOptions> jsonOptions,
ArrayPool<char> charPool)
: base((options) => ConfigureMvc(options, jsonOptions.Value.SerializerSettings, loggerFactory, charPool))
ArrayPool<char> charPool,
ObjectPoolProvider objectPoolProvider)
: base((options) => ConfigureMvc(
options,
jsonOptions.Value.SerializerSettings,
loggerFactory,
charPool,
objectPoolProvider))
{
}
@ -25,15 +42,26 @@ namespace Microsoft.AspNet.Mvc.Formatters.Json.Internal
MvcOptions options,
JsonSerializerSettings serializerSettings,
ILoggerFactory loggerFactory,
ArrayPool<char> charPool)
ArrayPool<char> charPool,
ObjectPoolProvider objectPoolProvider)
{
var jsonInputLogger = loggerFactory.CreateLogger<JsonInputFormatter>();
var jsonInputPatchLogger = loggerFactory.CreateLogger<JsonPatchInputFormatter>();
options.OutputFormatters.Add(new JsonOutputFormatter(serializerSettings, charPool));
options.InputFormatters.Add(new JsonInputFormatter(jsonInputLogger, serializerSettings, charPool));
options.InputFormatters.Add(new JsonPatchInputFormatter(jsonInputPatchLogger, serializerSettings, charPool));
var jsonInputLogger = loggerFactory.CreateLogger<JsonInputFormatter>();
options.InputFormatters.Add(new JsonInputFormatter(
jsonInputLogger,
serializerSettings,
charPool,
objectPoolProvider));
var jsonInputPatchLogger = loggerFactory.CreateLogger<JsonPatchInputFormatter>();
options.InputFormatters.Add(new JsonPatchInputFormatter(
jsonInputPatchLogger,
serializerSettings,
charPool,
objectPoolProvider));
options.FormatterMappings.SetMediaTypeMappingForFormat("json", MediaTypeHeaderValue.Parse("application/json"));
options.ModelMetadataDetailsProviders.Add(new ValidationExcludeFilter(typeof(JToken)));

View File

@ -4,47 +4,88 @@
using System;
using System.Buffers;
using System.Threading.Tasks;
using Microsoft.AspNet.Mvc.Formatters.Internal;
using Microsoft.AspNet.Mvc.Formatters.Json.Internal;
using Microsoft.AspNet.Mvc.Formatters.Json.Logging;
using Microsoft.AspNet.Mvc.Internal;
using Microsoft.AspNet.Mvc.ModelBinding;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.ObjectPool;
using Newtonsoft.Json;
namespace Microsoft.AspNet.Mvc.Formatters
{
/// <summary>
/// An <see cref="InputFormatter"/> for JSON content.
/// </summary>
public class JsonInputFormatter : InputFormatter
{
private readonly IArrayPool<char> _charPool;
private readonly ILogger _logger;
private readonly ObjectPoolProvider _objectPoolProvider;
private ObjectPool<JsonSerializer> _jsonSerializerPool;
private JsonSerializerSettings _serializerSettings;
/// <summary>
/// Initializes a new instance of <see cref="JsonInputFormatter"/>.
/// </summary>
/// <param name="logger">The <see cref="ILogger"/>.</param>
public JsonInputFormatter(ILogger logger)
: this(logger, SerializerSettingsProvider.CreateSerializerSettings(), ArrayPool<char>.Shared)
: this(logger, SerializerSettingsProvider.CreateSerializerSettings())
{
}
/// <summary>
/// Initializes a new instance of <see cref="JsonInputFormatter"/>.
/// </summary>
/// <param name="logger">The <see cref="ILogger"/>.</param>
/// <param name="serializerSettings">The <see cref="JsonSerializerSettings"/>.</param>
public JsonInputFormatter(ILogger logger, JsonSerializerSettings serializerSettings)
: this(logger, serializerSettings, ArrayPool<char>.Shared)
: this(
logger,
serializerSettings,
ArrayPool<char>.Shared,
new DefaultObjectPoolProvider())
{
}
public JsonInputFormatter(ILogger logger, JsonSerializerSettings serializerSettings, ArrayPool<char> charPool)
/// <summary>
/// Initializes a new instance of <see cref="JsonInputFormatter"/>.
/// </summary>
/// <param name="logger">The <see cref="ILogger"/>.</param>
/// <param name="serializerSettings">The <see cref="JsonSerializerSettings"/>.</param>
/// <param name="charPool">The <see cref="ArrayPool{char}"/>.</param>
/// <param name="objectPoolProvider">The <see cref="ObjectPoolProvider"/>.</param>
public JsonInputFormatter(
ILogger logger,
JsonSerializerSettings serializerSettings,
ArrayPool<char> charPool,
ObjectPoolProvider objectPoolProvider)
{
if (serializerSettings == null)
{
throw new ArgumentNullException(nameof(serializerSettings));
}
if (logger == null)
{
throw new ArgumentNullException(nameof(logger));
}
if (serializerSettings == null)
{
throw new ArgumentNullException(nameof(serializerSettings));
}
if (charPool == null)
{
throw new ArgumentNullException(nameof(charPool));
}
if (objectPoolProvider == null)
{
throw new ArgumentNullException(nameof(objectPoolProvider));
}
_logger = logger;
_serializerSettings = serializerSettings;
_charPool = new JsonArrayPool<char>(charPool);
_objectPoolProvider = objectPoolProvider;
SupportedEncodings.Add(UTF8EncodingWithoutBOM);
SupportedEncodings.Add(UTF16EncodingLittleEndian);
@ -56,6 +97,10 @@ namespace Microsoft.AspNet.Mvc.Formatters
/// <summary>
/// Gets or sets the <see cref="JsonSerializerSettings"/> used to configure the <see cref="JsonSerializer"/>.
/// </summary>
/// <remarks>
/// Any modifications to the <see cref="JsonSerializerSettings"/> object after this
/// <see cref="JsonInputFormatter"/> has been used will have no effect.
/// </remarks>
public JsonSerializerSettings SerializerSettings
{
get
@ -135,7 +180,6 @@ namespace Microsoft.AspNet.Mvc.Formatters
var type = context.ModelType;
var jsonSerializer = CreateJsonSerializer();
jsonSerializer.Error += errorHandler;
object model;
try
{
@ -143,8 +187,9 @@ namespace Microsoft.AspNet.Mvc.Formatters
}
finally
{
// Clean up the error handler in case CreateJsonSerializer() reuses a serializer
// Clean up the error handler since CreateJsonSerializer() pools instances.
jsonSerializer.Error -= errorHandler;
ReleaseJsonSerializer(jsonSerializer);
}
if (successful)
@ -160,12 +205,32 @@ namespace Microsoft.AspNet.Mvc.Formatters
/// <summary>
/// Called during deserialization to get the <see cref="JsonSerializer"/>.
/// </summary>
/// <returns>The <see cref="JsonSerializer"/> used during serialization and deserialization.</returns>
/// <returns>The <see cref="JsonSerializer"/> used during deserialization.</returns>
/// <remarks>
/// This method works in tandem with <see cref="ReleaseJsonSerializer(JsonSerializer)"/> to
/// manage the lifetimes of <see cref="JsonSerializer"/> instances.
/// </remarks>
protected virtual JsonSerializer CreateJsonSerializer()
{
return JsonSerializer.Create(SerializerSettings);
if (_jsonSerializerPool == null)
{
_jsonSerializerPool = _objectPoolProvider.Create<JsonSerializer>();
}
return _jsonSerializerPool.Get();
}
/// <summary>
/// Releases the <paramref name="serializer"/> instance.
/// </summary>
/// <param name="serializer">The <see cref="JsonSerializer"/> to release.</param>
/// <remarks>
/// This method works in tandem with <see cref="ReleaseJsonSerializer(JsonSerializer)"/> to
/// manage the lifetimes of <see cref="JsonSerializer"/> instances.
/// </remarks>
protected virtual void ReleaseJsonSerializer(JsonSerializer serializer)
=> _jsonSerializerPool.Return(serializer);
private ModelMetadata GetPathMetadata(ModelMetadata metadata, string path)
{
var index = 0;

View File

@ -8,6 +8,7 @@ using System.Threading.Tasks;
using Microsoft.AspNet.JsonPatch;
using Microsoft.AspNet.Mvc.Internal;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.ObjectPool;
using Newtonsoft.Json;
namespace Microsoft.AspNet.Mvc.Formatters
@ -15,15 +16,20 @@ namespace Microsoft.AspNet.Mvc.Formatters
public class JsonPatchInputFormatter : JsonInputFormatter
{
public JsonPatchInputFormatter(ILogger logger)
: this(logger, SerializerSettingsProvider.CreateSerializerSettings(), ArrayPool<char>.Shared)
: this(
logger,
SerializerSettingsProvider.CreateSerializerSettings(),
ArrayPool<char>.Shared,
new DefaultObjectPoolProvider())
{
}
public JsonPatchInputFormatter(
ILogger logger,
JsonSerializerSettings serializerSettings,
ArrayPool<char> charPool)
: base(logger, serializerSettings, charPool)
ArrayPool<char> charPool,
ObjectPoolProvider objectPoolProvider)
: base(logger, serializerSettings, charPool, objectPoolProvider)
{
// Clear all values and only include json-patch+json value.
SupportedMediaTypes.Clear();

View File

@ -10,6 +10,7 @@ using Microsoft.AspNet.Mvc.ModelBinding;
using Microsoft.AspNet.Mvc.ModelBinding.Metadata;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.ObjectPool;
using Microsoft.Extensions.Options;
namespace Microsoft.AspNet.Mvc.IntegrationTests
@ -31,7 +32,12 @@ namespace Microsoft.AspNet.Mvc.IntegrationTests
var loggerFactory = new LoggerFactory();
var serializerSettings = SerializerSettingsProvider.CreateSerializerSettings();
MvcJsonMvcOptionsSetup.ConfigureMvc(Value, serializerSettings, loggerFactory, ArrayPool<char>.Shared);
MvcJsonMvcOptionsSetup.ConfigureMvc(
Value,
serializerSettings,
loggerFactory,
ArrayPool<char>.Shared,
new DefaultObjectPoolProvider());
}
public MvcOptions Value { get; }