From 2b637553967308ced51b46c5aa3d7121f8b46b86 Mon Sep 17 00:00:00 2001 From: pkindruk Date: Fri, 2 Dec 2022 01:24:27 +0300 Subject: [PATCH 1/2] Add XmlSerializer cache to fix memory leaks. --- .../Layout/LayoutAnchorableFloatingWindow.cs | 4 +-- .../Layout/LayoutDocumentFloatingWindow.cs | 4 +-- .../AvalonDock/Layout/LayoutFloatingWindow.cs | 2 +- .../AvalonDock/Layout/LayoutGroup.cs | 4 +-- .../AvalonDock/Layout/LayoutRoot.cs | 2 +- .../Serialization/XmlLayoutSerializer.cs | 2 +- .../AvalonDock/XmlSerializersCache.cs | 25 +++++++++++++++++++ 7 files changed, 34 insertions(+), 9 deletions(-) create mode 100644 source/Components/AvalonDock/XmlSerializersCache.cs diff --git a/source/Components/AvalonDock/Layout/LayoutAnchorableFloatingWindow.cs b/source/Components/AvalonDock/Layout/LayoutAnchorableFloatingWindow.cs index 6bfe812c..abf7b830 100644 --- a/source/Components/AvalonDock/Layout/LayoutAnchorableFloatingWindow.cs +++ b/source/Components/AvalonDock/Layout/LayoutAnchorableFloatingWindow.cs @@ -155,13 +155,13 @@ public override void ReadXml(XmlReader reader) XmlSerializer serializer; if (reader.LocalName.Equals(nameof(LayoutAnchorablePaneGroup))) - serializer = XmlSerializer.FromTypes(new[] { typeof(LayoutAnchorablePaneGroup) })[0]; + serializer = XmlSerializersCache.GetSerializer(); else { var type = LayoutRoot.FindType(reader.LocalName); if (type == null) throw new ArgumentException("AvalonDock.LayoutAnchorableFloatingWindow doesn't know how to deserialize " + reader.LocalName); - serializer = XmlSerializer.FromTypes(new[] { type })[0]; + serializer = XmlSerializersCache.GetSerializer(type); } RootPanel = (LayoutAnchorablePaneGroup)serializer.Deserialize(reader); } diff --git a/source/Components/AvalonDock/Layout/LayoutDocumentFloatingWindow.cs b/source/Components/AvalonDock/Layout/LayoutDocumentFloatingWindow.cs index 68d07d6a..d716b884 100644 --- a/source/Components/AvalonDock/Layout/LayoutDocumentFloatingWindow.cs +++ b/source/Components/AvalonDock/Layout/LayoutDocumentFloatingWindow.cs @@ -153,12 +153,12 @@ public override void ReadXml(XmlReader reader) XmlSerializer serializer; if (reader.LocalName.Equals(nameof(LayoutDocument))) - serializer = new XmlSerializer(typeof(LayoutDocument)); + serializer = XmlSerializersCache.GetSerializer(); else { var type = LayoutRoot.FindType(reader.LocalName); if (type == null) throw new ArgumentException("AvalonDock.LayoutDocumentFloatingWindow doesn't know how to deserialize " + reader.LocalName); - serializer = new XmlSerializer(type); + serializer = XmlSerializersCache.GetSerializer(type); } RootPanel = (LayoutDocumentPaneGroup)serializer.Deserialize(reader); } diff --git a/source/Components/AvalonDock/Layout/LayoutFloatingWindow.cs b/source/Components/AvalonDock/Layout/LayoutFloatingWindow.cs index 44ae1f14..d0b409e2 100644 --- a/source/Components/AvalonDock/Layout/LayoutFloatingWindow.cs +++ b/source/Components/AvalonDock/Layout/LayoutFloatingWindow.cs @@ -61,7 +61,7 @@ public virtual void WriteXml(XmlWriter writer) foreach (var child in Children) { var type = child.GetType(); - var serializer = XmlSerializer.FromTypes(new[] { type })[0]; + var serializer = XmlSerializersCache.GetSerializer(type); serializer.Serialize(writer, child); } } diff --git a/source/Components/AvalonDock/Layout/LayoutGroup.cs b/source/Components/AvalonDock/Layout/LayoutGroup.cs index 7b243e8b..a019a711 100644 --- a/source/Components/AvalonDock/Layout/LayoutGroup.cs +++ b/source/Components/AvalonDock/Layout/LayoutGroup.cs @@ -159,7 +159,7 @@ public virtual void ReadXml(System.Xml.XmlReader reader) if (typeForSerializer == null) throw new ArgumentException("AvalonDock.LayoutGroup doesn't know how to deserialize " + reader.LocalName); - XmlSerializer serializer = XmlSerializer.FromTypes(new[] { typeForSerializer })[0]; + XmlSerializer serializer = XmlSerializersCache.GetSerializer(typeForSerializer); Children.Add((T)serializer.Deserialize(reader)); } @@ -173,7 +173,7 @@ public virtual void WriteXml(System.Xml.XmlWriter writer) foreach (var child in Children) { var type = child.GetType(); - var serializer = XmlSerializer.FromTypes(new[] { type })[0]; + var serializer = XmlSerializersCache.GetSerializer(type); serializer.Serialize(writer, child); } } diff --git a/source/Components/AvalonDock/Layout/LayoutRoot.cs b/source/Components/AvalonDock/Layout/LayoutRoot.cs index ed905d49..957b8a8f 100644 --- a/source/Components/AvalonDock/Layout/LayoutRoot.cs +++ b/source/Components/AvalonDock/Layout/LayoutRoot.cs @@ -966,7 +966,7 @@ private object ReadElement(XmlReader reader) throw new ArgumentException("AvalonDock.LayoutRoot doesn't know how to deserialize " + reader.LocalName); break; } - XmlSerializer serializer = XmlSerializer.FromTypes(new[] { typeToSerialize })[0]; + XmlSerializer serializer = XmlSerializersCache.GetSerializer(typeToSerialize); return serializer.Deserialize(reader); } diff --git a/source/Components/AvalonDock/Layout/Serialization/XmlLayoutSerializer.cs b/source/Components/AvalonDock/Layout/Serialization/XmlLayoutSerializer.cs index d883b82d..66e8d8b5 100644 --- a/source/Components/AvalonDock/Layout/Serialization/XmlLayoutSerializer.cs +++ b/source/Components/AvalonDock/Layout/Serialization/XmlLayoutSerializer.cs @@ -55,7 +55,7 @@ private void DeserializeCommon(DeserializeFunction function) #region Public Methods - readonly XmlSerializer _serializer = XmlSerializer.FromTypes(new[] { typeof(LayoutRoot) })[0]; + readonly XmlSerializer _serializer = XmlSerializersCache.GetSerializer(); /// Serialize the layout into a . /// diff --git a/source/Components/AvalonDock/XmlSerializersCache.cs b/source/Components/AvalonDock/XmlSerializersCache.cs new file mode 100644 index 00000000..43f7d469 --- /dev/null +++ b/source/Components/AvalonDock/XmlSerializersCache.cs @@ -0,0 +1,25 @@ +using System; +using System.Collections.Generic; +using System.Xml.Serialization; + +namespace AvalonDock +{ + internal static class XmlSerializersCache + { + private static readonly Dictionary s_cache = new Dictionary(); + + + public static XmlSerializer GetSerializer(Type targetType) + { + if (!s_cache.TryGetValue(targetType, out var serializer)) + { + serializer = XmlSerializer.FromTypes(new[] { targetType })[0]; + s_cache.Add(targetType, serializer); + } + + return serializer; + } + + public static XmlSerializer GetSerializer() => GetSerializer(typeof(T)); + } +} From ef2eae0f6b3f535f4103e191ccfb0936ef5fcf0c Mon Sep 17 00:00:00 2001 From: pkindruk Date: Mon, 5 Dec 2022 14:50:04 +0300 Subject: [PATCH 2/2] Add thread safety to xml serializer cache. --- .../AvalonDock/XmlSerializersCache.cs | 19 ++++++++++++++----- 1 file changed, 14 insertions(+), 5 deletions(-) diff --git a/source/Components/AvalonDock/XmlSerializersCache.cs b/source/Components/AvalonDock/XmlSerializersCache.cs index 43f7d469..06846d09 100644 --- a/source/Components/AvalonDock/XmlSerializersCache.cs +++ b/source/Components/AvalonDock/XmlSerializersCache.cs @@ -1,20 +1,29 @@ using System; -using System.Collections.Generic; +using System.Collections.Concurrent; using System.Xml.Serialization; namespace AvalonDock { internal static class XmlSerializersCache { - private static readonly Dictionary s_cache = new Dictionary(); + private static readonly object s_lock = new object(); + private static readonly ConcurrentDictionary s_cache = new ConcurrentDictionary(); public static XmlSerializer GetSerializer(Type targetType) { - if (!s_cache.TryGetValue(targetType, out var serializer)) + if (s_cache.TryGetValue(targetType, out var serializer)) + return serializer; + + lock (s_lock) { - serializer = XmlSerializer.FromTypes(new[] { targetType })[0]; - s_cache.Add(targetType, serializer); + // we can have multiple threads waiting on lock + // one of them could create what we were looking for + if (!s_cache.TryGetValue(targetType, out serializer)) + { + serializer = XmlSerializer.FromTypes(new[] { targetType })[0]; + _ = s_cache.TryAdd(targetType, serializer); + } } return serializer;