From 9208b253433243bbb34ba33565c1e3fdc9db79f0 Mon Sep 17 00:00:00 2001 From: Rico Suter Date: Thu, 20 Sep 2018 17:37:39 +0200 Subject: [PATCH 1/9] Fix XmlDocumentationExtensions deadlock --- .../Generation/XmlDocTests.cs | 24 +-- src/NJsonSchema/Infrastructure/DynamicApis.cs | 4 +- .../XmlDocumentationExtensions.cs | 204 +++++++----------- 3 files changed, 97 insertions(+), 135 deletions(-) diff --git a/src/NJsonSchema.Tests/Generation/XmlDocTests.cs b/src/NJsonSchema.Tests/Generation/XmlDocTests.cs index ec10d8767..97c32621e 100644 --- a/src/NJsonSchema.Tests/Generation/XmlDocTests.cs +++ b/src/NJsonSchema.Tests/Generation/XmlDocTests.cs @@ -28,7 +28,7 @@ public class WithComplexXmlDoc public async Task When_xml_doc_with_multiple_breaks_is_read_then_they_are_not_stripped_away() { //// Arrange - await XmlDocumentationExtensions.ClearCacheAsync(); + XmlDocumentationExtensions.ClearCache(); //// Act var summary = await typeof(WithComplexXmlDoc).GetProperty("Foo").GetXmlSummaryAsync(); @@ -51,7 +51,7 @@ public class WithTagsInXmlDoc public async Task When_xml_doc_contains_xml_then_it_is_fully_read() { //// Arrange - await XmlDocumentationExtensions.ClearCacheAsync(); + XmlDocumentationExtensions.ClearCache(); //// Act var element = await typeof(WithTagsInXmlDoc).GetProperty("Foo").GetXmlDocumentationAsync(); @@ -77,7 +77,7 @@ public class WithSeeTagInXmlDoc public async Task When_summary_has_see_tag_then_it_is_converted() { //// Arrange - await XmlDocumentationExtensions.ClearCacheAsync(); + XmlDocumentationExtensions.ClearCache(); //// Act var summary = await typeof(WithSeeTagInXmlDoc).GetProperty("Foo").GetXmlSummaryAsync(); @@ -96,7 +96,7 @@ public class WithGenericTagsInXmlDoc public async Task When_summary_has_generic_tags_then_it_is_converted() { //// Arrange - await XmlDocumentationExtensions.ClearCacheAsync(); + XmlDocumentationExtensions.ClearCache(); //// Act var summary = await typeof(WithGenericTagsInXmlDoc).GetProperty("Foo").GetXmlSummaryAsync(); @@ -109,7 +109,7 @@ public async Task When_summary_has_generic_tags_then_it_is_converted() public async Task When_xml_doc_is_missing_then_summary_is_missing() { //// Arrange - await XmlDocumentationExtensions.ClearCacheAsync(); + XmlDocumentationExtensions.ClearCache(); //// Act var summary = await typeof(Point).GetXmlSummaryAsync(); @@ -118,7 +118,7 @@ public async Task When_xml_doc_is_missing_then_summary_is_missing() //// Assert Assert.Empty(summary); } - + public abstract class BaseBaseClass { /// Foo. @@ -151,7 +151,7 @@ public class ClassWithInheritdoc : BaseClass public async Task When_parameter_has_inheritdoc_then_it_is_resolved() { //// Arrange - await XmlDocumentationExtensions.ClearCacheAsync(); + XmlDocumentationExtensions.ClearCache(); //// Act var parameterXml = await typeof(ClassWithInheritdoc).GetMethod("Bar").GetParameters() @@ -165,7 +165,7 @@ public async Task When_parameter_has_inheritdoc_then_it_is_resolved() public async Task When_property_has_inheritdoc_then_it_is_resolved() { //// Arrange - await XmlDocumentationExtensions.ClearCacheAsync(); + XmlDocumentationExtensions.ClearCache(); //// Act var propertySummary = await typeof(ClassWithInheritdoc).GetProperty("Foo").GetXmlSummaryAsync(); @@ -178,7 +178,7 @@ public async Task When_property_has_inheritdoc_then_it_is_resolved() public async Task When_method_has_inheritdoc_then_it_is_resolved() { //// Arrange - await XmlDocumentationExtensions.ClearCacheAsync(); + XmlDocumentationExtensions.ClearCache(); //// Act var methodSummary = await typeof(ClassWithInheritdoc).GetMethod("Bar").GetXmlSummaryAsync(); @@ -214,7 +214,7 @@ public class ClassWithInheritdocOnInterface : IBaseInterface public async Task When_parameter_has_inheritdoc_on_interface_then_it_is_resolved() { //// Arrange - await XmlDocumentationExtensions.ClearCacheAsync(); + XmlDocumentationExtensions.ClearCache(); //// Act var parameterXml = await typeof(ClassWithInheritdocOnInterface).GetMethod("Bar").GetParameters() @@ -228,7 +228,7 @@ public async Task When_parameter_has_inheritdoc_on_interface_then_it_is_resolved public async Task When_property_has_inheritdoc_on_interface_then_it_is_resolved() { //// Arrange - await XmlDocumentationExtensions.ClearCacheAsync(); + XmlDocumentationExtensions.ClearCache(); //// Act var propertySummary = await typeof(ClassWithInheritdocOnInterface).GetProperty("Foo").GetXmlSummaryAsync(); @@ -241,7 +241,7 @@ public async Task When_property_has_inheritdoc_on_interface_then_it_is_resolved( public async Task When_method_has_inheritdoc_then_on_interface_it_is_resolved() { //// Arrange - await XmlDocumentationExtensions.ClearCacheAsync(); + XmlDocumentationExtensions.ClearCache(); //// Act var methodSummary = await typeof(ClassWithInheritdocOnInterface).GetMethod("Bar").GetXmlSummaryAsync(); diff --git a/src/NJsonSchema/Infrastructure/DynamicApis.cs b/src/NJsonSchema/Infrastructure/DynamicApis.cs index 68eb7608b..99e425932 100644 --- a/src/NJsonSchema/Infrastructure/DynamicApis.cs +++ b/src/NJsonSchema/Infrastructure/DynamicApis.cs @@ -206,13 +206,13 @@ public static object XPathEvaluate(XDocument document, string path) } #if LEGACY - private static async Task FromResult(T result) + internal static async Task FromResult(T result) { return result; } #else [MethodImpl(MethodImplOptions.AggressiveInlining)] - private static Task FromResult(T result) + internal static Task FromResult(T result) { return Task.FromResult(result); } diff --git a/src/NJsonSchema/Infrastructure/XmlDocumentationExtensions.cs b/src/NJsonSchema/Infrastructure/XmlDocumentationExtensions.cs index d05b6f47c..68509daee 100644 --- a/src/NJsonSchema/Infrastructure/XmlDocumentationExtensions.cs +++ b/src/NJsonSchema/Infrastructure/XmlDocumentationExtensions.cs @@ -13,7 +13,6 @@ using System.Reflection; using System.Text; using System.Text.RegularExpressions; -using System.Threading; using System.Threading.Tasks; using System.Xml.Linq; @@ -23,10 +22,8 @@ namespace NJsonSchema.Infrastructure /// This class currently works only on the desktop .NET framework. public static class XmlDocumentationExtensions { - private static readonly SemaphoreSlim _lock = new SemaphoreSlim(1, 1); - - private static readonly Dictionary Cache = - new Dictionary(StringComparer.OrdinalIgnoreCase); + private static readonly Dictionary> Cache = + new Dictionary>(StringComparer.OrdinalIgnoreCase); #if !LEGACY @@ -85,9 +82,17 @@ public static async Task GetXmlRemarksAsync(this MemberInfo member) /// Returns the contents of an XML documentation tag for the specified member. /// The reflected member. /// The contents of the "summary" tag for the member. - public static Task GetXmlDocumentationAsync(this MemberInfo member) + public static async Task GetXmlDocumentationAsync(this MemberInfo member) { - return GetXmlDocumentationAsync(member, true); + if (DynamicApis.SupportsXPathApis == false || DynamicApis.SupportsFileApis == false || DynamicApis.SupportsPathApis == false) + return null; + + var assemblyName = member.Module.Assembly.GetName(); + if (IgnoreAssembly(assemblyName)) + return null; + + var documentationPath = await GetXmlDocumentationPathAsync(member.Module.Assembly).ConfigureAwait(false); + return await GetXmlDocumentationAsync(member, documentationPath).ConfigureAwait(false); } /// Returns the contents of an XML documentation tag for the specified member. @@ -100,7 +105,7 @@ public static async Task GetXmlDocumentationTagAsync(this MemberInfo mem return string.Empty; var assemblyName = member.Module.Assembly.GetName(); - if (await IgnoreAssemblyAsync(assemblyName, true)) + if (IgnoreAssembly(assemblyName)) return string.Empty; var documentationPath = await GetXmlDocumentationPathAsync(member.Module.Assembly).ConfigureAwait(false); @@ -117,9 +122,9 @@ public static async Task GetXmlDocumentationAsync(this ParameterInfo par return string.Empty; var assemblyName = parameter.Member.Module.Assembly.GetName(); - if (await IgnoreAssemblyAsync(assemblyName, true)) + if (IgnoreAssembly(assemblyName)) return string.Empty; - + var documentationPath = await GetXmlDocumentationPathAsync(parameter.Member.Module.Assembly).ConfigureAwait(false); var element = await GetXmlDocumentationAsync(parameter, documentationPath).ConfigureAwait(false); return RemoveLineBreakWhiteSpaces(GetXmlDocumentationText(element)); @@ -138,9 +143,26 @@ public static Task GetXmlDocumentationAsync(this Type type, string pat /// The reflected member. /// The path to the XML documentation file. /// The contents of the "summary" tag for the member. - public static Task GetXmlDocumentationAsync(this MemberInfo member, string pathToXmlFile) + public static async Task GetXmlDocumentationAsync(this MemberInfo member, string pathToXmlFile) { - return GetXmlDocumentationAsync(member, pathToXmlFile, true); + try + { + if (pathToXmlFile == null || DynamicApis.SupportsXPathApis == false || DynamicApis.SupportsFileApis == false || DynamicApis.SupportsPathApis == false) + return null; + + var assemblyName = member.Module.Assembly.GetName(); + var document = await TryGetXmlDocumentAsync(assemblyName, pathToXmlFile); + if (document == null) + return null; + + var element = GetXmlDocumentation(member, document); + await ReplaceInheritdocElementsAsync(member, element); + return element; + } + catch + { + return null; + } } /// Returns the contents of the "returns" or "param" XML documentation tag for the specified parameter. @@ -155,7 +177,7 @@ public static async Task GetXmlDocumentationAsync(this ParameterInfo p return null; var assemblyName = parameter.Member.Module.Assembly.GetName(); - var document = await TryGetXmlDocumentAsync(assemblyName, pathToXmlFile, true); + var document = await TryGetXmlDocumentAsync(assemblyName, pathToXmlFile); if (document == null) return null; @@ -269,115 +291,60 @@ public static string GetXmlDocumentationText(this XElement element) /// Clears the cache. /// The task. - public static async Task ClearCacheAsync() + public static void ClearCache() { -#if !LEGACY - await _lock.WaitAsync(); -#else - _lock.Wait(); -#endif - - try + lock (Cache) { Cache.Clear(); } - finally - { - _lock.Release(); - } - } - - private static async Task GetXmlDocumentationAsync(this MemberInfo member, bool useLock) - { - if (DynamicApis.SupportsXPathApis == false || DynamicApis.SupportsFileApis == false || DynamicApis.SupportsPathApis == false) - return null; - - var assemblyName = member.Module.Assembly.GetName(); - if (await IgnoreAssemblyAsync(assemblyName, useLock)) - return null; - - var documentationPath = await GetXmlDocumentationPathAsync(member.Module.Assembly).ConfigureAwait(false); - return await GetXmlDocumentationAsync(member, documentationPath, useLock).ConfigureAwait(false); - } - - private static async Task GetXmlDocumentationAsync(this MemberInfo member, string pathToXmlFile, bool useLock) - { - try - { - if (pathToXmlFile == null || DynamicApis.SupportsXPathApis == false || DynamicApis.SupportsFileApis == false || DynamicApis.SupportsPathApis == false) - return null; - - var assemblyName = member.Module.Assembly.GetName(); - var document = await TryGetXmlDocumentAsync(assemblyName, pathToXmlFile, useLock); - if (document == null) - return null; - - var element = GetXmlDocumentation(member, document); - await ReplaceInheritdocElementsAsync(member, element, useLock); - return element; - } - catch - { - return null; - } } - private static async Task TryGetXmlDocumentAsync(AssemblyName assemblyName, string pathToXmlFile, bool useLock) + private static async Task TryGetXmlDocumentAsync(AssemblyName assemblyName, string pathToXmlFile) { - if (useLock) + var containsKey = false; + lock (Cache) { -#if !LEGACY - await _lock.WaitAsync(); -#else - _lock.Wait(); -#endif + containsKey = Cache.ContainsKey(assemblyName.FullName); } - try + if (!containsKey) { - if (!Cache.ContainsKey(assemblyName.FullName)) + if (await DynamicApis.FileExistsAsync(pathToXmlFile).ConfigureAwait(false) == false) { - if (await DynamicApis.FileExistsAsync(pathToXmlFile).ConfigureAwait(false) == false) + lock (Cache) { - Cache[assemblyName.FullName] = null; + Cache[assemblyName.FullName] = DynamicApis.FromResult(null); return null; } - - Cache[assemblyName.FullName] = await Task.Factory.StartNew(() => XDocument.Load(pathToXmlFile, LoadOptions.PreserveWhitespace)).ConfigureAwait(false); } - return Cache[assemblyName.FullName]; + lock (Cache) + { + if (!Cache.ContainsKey(assemblyName.FullName)) + { + Cache[assemblyName.FullName] = Task.Factory.StartNew(() => XDocument.Load(pathToXmlFile, LoadOptions.PreserveWhitespace)); + } + } } - finally + + Task task = null; + lock (Cache) { - if (useLock) - _lock.Release(); + task = Cache[assemblyName.FullName]; } + + return await task; } - private static async Task IgnoreAssemblyAsync(AssemblyName assemblyName, bool useLock) + private static bool IgnoreAssembly(AssemblyName assemblyName) { - if (useLock) - { -#if !LEGACY - await _lock.WaitAsync(); -#else - _lock.Wait(); -#endif - } - - try + lock (Cache) { if (Cache.ContainsKey(assemblyName.FullName) && Cache[assemblyName.FullName] == null) return true; - } - finally - { - if (useLock) - _lock.Release(); - } - return false; + return false; + } } private static XElement GetXmlDocumentation(this MemberInfo member, XDocument xml) @@ -393,7 +360,7 @@ private static async Task GetXmlDocumentationAsync(this ParameterInfo var result = (IEnumerable)DynamicApis.XPathEvaluate(xml, $"/doc/members/member[@name='{name}']"); var element = result.OfType().First(); - await ReplaceInheritdocElementsAsync(parameter.Member, element, true); + await ReplaceInheritdocElementsAsync(parameter.Member, element); if (parameter.IsRetval || string.IsNullOrEmpty(parameter.Name)) result = (IEnumerable)DynamicApis.XPathEvaluate(xml, $"/doc/members/member[@name='{name}']/returns"); @@ -403,49 +370,41 @@ private static async Task GetXmlDocumentationAsync(this ParameterInfo return result.OfType().FirstOrDefault(); } - private static async Task ReplaceInheritdocElementsAsync(this MemberInfo member, XElement element, bool useLock) + private static async Task ReplaceInheritdocElementsAsync(this MemberInfo member, XElement element) { #if !LEGACY if (element == null) return; - if (useLock) - await _lock.WaitAsync(); - - try + var children = element.Nodes().ToList(); + foreach (var child in children.OfType()) { - var children = element.Nodes().ToList(); - foreach (var child in children.OfType()) + if (child.Name.LocalName.ToLowerInvariant() == "inheritdoc") { - if (child.Name.LocalName.ToLowerInvariant() == "inheritdoc") + var baseType = member.DeclaringType.GetTypeInfo().BaseType; + var baseMember = baseType?.GetTypeInfo().DeclaredMembers.SingleOrDefault(m => m.Name == member.Name); + if (baseMember != null) { - var baseType = member.DeclaringType.GetTypeInfo().BaseType; - var baseMember = baseType?.GetTypeInfo().DeclaredMembers.SingleOrDefault(m => m.Name == member.Name); - if (baseMember != null) + var baseDoc = await baseMember.GetXmlDocumentationAsync(); + if (baseDoc != null) { - var baseDoc = await baseMember.GetXmlDocumentationAsync(false); - if (baseDoc != null) + lock (Cache) { var nodes = baseDoc.Nodes().OfType().ToArray(); child.ReplaceWith(nodes); } - else - { - await ProcessInheritdocInterfaceElementsAsync(member, child); - } } else { await ProcessInheritdocInterfaceElementsAsync(member, child); } } + else + { + await ProcessInheritdocInterfaceElementsAsync(member, child); + } } } - finally - { - if (useLock) - _lock.Release(); - } #endif } @@ -457,11 +416,14 @@ private static async Task ProcessInheritdocInterfaceElementsAsync(this MemberInf var baseMember = baseInterface?.GetTypeInfo().DeclaredMembers.SingleOrDefault(m => m.Name == member.Name); if (baseMember != null) { - var baseDoc = await baseMember.GetXmlDocumentationAsync(false); + var baseDoc = await baseMember.GetXmlDocumentationAsync(); if (baseDoc != null) { - var nodes = baseDoc.Nodes().OfType().ToArray(); - child.ReplaceWith(nodes); + lock (Cache) + { + var nodes = baseDoc.Nodes().OfType().ToArray(); + child.ReplaceWith(nodes); + } } } } From 97f99c5f4b4e53225bdb0f44319b2c1439078e2f Mon Sep 17 00:00:00 2001 From: Rico Suter Date: Thu, 20 Sep 2018 19:00:41 +0200 Subject: [PATCH 2/9] Add ConfigureAwait(false) --- .../XmlDocumentationExtensions.cs | 20 +++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/src/NJsonSchema/Infrastructure/XmlDocumentationExtensions.cs b/src/NJsonSchema/Infrastructure/XmlDocumentationExtensions.cs index 68509daee..cd6031063 100644 --- a/src/NJsonSchema/Infrastructure/XmlDocumentationExtensions.cs +++ b/src/NJsonSchema/Infrastructure/XmlDocumentationExtensions.cs @@ -151,12 +151,12 @@ public static async Task GetXmlDocumentationAsync(this MemberInfo memb return null; var assemblyName = member.Module.Assembly.GetName(); - var document = await TryGetXmlDocumentAsync(assemblyName, pathToXmlFile); + var document = await TryGetXmlDocumentAsync(assemblyName, pathToXmlFile).ConfigureAwait(false); if (document == null) return null; var element = GetXmlDocumentation(member, document); - await ReplaceInheritdocElementsAsync(member, element); + await ReplaceInheritdocElementsAsync(member, element).ConfigureAwait(false); return element; } catch @@ -177,11 +177,11 @@ public static async Task GetXmlDocumentationAsync(this ParameterInfo p return null; var assemblyName = parameter.Member.Module.Assembly.GetName(); - var document = await TryGetXmlDocumentAsync(assemblyName, pathToXmlFile); + var document = await TryGetXmlDocumentAsync(assemblyName, pathToXmlFile).ConfigureAwait(false); if (document == null) return null; - return await GetXmlDocumentationAsync(parameter, document); + return await GetXmlDocumentationAsync(parameter, document).ConfigureAwait(false); } catch { @@ -333,7 +333,7 @@ private static async Task TryGetXmlDocumentAsync(AssemblyName assembl task = Cache[assemblyName.FullName]; } - return await task; + return await task.ConfigureAwait(false); } private static bool IgnoreAssembly(AssemblyName assemblyName) @@ -360,7 +360,7 @@ private static async Task GetXmlDocumentationAsync(this ParameterInfo var result = (IEnumerable)DynamicApis.XPathEvaluate(xml, $"/doc/members/member[@name='{name}']"); var element = result.OfType().First(); - await ReplaceInheritdocElementsAsync(parameter.Member, element); + await ReplaceInheritdocElementsAsync(parameter.Member, element).ConfigureAwait(false); if (parameter.IsRetval || string.IsNullOrEmpty(parameter.Name)) result = (IEnumerable)DynamicApis.XPathEvaluate(xml, $"/doc/members/member[@name='{name}']/returns"); @@ -385,7 +385,7 @@ private static async Task ReplaceInheritdocElementsAsync(this MemberInfo member, var baseMember = baseType?.GetTypeInfo().DeclaredMembers.SingleOrDefault(m => m.Name == member.Name); if (baseMember != null) { - var baseDoc = await baseMember.GetXmlDocumentationAsync(); + var baseDoc = await baseMember.GetXmlDocumentationAsync().ConfigureAwait(false); if (baseDoc != null) { lock (Cache) @@ -396,12 +396,12 @@ private static async Task ReplaceInheritdocElementsAsync(this MemberInfo member, } else { - await ProcessInheritdocInterfaceElementsAsync(member, child); + await ProcessInheritdocInterfaceElementsAsync(member, child).ConfigureAwait(false); } } else { - await ProcessInheritdocInterfaceElementsAsync(member, child); + await ProcessInheritdocInterfaceElementsAsync(member, child).ConfigureAwait(false); } } } @@ -416,7 +416,7 @@ private static async Task ProcessInheritdocInterfaceElementsAsync(this MemberInf var baseMember = baseInterface?.GetTypeInfo().DeclaredMembers.SingleOrDefault(m => m.Name == member.Name); if (baseMember != null) { - var baseDoc = await baseMember.GetXmlDocumentationAsync(); + var baseDoc = await baseMember.GetXmlDocumentationAsync().ConfigureAwait(false); if (baseDoc != null) { lock (Cache) From 2f1ced5a75b4ee5aa6056f19f0e12c92a10786c2 Mon Sep 17 00:00:00 2001 From: Rico Suter Date: Thu, 20 Sep 2018 19:51:25 +0200 Subject: [PATCH 3/9] Add docs --- src/NJsonSchema/Infrastructure/XmlDocumentationExtensions.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/NJsonSchema/Infrastructure/XmlDocumentationExtensions.cs b/src/NJsonSchema/Infrastructure/XmlDocumentationExtensions.cs index cd6031063..52ca43dab 100644 --- a/src/NJsonSchema/Infrastructure/XmlDocumentationExtensions.cs +++ b/src/NJsonSchema/Infrastructure/XmlDocumentationExtensions.cs @@ -388,7 +388,7 @@ private static async Task ReplaceInheritdocElementsAsync(this MemberInfo member, var baseDoc = await baseMember.GetXmlDocumentationAsync().ConfigureAwait(false); if (baseDoc != null) { - lock (Cache) + lock (Cache) // modify xml (possible race condition) { var nodes = baseDoc.Nodes().OfType().ToArray(); child.ReplaceWith(nodes); @@ -419,7 +419,7 @@ private static async Task ProcessInheritdocInterfaceElementsAsync(this MemberInf var baseDoc = await baseMember.GetXmlDocumentationAsync().ConfigureAwait(false); if (baseDoc != null) { - lock (Cache) + lock (Cache) // modify xml (possible race condition) { var nodes = baseDoc.Nodes().OfType().ToArray(); child.ReplaceWith(nodes); From 7ec89399d7a9dd53d2675f90ba65dfc183a563af Mon Sep 17 00:00:00 2001 From: Rico Suter Date: Thu, 20 Sep 2018 20:57:41 +0200 Subject: [PATCH 4/9] Use easy lock --- .../Generation/XmlDocTests.cs | 22 +- .../XmlDocumentationExtensions.cs | 369 ++++++++++-------- 2 files changed, 225 insertions(+), 166 deletions(-) diff --git a/src/NJsonSchema.Tests/Generation/XmlDocTests.cs b/src/NJsonSchema.Tests/Generation/XmlDocTests.cs index 97c32621e..d6de7103d 100644 --- a/src/NJsonSchema.Tests/Generation/XmlDocTests.cs +++ b/src/NJsonSchema.Tests/Generation/XmlDocTests.cs @@ -28,7 +28,7 @@ public class WithComplexXmlDoc public async Task When_xml_doc_with_multiple_breaks_is_read_then_they_are_not_stripped_away() { //// Arrange - XmlDocumentationExtensions.ClearCache(); + await XmlDocumentationExtensions.ClearCacheAsync(); //// Act var summary = await typeof(WithComplexXmlDoc).GetProperty("Foo").GetXmlSummaryAsync(); @@ -51,7 +51,7 @@ public class WithTagsInXmlDoc public async Task When_xml_doc_contains_xml_then_it_is_fully_read() { //// Arrange - XmlDocumentationExtensions.ClearCache(); + await XmlDocumentationExtensions.ClearCacheAsync(); //// Act var element = await typeof(WithTagsInXmlDoc).GetProperty("Foo").GetXmlDocumentationAsync(); @@ -77,7 +77,7 @@ public class WithSeeTagInXmlDoc public async Task When_summary_has_see_tag_then_it_is_converted() { //// Arrange - XmlDocumentationExtensions.ClearCache(); + await XmlDocumentationExtensions.ClearCacheAsync(); //// Act var summary = await typeof(WithSeeTagInXmlDoc).GetProperty("Foo").GetXmlSummaryAsync(); @@ -96,7 +96,7 @@ public class WithGenericTagsInXmlDoc public async Task When_summary_has_generic_tags_then_it_is_converted() { //// Arrange - XmlDocumentationExtensions.ClearCache(); + await XmlDocumentationExtensions.ClearCacheAsync(); //// Act var summary = await typeof(WithGenericTagsInXmlDoc).GetProperty("Foo").GetXmlSummaryAsync(); @@ -109,7 +109,7 @@ public async Task When_summary_has_generic_tags_then_it_is_converted() public async Task When_xml_doc_is_missing_then_summary_is_missing() { //// Arrange - XmlDocumentationExtensions.ClearCache(); + await XmlDocumentationExtensions.ClearCacheAsync(); //// Act var summary = await typeof(Point).GetXmlSummaryAsync(); @@ -151,7 +151,7 @@ public class ClassWithInheritdoc : BaseClass public async Task When_parameter_has_inheritdoc_then_it_is_resolved() { //// Arrange - XmlDocumentationExtensions.ClearCache(); + await XmlDocumentationExtensions.ClearCacheAsync(); //// Act var parameterXml = await typeof(ClassWithInheritdoc).GetMethod("Bar").GetParameters() @@ -165,7 +165,7 @@ public async Task When_parameter_has_inheritdoc_then_it_is_resolved() public async Task When_property_has_inheritdoc_then_it_is_resolved() { //// Arrange - XmlDocumentationExtensions.ClearCache(); + await XmlDocumentationExtensions.ClearCacheAsync(); //// Act var propertySummary = await typeof(ClassWithInheritdoc).GetProperty("Foo").GetXmlSummaryAsync(); @@ -178,7 +178,7 @@ public async Task When_property_has_inheritdoc_then_it_is_resolved() public async Task When_method_has_inheritdoc_then_it_is_resolved() { //// Arrange - XmlDocumentationExtensions.ClearCache(); + await XmlDocumentationExtensions.ClearCacheAsync(); //// Act var methodSummary = await typeof(ClassWithInheritdoc).GetMethod("Bar").GetXmlSummaryAsync(); @@ -214,7 +214,7 @@ public class ClassWithInheritdocOnInterface : IBaseInterface public async Task When_parameter_has_inheritdoc_on_interface_then_it_is_resolved() { //// Arrange - XmlDocumentationExtensions.ClearCache(); + await XmlDocumentationExtensions.ClearCacheAsync(); //// Act var parameterXml = await typeof(ClassWithInheritdocOnInterface).GetMethod("Bar").GetParameters() @@ -228,7 +228,7 @@ public async Task When_parameter_has_inheritdoc_on_interface_then_it_is_resolved public async Task When_property_has_inheritdoc_on_interface_then_it_is_resolved() { //// Arrange - XmlDocumentationExtensions.ClearCache(); + await XmlDocumentationExtensions.ClearCacheAsync(); //// Act var propertySummary = await typeof(ClassWithInheritdocOnInterface).GetProperty("Foo").GetXmlSummaryAsync(); @@ -241,7 +241,7 @@ public async Task When_property_has_inheritdoc_on_interface_then_it_is_resolved( public async Task When_method_has_inheritdoc_then_on_interface_it_is_resolved() { //// Arrange - XmlDocumentationExtensions.ClearCache(); + await XmlDocumentationExtensions.ClearCacheAsync(); //// Act var methodSummary = await typeof(ClassWithInheritdocOnInterface).GetMethod("Bar").GetXmlSummaryAsync(); diff --git a/src/NJsonSchema/Infrastructure/XmlDocumentationExtensions.cs b/src/NJsonSchema/Infrastructure/XmlDocumentationExtensions.cs index 52ca43dab..357a1dddf 100644 --- a/src/NJsonSchema/Infrastructure/XmlDocumentationExtensions.cs +++ b/src/NJsonSchema/Infrastructure/XmlDocumentationExtensions.cs @@ -13,6 +13,7 @@ using System.Reflection; using System.Text; using System.Text.RegularExpressions; +using System.Threading; using System.Threading.Tasks; using System.Xml.Linq; @@ -22,6 +23,48 @@ namespace NJsonSchema.Infrastructure /// This class currently works only on the desktop .NET framework. public static class XmlDocumentationExtensions { + private class AsyncLock + { + private SemaphoreSlim _lock = new SemaphoreSlim(1, 1); + + public async Task RunAsync(Func> lockedAction) + { + try + { +#if !LEGACY + await _lock.WaitAsync(); +#else + _lock.Wait(); +#endif + return await lockedAction(); + } + finally + { + _lock.Release(); + } + } + + + public async Task RunAsync(Func lockedAction) + { + try + { +#if !LEGACY + await _lock.WaitAsync(); +#else + _lock.Wait(); +#endif + await lockedAction(); + } + finally + { + _lock.Release(); + } + } + } + + private static readonly AsyncLock Lock = new AsyncLock(); + private static readonly Dictionary> Cache = new Dictionary>(StringComparer.OrdinalIgnoreCase); @@ -52,6 +95,7 @@ public static Task GetXmlDocumentationAsync(this Type type) return GetXmlDocumentationTagAsync(type, "summary"); } + /// Returns the contents of an XML documentation tag for the specified member. /// The type. /// Name of the tag. @@ -79,116 +123,6 @@ public static async Task GetXmlRemarksAsync(this MemberInfo member) return await GetXmlDocumentationTagAsync(member, "remarks").ConfigureAwait(false); } - /// Returns the contents of an XML documentation tag for the specified member. - /// The reflected member. - /// The contents of the "summary" tag for the member. - public static async Task GetXmlDocumentationAsync(this MemberInfo member) - { - if (DynamicApis.SupportsXPathApis == false || DynamicApis.SupportsFileApis == false || DynamicApis.SupportsPathApis == false) - return null; - - var assemblyName = member.Module.Assembly.GetName(); - if (IgnoreAssembly(assemblyName)) - return null; - - var documentationPath = await GetXmlDocumentationPathAsync(member.Module.Assembly).ConfigureAwait(false); - return await GetXmlDocumentationAsync(member, documentationPath).ConfigureAwait(false); - } - - /// Returns the contents of an XML documentation tag for the specified member. - /// The reflected member. - /// Name of the tag. - /// The contents of the "summary" tag for the member. - public static async Task GetXmlDocumentationTagAsync(this MemberInfo member, string tagName) - { - if (DynamicApis.SupportsXPathApis == false || DynamicApis.SupportsFileApis == false || DynamicApis.SupportsPathApis == false) - return string.Empty; - - var assemblyName = member.Module.Assembly.GetName(); - if (IgnoreAssembly(assemblyName)) - return string.Empty; - - var documentationPath = await GetXmlDocumentationPathAsync(member.Module.Assembly).ConfigureAwait(false); - var element = await GetXmlDocumentationAsync(member, documentationPath).ConfigureAwait(false); - return RemoveLineBreakWhiteSpaces(GetXmlDocumentationText(element?.Element(tagName))); - } - - /// Returns the contents of the "returns" or "param" XML documentation tag for the specified parameter. - /// The reflected parameter or return info. - /// The contents of the "returns" or "param" tag. - public static async Task GetXmlDocumentationAsync(this ParameterInfo parameter) - { - if (DynamicApis.SupportsXPathApis == false || DynamicApis.SupportsFileApis == false || DynamicApis.SupportsPathApis == false) - return string.Empty; - - var assemblyName = parameter.Member.Module.Assembly.GetName(); - if (IgnoreAssembly(assemblyName)) - return string.Empty; - - var documentationPath = await GetXmlDocumentationPathAsync(parameter.Member.Module.Assembly).ConfigureAwait(false); - var element = await GetXmlDocumentationAsync(parameter, documentationPath).ConfigureAwait(false); - return RemoveLineBreakWhiteSpaces(GetXmlDocumentationText(element)); - } - - /// Returns the contents of the "summary" XML documentation tag for the specified member. - /// The type. - /// The path to the XML documentation file. - /// The contents of the "summary" tag for the member. - public static Task GetXmlDocumentationAsync(this Type type, string pathToXmlFile) - { - return ((MemberInfo)type.GetTypeInfo()).GetXmlDocumentationAsync(pathToXmlFile); - } - - /// Returns the contents of the "summary" XML documentation tag for the specified member. - /// The reflected member. - /// The path to the XML documentation file. - /// The contents of the "summary" tag for the member. - public static async Task GetXmlDocumentationAsync(this MemberInfo member, string pathToXmlFile) - { - try - { - if (pathToXmlFile == null || DynamicApis.SupportsXPathApis == false || DynamicApis.SupportsFileApis == false || DynamicApis.SupportsPathApis == false) - return null; - - var assemblyName = member.Module.Assembly.GetName(); - var document = await TryGetXmlDocumentAsync(assemblyName, pathToXmlFile).ConfigureAwait(false); - if (document == null) - return null; - - var element = GetXmlDocumentation(member, document); - await ReplaceInheritdocElementsAsync(member, element).ConfigureAwait(false); - return element; - } - catch - { - return null; - } - } - - /// Returns the contents of the "returns" or "param" XML documentation tag for the specified parameter. - /// The reflected parameter or return info. - /// The path to the XML documentation file. - /// The contents of the "returns" or "param" tag. - public static async Task GetXmlDocumentationAsync(this ParameterInfo parameter, string pathToXmlFile) - { - try - { - if (pathToXmlFile == null || DynamicApis.SupportsXPathApis == false || DynamicApis.SupportsFileApis == false || DynamicApis.SupportsPathApis == false) - return null; - - var assemblyName = parameter.Member.Module.Assembly.GetName(); - var document = await TryGetXmlDocumentAsync(assemblyName, pathToXmlFile).ConfigureAwait(false); - if (document == null) - return null; - - return await GetXmlDocumentationAsync(parameter, document).ConfigureAwait(false); - } - catch - { - return null; - } - } - /// Gets the description of the given member (based on the DescriptionAttribute, DisplayAttribute or XML Documentation). /// The member info /// The attributes. @@ -291,60 +225,191 @@ public static string GetXmlDocumentationText(this XElement element) /// Clears the cache. /// The task. - public static void ClearCache() + public static Task ClearCacheAsync() { - lock (Cache) + return Lock.RunAsync(() => { Cache.Clear(); - } + return DynamicApis.FromResult(null); + }); } - private static async Task TryGetXmlDocumentAsync(AssemblyName assemblyName, string pathToXmlFile) + /// Returns the contents of an XML documentation tag for the specified member. + /// The reflected member. + /// Name of the tag. + /// The contents of the "summary" tag for the member. + public static async Task GetXmlDocumentationTagAsync(this MemberInfo member, string tagName) { - var containsKey = false; - lock (Cache) + if (DynamicApis.SupportsXPathApis == false || DynamicApis.SupportsFileApis == false || DynamicApis.SupportsPathApis == false) + return string.Empty; + + var assemblyName = member.Module.Assembly.GetName(); + return await Lock.RunAsync(async () => { - containsKey = Cache.ContainsKey(assemblyName.FullName); - } + if (IgnoreAssembly(assemblyName)) + return string.Empty; - if (!containsKey) + var documentationPath = await GetXmlDocumentationPathAsync(member.Module.Assembly).ConfigureAwait(false); + var element = await GetXmlDocumentationWithoutLockAsync(member, documentationPath).ConfigureAwait(false); + return RemoveLineBreakWhiteSpaces(GetXmlDocumentationText(element?.Element(tagName))); + }).ConfigureAwait(false); + } + + /// Returns the contents of the "returns" or "param" XML documentation tag for the specified parameter. + /// The reflected parameter or return info. + /// The contents of the "returns" or "param" tag. + public static async Task GetXmlDocumentationAsync(this ParameterInfo parameter) + { + if (DynamicApis.SupportsXPathApis == false || DynamicApis.SupportsFileApis == false || DynamicApis.SupportsPathApis == false) + return string.Empty; + + var assemblyName = parameter.Member.Module.Assembly.GetName(); + return await Lock.RunAsync(async () => { - if (await DynamicApis.FileExistsAsync(pathToXmlFile).ConfigureAwait(false) == false) - { - lock (Cache) - { - Cache[assemblyName.FullName] = DynamicApis.FromResult(null); - return null; - } - } + if (IgnoreAssembly(assemblyName)) + return string.Empty; + + var documentationPath = await GetXmlDocumentationPathAsync(parameter.Member.Module.Assembly).ConfigureAwait(false); + var element = await GetXmlDocumentationWithoutLockAsync(parameter, documentationPath).ConfigureAwait(false); + return RemoveLineBreakWhiteSpaces(GetXmlDocumentationText(element)); + }).ConfigureAwait(false); + } + + /// Returns the contents of the "summary" XML documentation tag for the specified member. + /// The type. + /// The path to the XML documentation file. + /// The contents of the "summary" tag for the member. + public static Task GetXmlDocumentationAsync(this Type type, string pathToXmlFile) + { + return Lock.RunAsync(async () => + { + return await ((MemberInfo)type.GetTypeInfo()).GetXmlDocumentationWithoutLockAsync(pathToXmlFile).ConfigureAwait(false); + }); + } + + /// Returns the contents of the "returns" or "param" XML documentation tag for the specified parameter. + /// The reflected parameter or return info. + /// The path to the XML documentation file. + /// The contents of the "returns" or "param" tag. + public static Task GetXmlDocumentationAsync(this ParameterInfo parameter, string pathToXmlFile) + { + try + { + if (pathToXmlFile == null || DynamicApis.SupportsXPathApis == false || DynamicApis.SupportsFileApis == false || DynamicApis.SupportsPathApis == false) + return null; - lock (Cache) + var assemblyName = parameter.Member.Module.Assembly.GetName(); + return Lock.RunAsync(async () => { - if (!Cache.ContainsKey(assemblyName.FullName)) - { - Cache[assemblyName.FullName] = Task.Factory.StartNew(() => XDocument.Load(pathToXmlFile, LoadOptions.PreserveWhitespace)); - } - } + return await GetXmlDocumentationWithoutLockAsync(parameter, pathToXmlFile); + }); + } + catch + { + return null; } + } - Task task = null; - lock (Cache) + /// Returns the contents of an XML documentation tag for the specified member. + /// The reflected member. + /// The contents of the "summary" tag for the member. + public static Task GetXmlDocumentationAsync(this MemberInfo member) + { + return Lock.RunAsync(() => + { + return GetXmlDocumentationWithoutLockAsync(member); + }); + } + + /// Returns the contents of the "summary" XML documentation tag for the specified member. + /// The reflected member. + /// The path to the XML documentation file. + /// The contents of the "summary" tag for the member. + public static Task GetXmlDocumentationAsync(this MemberInfo member, string pathToXmlFile) + { + return Lock.RunAsync(() => { - task = Cache[assemblyName.FullName]; + return GetXmlDocumentationWithoutLockAsync(member, pathToXmlFile); + }); + } + + private static async Task GetXmlDocumentationWithoutLockAsync(this ParameterInfo parameter, string pathToXmlFile) + { + try + { + if (pathToXmlFile == null || DynamicApis.SupportsXPathApis == false || DynamicApis.SupportsFileApis == false || DynamicApis.SupportsPathApis == false) + return null; + + var assemblyName = parameter.Member.Module.Assembly.GetName(); + var document = await TryGetXmlDocumentAsync(assemblyName, pathToXmlFile).ConfigureAwait(false); + if (document == null) + return null; + + return await GetXmlDocumentationAsync(parameter, document).ConfigureAwait(false); + } + catch + { + return null; } + } - return await task.ConfigureAwait(false); + private static async Task GetXmlDocumentationWithoutLockAsync(this MemberInfo member) + { + if (DynamicApis.SupportsXPathApis == false || DynamicApis.SupportsFileApis == false || DynamicApis.SupportsPathApis == false) + return null; + + var assemblyName = member.Module.Assembly.GetName(); + if (IgnoreAssembly(assemblyName)) + return null; + + var documentationPath = await GetXmlDocumentationPathAsync(member.Module.Assembly).ConfigureAwait(false); + return await GetXmlDocumentationWithoutLockAsync(member, documentationPath).ConfigureAwait(false); } - private static bool IgnoreAssembly(AssemblyName assemblyName) + private static async Task GetXmlDocumentationWithoutLockAsync(this MemberInfo member, string pathToXmlFile) { - lock (Cache) + try { - if (Cache.ContainsKey(assemblyName.FullName) && Cache[assemblyName.FullName] == null) - return true; + if (pathToXmlFile == null || DynamicApis.SupportsXPathApis == false || DynamicApis.SupportsFileApis == false || DynamicApis.SupportsPathApis == false) + return null; + + var assemblyName = member.Module.Assembly.GetName(); + var document = await TryGetXmlDocumentAsync(assemblyName, pathToXmlFile).ConfigureAwait(false); + if (document == null) + return null; - return false; + var element = GetXmlDocumentation(member, document); + await ReplaceInheritdocElementsAsync(member, element).ConfigureAwait(false); + return element; + } + catch + { + return null; + } + } + + private static async Task TryGetXmlDocumentAsync(AssemblyName assemblyName, string pathToXmlFile) + { + if (!Cache.ContainsKey(assemblyName.FullName)) + { + if (await DynamicApis.FileExistsAsync(pathToXmlFile).ConfigureAwait(false) == false) + { + Cache[assemblyName.FullName] = null; + return null; + } + + Cache[assemblyName.FullName] = Task.Factory.StartNew(() => XDocument.Load(pathToXmlFile, LoadOptions.PreserveWhitespace)); } + + return await Cache[assemblyName.FullName].ConfigureAwait(false); + } + + private static bool IgnoreAssembly(AssemblyName assemblyName) + { + if (Cache.ContainsKey(assemblyName.FullName) && Cache[assemblyName.FullName] == null) + return true; + + return false; } private static XElement GetXmlDocumentation(this MemberInfo member, XDocument xml) @@ -385,14 +450,11 @@ private static async Task ReplaceInheritdocElementsAsync(this MemberInfo member, var baseMember = baseType?.GetTypeInfo().DeclaredMembers.SingleOrDefault(m => m.Name == member.Name); if (baseMember != null) { - var baseDoc = await baseMember.GetXmlDocumentationAsync().ConfigureAwait(false); + var baseDoc = await baseMember.GetXmlDocumentationWithoutLockAsync().ConfigureAwait(false); if (baseDoc != null) { - lock (Cache) // modify xml (possible race condition) - { - var nodes = baseDoc.Nodes().OfType().ToArray(); - child.ReplaceWith(nodes); - } + var nodes = baseDoc.Nodes().OfType().ToArray(); + child.ReplaceWith(nodes); } else { @@ -416,14 +478,11 @@ private static async Task ProcessInheritdocInterfaceElementsAsync(this MemberInf var baseMember = baseInterface?.GetTypeInfo().DeclaredMembers.SingleOrDefault(m => m.Name == member.Name); if (baseMember != null) { - var baseDoc = await baseMember.GetXmlDocumentationAsync().ConfigureAwait(false); + var baseDoc = await baseMember.GetXmlDocumentationWithoutLockAsync().ConfigureAwait(false); if (baseDoc != null) { - lock (Cache) // modify xml (possible race condition) - { - var nodes = baseDoc.Nodes().OfType().ToArray(); - child.ReplaceWith(nodes); - } + var nodes = baseDoc.Nodes().OfType().ToArray(); + child.ReplaceWith(nodes); } } } @@ -542,4 +601,4 @@ private static async Task GetXmlDocumentationPathAsync(dynamic assembly) } } } -} +} \ No newline at end of file From 784cb631298e66d224c0c9c806cdf7855bacf37a Mon Sep 17 00:00:00 2001 From: Rico Suter Date: Thu, 20 Sep 2018 21:08:12 +0200 Subject: [PATCH 5/9] Improve performance --- .../Infrastructure/XmlDocumentationExtensions.cs | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/src/NJsonSchema/Infrastructure/XmlDocumentationExtensions.cs b/src/NJsonSchema/Infrastructure/XmlDocumentationExtensions.cs index 357a1dddf..b1bea4741 100644 --- a/src/NJsonSchema/Infrastructure/XmlDocumentationExtensions.cs +++ b/src/NJsonSchema/Infrastructure/XmlDocumentationExtensions.cs @@ -337,7 +337,7 @@ private static async Task GetXmlDocumentationWithoutLockAsync(this Par { try { - if (pathToXmlFile == null || DynamicApis.SupportsXPathApis == false || DynamicApis.SupportsFileApis == false || DynamicApis.SupportsPathApis == false) + if (DynamicApis.SupportsXPathApis == false || DynamicApis.SupportsFileApis == false || DynamicApis.SupportsPathApis == false) return null; var assemblyName = parameter.Member.Module.Assembly.GetName(); @@ -370,7 +370,7 @@ private static async Task GetXmlDocumentationWithoutLockAsync(this Mem { try { - if (pathToXmlFile == null || DynamicApis.SupportsXPathApis == false || DynamicApis.SupportsFileApis == false || DynamicApis.SupportsPathApis == false) + if (DynamicApis.SupportsXPathApis == false || DynamicApis.SupportsFileApis == false || DynamicApis.SupportsPathApis == false) return null; var assemblyName = member.Module.Assembly.GetName(); @@ -567,6 +567,9 @@ private static async Task GetXmlDocumentationPathAsync(dynamic assembly) if (string.IsNullOrEmpty(assemblyName.Name)) return null; + if (Cache.ContainsKey(assemblyName.FullName)) + return null; + var assemblyDirectory = DynamicApis.PathGetDirectoryName((string)assembly.Location); var path = DynamicApis.PathCombine(assemblyDirectory, (string)assemblyName.Name + ".xml"); if (await DynamicApis.FileExistsAsync(path).ConfigureAwait(false)) From d832952542f64adafbb6c526a2c92d8180df4f13 Mon Sep 17 00:00:00 2001 From: Rico Suter Date: Fri, 21 Sep 2018 11:27:38 +0200 Subject: [PATCH 6/9] Various patches --- src/NJsonSchema/Generation/JsonSchemaGenerator.cs | 4 ++-- src/NJsonSchema/Infrastructure/DynamicApis.cs | 4 ++-- .../Infrastructure/XmlDocumentationExtensions.cs | 15 +++++++-------- src/NJsonSchema/JsonReferenceResolver.cs | 4 ++-- src/NJsonSchema/JsonSchema4.cs | 2 +- src/NJsonSchema/JsonSchemaReferenceUtilities.cs | 4 ++-- 6 files changed, 16 insertions(+), 17 deletions(-) diff --git a/src/NJsonSchema/Generation/JsonSchemaGenerator.cs b/src/NJsonSchema/Generation/JsonSchemaGenerator.cs index ff733f2fb..1947bc34e 100644 --- a/src/NJsonSchema/Generation/JsonSchemaGenerator.cs +++ b/src/NJsonSchema/Generation/JsonSchemaGenerator.cs @@ -325,7 +325,7 @@ public virtual string GetPropertyName(Newtonsoft.Json.Serialization.JsonProperty schema.IsAbstract = type.GetTypeInfo().IsAbstract; await GeneratePropertiesAndInheritanceAsync(type, schema, schemaResolver).ConfigureAwait(false); - await ApplyAdditionalPropertiesAsync(type, schema, schemaResolver); + await ApplyAdditionalPropertiesAsync(type, schema, schemaResolver).ConfigureAwait(false); if (Settings.GenerateKnownTypes) await GenerateKnownTypesAsync(type, schemaResolver).ConfigureAwait(false); @@ -345,7 +345,7 @@ public virtual string GetPropertyName(Newtonsoft.Json.Serialization.JsonProperty var genericTypeArguments = extensionDataProperty.PropertyType.GetGenericTypeArguments(); var extensionDataPropertyType = genericTypeArguments.Length == 2 ? genericTypeArguments[1] : typeof(object); - schema.AdditionalPropertiesSchema = await GenerateWithReferenceAndNullabilityAsync(extensionDataPropertyType, null, schemaResolver); + schema.AdditionalPropertiesSchema = await GenerateWithReferenceAndNullabilityAsync(extensionDataPropertyType, null, schemaResolver).ConfigureAwait(false); } else schema.AllowAdditionalProperties = false; diff --git a/src/NJsonSchema/Infrastructure/DynamicApis.cs b/src/NJsonSchema/Infrastructure/DynamicApis.cs index 99e425932..8743f4a2d 100644 --- a/src/NJsonSchema/Infrastructure/DynamicApis.cs +++ b/src/NJsonSchema/Infrastructure/DynamicApis.cs @@ -121,7 +121,7 @@ public static async Task DirectoryExistsAsync(string filePath) return false; return await FromResult((bool)DirectoryType.GetRuntimeMethod("Exists", - new[] { typeof(string) }).Invoke(null, new object[] { filePath })); + new[] { typeof(string) }).Invoke(null, new object[] { filePath })).ConfigureAwait(false); } /// Checks whether a file exists. @@ -137,7 +137,7 @@ public static async Task FileExistsAsync(string filePath) return false; return await FromResult((bool)FileType.GetRuntimeMethod("Exists", - new[] { typeof(string) }).Invoke(null, new object[] { filePath })); + new[] { typeof(string) }).Invoke(null, new object[] { filePath })).ConfigureAwait(false); } /// Reads all content of a file (UTF8). diff --git a/src/NJsonSchema/Infrastructure/XmlDocumentationExtensions.cs b/src/NJsonSchema/Infrastructure/XmlDocumentationExtensions.cs index b1bea4741..eb7a9b259 100644 --- a/src/NJsonSchema/Infrastructure/XmlDocumentationExtensions.cs +++ b/src/NJsonSchema/Infrastructure/XmlDocumentationExtensions.cs @@ -32,11 +32,11 @@ public async Task RunAsync(Func> lockedAction) try { #if !LEGACY - await _lock.WaitAsync(); + await _lock.WaitAsync().ConfigureAwait(false); #else _lock.Wait(); #endif - return await lockedAction(); + return await lockedAction().ConfigureAwait(false); } finally { @@ -50,11 +50,11 @@ public async Task RunAsync(Func lockedAction) try { #if !LEGACY - await _lock.WaitAsync(); + await _lock.WaitAsync().ConfigureAwait(false); #else _lock.Wait(); #endif - await lockedAction(); + await lockedAction().ConfigureAwait(false); } finally { @@ -65,8 +65,7 @@ public async Task RunAsync(Func lockedAction) private static readonly AsyncLock Lock = new AsyncLock(); - private static readonly Dictionary> Cache = - new Dictionary>(StringComparer.OrdinalIgnoreCase); + private static readonly Dictionary Cache = new Dictionary(StringComparer.OrdinalIgnoreCase); #if !LEGACY @@ -398,10 +397,10 @@ private static async Task TryGetXmlDocumentAsync(AssemblyName assembl return null; } - Cache[assemblyName.FullName] = Task.Factory.StartNew(() => XDocument.Load(pathToXmlFile, LoadOptions.PreserveWhitespace)); + Cache[assemblyName.FullName] = await Task.Factory.StartNew(() => XDocument.Load(pathToXmlFile, LoadOptions.PreserveWhitespace)).ConfigureAwait(false); } - return await Cache[assemblyName.FullName].ConfigureAwait(false); + return Cache[assemblyName.FullName]; } private static bool IgnoreAssembly(AssemblyName assemblyName) diff --git a/src/NJsonSchema/JsonReferenceResolver.cs b/src/NJsonSchema/JsonReferenceResolver.cs index dc22ad9ae..d6e26c068 100644 --- a/src/NJsonSchema/JsonReferenceResolver.cs +++ b/src/NJsonSchema/JsonReferenceResolver.cs @@ -58,7 +58,7 @@ public void AddDocumentReference(string documentPath, IJsonReference schema) /// Could not resolve the JSON path. public async Task ResolveReferenceAsync(object rootObject, string jsonPath) { - return await ResolveReferenceAsync(rootObject, jsonPath, true); + return await ResolveReferenceAsync(rootObject, jsonPath, true).ConfigureAwait(false); } /// Gets the object from the given JSON path. @@ -69,7 +69,7 @@ public async Task ResolveReferenceAsync(object rootObject, strin /// Could not resolve the JSON path. public async Task ResolveReferenceWithoutAppendAsync(object rootObject, string jsonPath) { - return await ResolveReferenceAsync(rootObject, jsonPath, false); + return await ResolveReferenceAsync(rootObject, jsonPath, false).ConfigureAwait(false); } /// Resolves a document reference. diff --git a/src/NJsonSchema/JsonSchema4.cs b/src/NJsonSchema/JsonSchema4.cs index e9a230b91..87f408e28 100644 --- a/src/NJsonSchema/JsonSchema4.cs +++ b/src/NJsonSchema/JsonSchema4.cs @@ -158,7 +158,7 @@ public static async Task FromUrlAsync(string url) /// The HttpClient.GetAsync API is not available on this platform. public static async Task FromUrlAsync(string url, Func referenceResolverFactory) { - var data = await DynamicApis.HttpGetAsync(url); + var data = await DynamicApis.HttpGetAsync(url).ConfigureAwait(false); return await FromJsonAsync(data, url, referenceResolverFactory).ConfigureAwait(false); } diff --git a/src/NJsonSchema/JsonSchemaReferenceUtilities.cs b/src/NJsonSchema/JsonSchemaReferenceUtilities.cs index 13e0f9c1c..018fcb35a 100644 --- a/src/NJsonSchema/JsonSchemaReferenceUtilities.cs +++ b/src/NJsonSchema/JsonSchemaReferenceUtilities.cs @@ -96,9 +96,9 @@ public JsonReferenceUpdater(object rootObject, JsonReferenceResolver referenceRe public override async Task VisitAsync(object obj) { _replaceRefsRound = true; - await base.VisitAsync(obj); + await base.VisitAsync(obj).ConfigureAwait(false); _replaceRefsRound = false; - await base.VisitAsync(obj); + await base.VisitAsync(obj).ConfigureAwait(false); } protected override async Task VisitJsonReferenceAsync(IJsonReference reference, string path, string typeNameHint) From 7a7803fd8f48e2231b5db18ea7a8dc1ffcf56c60 Mon Sep 17 00:00:00 2001 From: Rico Suter Date: Fri, 21 Sep 2018 11:49:20 +0200 Subject: [PATCH 7/9] Use other impl --- .../XmlDocumentationExtensions.cs | 82 +++++++------------ 1 file changed, 30 insertions(+), 52 deletions(-) diff --git a/src/NJsonSchema/Infrastructure/XmlDocumentationExtensions.cs b/src/NJsonSchema/Infrastructure/XmlDocumentationExtensions.cs index eb7a9b259..4b52ab27c 100644 --- a/src/NJsonSchema/Infrastructure/XmlDocumentationExtensions.cs +++ b/src/NJsonSchema/Infrastructure/XmlDocumentationExtensions.cs @@ -23,43 +23,23 @@ namespace NJsonSchema.Infrastructure /// This class currently works only on the desktop .NET framework. public static class XmlDocumentationExtensions { - private class AsyncLock + private class AsyncLock : IDisposable { - private SemaphoreSlim _lock = new SemaphoreSlim(1, 1); + private SemaphoreSlim _semaphoreSlim = new SemaphoreSlim(1, 1); - public async Task RunAsync(Func> lockedAction) + public async Task LockAsync() { - try - { #if !LEGACY - await _lock.WaitAsync().ConfigureAwait(false); + await _semaphoreSlim.WaitAsync(); #else - _lock.Wait(); + _semaphoreSlim.Wait(); #endif - return await lockedAction().ConfigureAwait(false); - } - finally - { - _lock.Release(); - } + return this; } - - public async Task RunAsync(Func lockedAction) + public void Dispose() { - try - { -#if !LEGACY - await _lock.WaitAsync().ConfigureAwait(false); -#else - _lock.Wait(); -#endif - await lockedAction().ConfigureAwait(false); - } - finally - { - _lock.Release(); - } + _semaphoreSlim.Release(); } } @@ -94,7 +74,6 @@ public static Task GetXmlDocumentationAsync(this Type type) return GetXmlDocumentationTagAsync(type, "summary"); } - /// Returns the contents of an XML documentation tag for the specified member. /// The type. /// Name of the tag. @@ -224,13 +203,12 @@ public static string GetXmlDocumentationText(this XElement element) /// Clears the cache. /// The task. - public static Task ClearCacheAsync() + public static async Task ClearCacheAsync() { - return Lock.RunAsync(() => + using (await Lock.LockAsync().ConfigureAwait(false)) { Cache.Clear(); - return DynamicApis.FromResult(null); - }); + } } /// Returns the contents of an XML documentation tag for the specified member. @@ -243,7 +221,7 @@ public static async Task GetXmlDocumentationTagAsync(this MemberInfo mem return string.Empty; var assemblyName = member.Module.Assembly.GetName(); - return await Lock.RunAsync(async () => + using (await Lock.LockAsync().ConfigureAwait(false)) { if (IgnoreAssembly(assemblyName)) return string.Empty; @@ -251,7 +229,7 @@ public static async Task GetXmlDocumentationTagAsync(this MemberInfo mem var documentationPath = await GetXmlDocumentationPathAsync(member.Module.Assembly).ConfigureAwait(false); var element = await GetXmlDocumentationWithoutLockAsync(member, documentationPath).ConfigureAwait(false); return RemoveLineBreakWhiteSpaces(GetXmlDocumentationText(element?.Element(tagName))); - }).ConfigureAwait(false); + } } /// Returns the contents of the "returns" or "param" XML documentation tag for the specified parameter. @@ -263,7 +241,7 @@ public static async Task GetXmlDocumentationAsync(this ParameterInfo par return string.Empty; var assemblyName = parameter.Member.Module.Assembly.GetName(); - return await Lock.RunAsync(async () => + using (await Lock.LockAsync().ConfigureAwait(false)) { if (IgnoreAssembly(assemblyName)) return string.Empty; @@ -271,26 +249,26 @@ public static async Task GetXmlDocumentationAsync(this ParameterInfo par var documentationPath = await GetXmlDocumentationPathAsync(parameter.Member.Module.Assembly).ConfigureAwait(false); var element = await GetXmlDocumentationWithoutLockAsync(parameter, documentationPath).ConfigureAwait(false); return RemoveLineBreakWhiteSpaces(GetXmlDocumentationText(element)); - }).ConfigureAwait(false); + } } /// Returns the contents of the "summary" XML documentation tag for the specified member. /// The type. /// The path to the XML documentation file. /// The contents of the "summary" tag for the member. - public static Task GetXmlDocumentationAsync(this Type type, string pathToXmlFile) + public static async Task GetXmlDocumentationAsync(this Type type, string pathToXmlFile) { - return Lock.RunAsync(async () => + using (await Lock.LockAsync().ConfigureAwait(false)) { return await ((MemberInfo)type.GetTypeInfo()).GetXmlDocumentationWithoutLockAsync(pathToXmlFile).ConfigureAwait(false); - }); + } } /// Returns the contents of the "returns" or "param" XML documentation tag for the specified parameter. /// The reflected parameter or return info. /// The path to the XML documentation file. /// The contents of the "returns" or "param" tag. - public static Task GetXmlDocumentationAsync(this ParameterInfo parameter, string pathToXmlFile) + public static async Task GetXmlDocumentationAsync(this ParameterInfo parameter, string pathToXmlFile) { try { @@ -298,10 +276,10 @@ public static Task GetXmlDocumentationAsync(this ParameterInfo paramet return null; var assemblyName = parameter.Member.Module.Assembly.GetName(); - return Lock.RunAsync(async () => + using (await Lock.LockAsync().ConfigureAwait(false)) { - return await GetXmlDocumentationWithoutLockAsync(parameter, pathToXmlFile); - }); + return await GetXmlDocumentationWithoutLockAsync(parameter, pathToXmlFile).ConfigureAwait(false); + } } catch { @@ -312,24 +290,24 @@ public static Task GetXmlDocumentationAsync(this ParameterInfo paramet /// Returns the contents of an XML documentation tag for the specified member. /// The reflected member. /// The contents of the "summary" tag for the member. - public static Task GetXmlDocumentationAsync(this MemberInfo member) + public static async Task GetXmlDocumentationAsync(this MemberInfo member) { - return Lock.RunAsync(() => + using (await Lock.LockAsync().ConfigureAwait(false)) { - return GetXmlDocumentationWithoutLockAsync(member); - }); + return await GetXmlDocumentationWithoutLockAsync(member).ConfigureAwait(false); + } } /// Returns the contents of the "summary" XML documentation tag for the specified member. /// The reflected member. /// The path to the XML documentation file. /// The contents of the "summary" tag for the member. - public static Task GetXmlDocumentationAsync(this MemberInfo member, string pathToXmlFile) + public static async Task GetXmlDocumentationAsync(this MemberInfo member, string pathToXmlFile) { - return Lock.RunAsync(() => + using (await Lock.LockAsync().ConfigureAwait(false)) { - return GetXmlDocumentationWithoutLockAsync(member, pathToXmlFile); - }); + return await GetXmlDocumentationWithoutLockAsync(member, pathToXmlFile).ConfigureAwait(false); + } } private static async Task GetXmlDocumentationWithoutLockAsync(this ParameterInfo parameter, string pathToXmlFile) From 009f3e6aa5b8b43c814fe5d673d3cb2c08f3b683 Mon Sep 17 00:00:00 2001 From: Rico Suter Date: Fri, 21 Sep 2018 13:08:05 +0200 Subject: [PATCH 8/9] Use sync lock --- .../XmlDocumentationExtensions.cs | 23 ++++++++----------- 1 file changed, 10 insertions(+), 13 deletions(-) diff --git a/src/NJsonSchema/Infrastructure/XmlDocumentationExtensions.cs b/src/NJsonSchema/Infrastructure/XmlDocumentationExtensions.cs index 4b52ab27c..10336373a 100644 --- a/src/NJsonSchema/Infrastructure/XmlDocumentationExtensions.cs +++ b/src/NJsonSchema/Infrastructure/XmlDocumentationExtensions.cs @@ -27,13 +27,9 @@ private class AsyncLock : IDisposable { private SemaphoreSlim _semaphoreSlim = new SemaphoreSlim(1, 1); - public async Task LockAsync() + public AsyncLock Lock() { -#if !LEGACY - await _semaphoreSlim.WaitAsync(); -#else _semaphoreSlim.Wait(); -#endif return this; } @@ -203,11 +199,12 @@ public static string GetXmlDocumentationText(this XElement element) /// Clears the cache. /// The task. - public static async Task ClearCacheAsync() + public static Task ClearCacheAsync() { - using (await Lock.LockAsync().ConfigureAwait(false)) + using (Lock.Lock()) { Cache.Clear(); + return DynamicApis.FromResult(null); } } @@ -221,7 +218,7 @@ public static async Task GetXmlDocumentationTagAsync(this MemberInfo mem return string.Empty; var assemblyName = member.Module.Assembly.GetName(); - using (await Lock.LockAsync().ConfigureAwait(false)) + using (Lock.Lock()) { if (IgnoreAssembly(assemblyName)) return string.Empty; @@ -241,7 +238,7 @@ public static async Task GetXmlDocumentationAsync(this ParameterInfo par return string.Empty; var assemblyName = parameter.Member.Module.Assembly.GetName(); - using (await Lock.LockAsync().ConfigureAwait(false)) + using (Lock.Lock()) { if (IgnoreAssembly(assemblyName)) return string.Empty; @@ -258,7 +255,7 @@ public static async Task GetXmlDocumentationAsync(this ParameterInfo par /// The contents of the "summary" tag for the member. public static async Task GetXmlDocumentationAsync(this Type type, string pathToXmlFile) { - using (await Lock.LockAsync().ConfigureAwait(false)) + using (Lock.Lock()) { return await ((MemberInfo)type.GetTypeInfo()).GetXmlDocumentationWithoutLockAsync(pathToXmlFile).ConfigureAwait(false); } @@ -276,7 +273,7 @@ public static async Task GetXmlDocumentationAsync(this ParameterInfo p return null; var assemblyName = parameter.Member.Module.Assembly.GetName(); - using (await Lock.LockAsync().ConfigureAwait(false)) + using (Lock.Lock()) { return await GetXmlDocumentationWithoutLockAsync(parameter, pathToXmlFile).ConfigureAwait(false); } @@ -292,7 +289,7 @@ public static async Task GetXmlDocumentationAsync(this ParameterInfo p /// The contents of the "summary" tag for the member. public static async Task GetXmlDocumentationAsync(this MemberInfo member) { - using (await Lock.LockAsync().ConfigureAwait(false)) + using (Lock.Lock()) { return await GetXmlDocumentationWithoutLockAsync(member).ConfigureAwait(false); } @@ -304,7 +301,7 @@ public static async Task GetXmlDocumentationAsync(this MemberInfo memb /// The contents of the "summary" tag for the member. public static async Task GetXmlDocumentationAsync(this MemberInfo member, string pathToXmlFile) { - using (await Lock.LockAsync().ConfigureAwait(false)) + using (Lock.Lock()) { return await GetXmlDocumentationWithoutLockAsync(member, pathToXmlFile).ConfigureAwait(false); } From 48774b58b8c235dbf03ffada5b4d5eedb7b9252a Mon Sep 17 00:00:00 2001 From: Rico Suter Date: Fri, 21 Sep 2018 13:46:57 +0200 Subject: [PATCH 9/9] Move private class --- .../XmlDocumentationExtensions.cs | 33 +++++++++---------- 1 file changed, 16 insertions(+), 17 deletions(-) diff --git a/src/NJsonSchema/Infrastructure/XmlDocumentationExtensions.cs b/src/NJsonSchema/Infrastructure/XmlDocumentationExtensions.cs index 10336373a..4100d4aaa 100644 --- a/src/NJsonSchema/Infrastructure/XmlDocumentationExtensions.cs +++ b/src/NJsonSchema/Infrastructure/XmlDocumentationExtensions.cs @@ -23,24 +23,7 @@ namespace NJsonSchema.Infrastructure /// This class currently works only on the desktop .NET framework. public static class XmlDocumentationExtensions { - private class AsyncLock : IDisposable - { - private SemaphoreSlim _semaphoreSlim = new SemaphoreSlim(1, 1); - - public AsyncLock Lock() - { - _semaphoreSlim.Wait(); - return this; - } - - public void Dispose() - { - _semaphoreSlim.Release(); - } - } - private static readonly AsyncLock Lock = new AsyncLock(); - private static readonly Dictionary Cache = new Dictionary(StringComparer.OrdinalIgnoreCase); #if !LEGACY @@ -577,5 +560,21 @@ private static async Task GetXmlDocumentationPathAsync(dynamic assembly) return null; } } + + private class AsyncLock : IDisposable + { + private SemaphoreSlim _semaphoreSlim = new SemaphoreSlim(1, 1); + + public AsyncLock Lock() + { + _semaphoreSlim.Wait(); + return this; + } + + public void Dispose() + { + _semaphoreSlim.Release(); + } + } } } \ No newline at end of file