From cf9221df073467f0e6bae910f65a6957cd29523e Mon Sep 17 00:00:00 2001 From: Pranav K Date: Thu, 14 Jan 2016 18:03:48 -0800 Subject: [PATCH] Pool JsonSerializer instances Fixes #3550 --- .../MvcJsonMvcCoreBuilderExtensions.cs | 2 + .../JsonSerializerObjectPolicy.cs | 32 +++++++ .../Internal/MvcJsonMvcOptionsSetup.cs | 44 +++++++-- .../JsonInputFormatter.cs | 91 ++++++++++++++++--- .../JsonPatchInputFormatter.cs | 12 ++- .../TestMvcOptions.cs | 8 +- 6 files changed, 164 insertions(+), 25 deletions(-) create mode 100644 src/Microsoft.AspNet.Mvc.Formatters.Json/Infrastructure/JsonSerializerObjectPolicy.cs diff --git a/src/Microsoft.AspNet.Mvc.Formatters.Json/DependencyInjection/MvcJsonMvcCoreBuilderExtensions.cs b/src/Microsoft.AspNet.Mvc.Formatters.Json/DependencyInjection/MvcJsonMvcCoreBuilderExtensions.cs index 20331f9806..092db42920 100644 --- a/src/Microsoft.AspNet.Mvc.Formatters.Json/DependencyInjection/MvcJsonMvcCoreBuilderExtensions.cs +++ b/src/Microsoft.AspNet.Mvc.Formatters.Json/DependencyInjection/MvcJsonMvcCoreBuilderExtensions.cs @@ -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, MvcJsonMvcOptionsSetup>()); services.TryAddSingleton(); + services.TryAddSingleton(); } } } diff --git a/src/Microsoft.AspNet.Mvc.Formatters.Json/Infrastructure/JsonSerializerObjectPolicy.cs b/src/Microsoft.AspNet.Mvc.Formatters.Json/Infrastructure/JsonSerializerObjectPolicy.cs new file mode 100644 index 0000000000..3a0535631f --- /dev/null +++ b/src/Microsoft.AspNet.Mvc.Formatters.Json/Infrastructure/JsonSerializerObjectPolicy.cs @@ -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 +{ + /// + /// for . + /// + public class JsonSerializerObjectPolicy : IPooledObjectPolicy + { + private readonly JsonSerializerSettings _serializerSettings; + + /// + /// Initializes a new instance of . + /// + /// The used to instantiate + /// instances. + public JsonSerializerObjectPolicy(JsonSerializerSettings serializerSettings) + { + _serializerSettings = serializerSettings; + } + + /// + public JsonSerializer Create() => JsonSerializer.Create(_serializerSettings); + + /// + public bool Return(JsonSerializer serializer) => true; + } +} \ No newline at end of file diff --git a/src/Microsoft.AspNet.Mvc.Formatters.Json/Internal/MvcJsonMvcOptionsSetup.cs b/src/Microsoft.AspNet.Mvc.Formatters.Json/Internal/MvcJsonMvcOptionsSetup.cs index 962cbafd33..5a8c50b31f 100644 --- a/src/Microsoft.AspNet.Mvc.Formatters.Json/Internal/MvcJsonMvcOptionsSetup.cs +++ b/src/Microsoft.AspNet.Mvc.Formatters.Json/Internal/MvcJsonMvcOptionsSetup.cs @@ -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 { + /// + /// Sets up JSON formatter options for . + /// public class MvcJsonMvcOptionsSetup : ConfigureOptions { + /// + /// Intiailizes a new instance of . + /// + /// The . + /// + /// + /// public MvcJsonMvcOptionsSetup( ILoggerFactory loggerFactory, IOptions jsonOptions, - ArrayPool charPool) - : base((options) => ConfigureMvc(options, jsonOptions.Value.SerializerSettings, loggerFactory, charPool)) + ArrayPool 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 charPool) + ArrayPool charPool, + ObjectPoolProvider objectPoolProvider) { - var jsonInputLogger = loggerFactory.CreateLogger(); - var jsonInputPatchLogger = loggerFactory.CreateLogger(); 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(); + options.InputFormatters.Add(new JsonInputFormatter( + jsonInputLogger, + serializerSettings, + charPool, + objectPoolProvider)); + + var jsonInputPatchLogger = loggerFactory.CreateLogger(); + options.InputFormatters.Add(new JsonPatchInputFormatter( + jsonInputPatchLogger, + serializerSettings, + charPool, + objectPoolProvider)); + options.FormatterMappings.SetMediaTypeMappingForFormat("json", MediaTypeHeaderValue.Parse("application/json")); options.ModelMetadataDetailsProviders.Add(new ValidationExcludeFilter(typeof(JToken))); diff --git a/src/Microsoft.AspNet.Mvc.Formatters.Json/JsonInputFormatter.cs b/src/Microsoft.AspNet.Mvc.Formatters.Json/JsonInputFormatter.cs index 77150163bd..3eceef3bfc 100644 --- a/src/Microsoft.AspNet.Mvc.Formatters.Json/JsonInputFormatter.cs +++ b/src/Microsoft.AspNet.Mvc.Formatters.Json/JsonInputFormatter.cs @@ -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 { + /// + /// An for JSON content. + /// public class JsonInputFormatter : InputFormatter { private readonly IArrayPool _charPool; private readonly ILogger _logger; - + private readonly ObjectPoolProvider _objectPoolProvider; + private ObjectPool _jsonSerializerPool; private JsonSerializerSettings _serializerSettings; + /// + /// Initializes a new instance of . + /// + /// The . public JsonInputFormatter(ILogger logger) - : this(logger, SerializerSettingsProvider.CreateSerializerSettings(), ArrayPool.Shared) + : this(logger, SerializerSettingsProvider.CreateSerializerSettings()) { } + /// + /// Initializes a new instance of . + /// + /// The . + /// The . public JsonInputFormatter(ILogger logger, JsonSerializerSettings serializerSettings) - : this(logger, serializerSettings, ArrayPool.Shared) + : this( + logger, + serializerSettings, + ArrayPool.Shared, + new DefaultObjectPoolProvider()) { } - public JsonInputFormatter(ILogger logger, JsonSerializerSettings serializerSettings, ArrayPool charPool) + /// + /// Initializes a new instance of . + /// + /// The . + /// The . + /// The . + /// The . + public JsonInputFormatter( + ILogger logger, + JsonSerializerSettings serializerSettings, + ArrayPool 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(charPool); + _objectPoolProvider = objectPoolProvider; SupportedEncodings.Add(UTF8EncodingWithoutBOM); SupportedEncodings.Add(UTF16EncodingLittleEndian); @@ -56,6 +97,10 @@ namespace Microsoft.AspNet.Mvc.Formatters /// /// Gets or sets the used to configure the . /// + /// + /// Any modifications to the object after this + /// has been used will have no effect. + /// 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 /// /// Called during deserialization to get the . /// - /// The used during serialization and deserialization. + /// The used during deserialization. + /// + /// This method works in tandem with to + /// manage the lifetimes of instances. + /// protected virtual JsonSerializer CreateJsonSerializer() { - return JsonSerializer.Create(SerializerSettings); + if (_jsonSerializerPool == null) + { + _jsonSerializerPool = _objectPoolProvider.Create(); + } + + return _jsonSerializerPool.Get(); } + /// + /// Releases the instance. + /// + /// The to release. + /// + /// This method works in tandem with to + /// manage the lifetimes of instances. + /// + protected virtual void ReleaseJsonSerializer(JsonSerializer serializer) + => _jsonSerializerPool.Return(serializer); + private ModelMetadata GetPathMetadata(ModelMetadata metadata, string path) { var index = 0; diff --git a/src/Microsoft.AspNet.Mvc.Formatters.Json/JsonPatchInputFormatter.cs b/src/Microsoft.AspNet.Mvc.Formatters.Json/JsonPatchInputFormatter.cs index 804ade8349..d2de9e88c2 100644 --- a/src/Microsoft.AspNet.Mvc.Formatters.Json/JsonPatchInputFormatter.cs +++ b/src/Microsoft.AspNet.Mvc.Formatters.Json/JsonPatchInputFormatter.cs @@ -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.Shared) + : this( + logger, + SerializerSettingsProvider.CreateSerializerSettings(), + ArrayPool.Shared, + new DefaultObjectPoolProvider()) { } public JsonPatchInputFormatter( ILogger logger, JsonSerializerSettings serializerSettings, - ArrayPool charPool) - : base(logger, serializerSettings, charPool) + ArrayPool charPool, + ObjectPoolProvider objectPoolProvider) + : base(logger, serializerSettings, charPool, objectPoolProvider) { // Clear all values and only include json-patch+json value. SupportedMediaTypes.Clear(); diff --git a/test/Microsoft.AspNet.Mvc.IntegrationTests/TestMvcOptions.cs b/test/Microsoft.AspNet.Mvc.IntegrationTests/TestMvcOptions.cs index 76eaea04b7..d1c19bf337 100644 --- a/test/Microsoft.AspNet.Mvc.IntegrationTests/TestMvcOptions.cs +++ b/test/Microsoft.AspNet.Mvc.IntegrationTests/TestMvcOptions.cs @@ -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.Shared); + MvcJsonMvcOptionsSetup.ConfigureMvc( + Value, + serializerSettings, + loggerFactory, + ArrayPool.Shared, + new DefaultObjectPoolProvider()); } public MvcOptions Value { get; }