Преглед изворни кода

[WebConfigurationManager] Fix memleak by using a LruCache (BXC#5598)

System.Web.ConfigurationManager had a cache which didn't have any max limit,
therefore when using Restful URIs in ASP.NET (like having some variable
parameter in the virtual path, i.e. /user/{userId}/tracks, where {userId}
is a variable Integer) this was very likely to cause a leak which would
end up causing troubles, like an InvalidCastException thrown by
HttpApplication::PreStart()

Fixes https://bugzilla.xamarin.com/show_bug.cgi?id=5598
Andres G. Aragoneses пре 12 година
родитељ
комит
96bbdc60bf

+ 1 - 0
mcs/class/System.Web/System.Web-net_4_0.csproj

@@ -282,6 +282,7 @@
     <Compile Include="System.Web.Configuration_2.0\IConfigMapPathFactory.cs" />
     <Compile Include="System.Web.Configuration_2.0\IdentitySection.cs" />
     <Compile Include="System.Web.Configuration_2.0\IRemoteWebConfigurationHostServer.cs" />
+    <Compile Include="System.Web.Configuration_2.0\LruCache.cs" />
     <Compile Include="System.Web.Configuration_2.0\LowerCaseStringConverter.cs" />
     <Compile Include="System.Web.Configuration_2.0\MachineKeyCompatibilityMode.cs" />
     <Compile Include="System.Web.Configuration_2.0\MachineKeyRegistryStorage.cs" />

+ 128 - 0
mcs/class/System.Web/System.Web.Configuration_2.0/LruCache.cs

@@ -0,0 +1,128 @@
+//
+// A simple LRU cache
+//
+// Authors:
+//   Miguel de Icaza ([email protected])
+//   Andres G. Aragoneses ([email protected])
+//
+// Copyright 2010 Miguel de Icaza
+// Copyright 2013 7digital Media Ltd.
+//
+// Permission is hereby granted, free of charge, to any person obtaining a copy
+// of this software and associated documentation files (the "Software"), to deal
+// in the Software without restriction, including without limitation the rights
+// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+// copies of the Software, and to permit persons to whom the Software is
+// furnished to do so, subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be included in
+// all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+// THE SOFTWARE.
+//
+
+using System;
+using System.Collections.Generic;
+
+namespace System.Web.Configuration {
+
+	internal class LruCache<TKey, TValue> {
+		Dictionary<TKey, LinkedListNode <TValue>> dict;
+		Dictionary<LinkedListNode<TValue>, TKey> revdict;
+		LinkedList<TValue> list;
+		int entryLimit;
+
+		public LruCache (int entryLimit)
+		{
+			this.entryLimit = entryLimit;
+			dict = new Dictionary<TKey, LinkedListNode<TValue>> ();
+			revdict = new Dictionary<LinkedListNode<TValue>, TKey> ();
+			list = new LinkedList<TValue> ();
+		}
+
+		//for debugging: public int Count { get { return dict.Count; } }
+
+		void Evict ()
+		{
+			var last = list.Last;
+			if (last == null)
+				return;
+
+			var key = revdict [last];
+
+			dict.Remove (key);
+			revdict.Remove (last);
+			list.RemoveLast ();
+			DisposeValue (last.Value);
+		}
+
+		public void Clear ()
+		{
+			foreach (var element in list) {
+				DisposeValue (element);
+			}
+
+			dict.Clear ();
+			revdict.Clear ();
+			list.Clear ();
+		}
+
+		void DisposeValue (TValue value) {
+			if (value is IDisposable) {
+				((IDisposable)value).Dispose ();
+			}
+		}
+
+		public bool TryGetValue (TKey key, out TValue value)
+		{
+			LinkedListNode<TValue> node;
+
+			if (dict.TryGetValue (key, out node)){
+				list.Remove (node);
+				list.AddFirst (node);
+
+				value = node.Value;
+				return true;
+			}
+			value = default (TValue);
+			return false;
+		}
+
+		public void Add (TKey key, TValue value) {
+			LinkedListNode<TValue> node;
+
+			if (dict.TryGetValue (key, out node)){
+
+				// If we already have a key, move it to the front
+				list.Remove (node);
+				list.AddFirst (node);
+
+				// Remove the old value
+				DisposeValue (node.Value);
+
+				node.Value = value;
+				return;
+			}
+
+			if (dict.Count >= entryLimit)
+				Evict ();
+
+			// Adding new node
+			node = new LinkedListNode<TValue> (value);
+			list.AddFirst (node);
+			dict [key] = node;
+			revdict [node] = key;
+		}
+
+		public override string ToString ()
+		{
+			return "LRUCache dict={0} revdict={1} list={2}";
+		}
+	}
+}

+ 2 - 1
mcs/class/System.Web/System.Web.Configuration_2.0/WebConfigurationManager.cs

@@ -76,7 +76,8 @@ namespace System.Web.Configuration {
 #if !TARGET_J2EE
 		static IInternalConfigConfigurationFactory configFactory;
 		static Hashtable configurations = Hashtable.Synchronized (new Hashtable ());
-		static Dictionary <int, object> sectionCache = new Dictionary <int, object> ();
+		const int MAX_CACHED_SECTIONS = 100;
+		static LruCache<int, object> sectionCache = new LruCache<int, object> (MAX_CACHED_SECTIONS);
 		static Hashtable configPaths = Hashtable.Synchronized (new Hashtable ());
 		static bool suppressAppReload;
 #else

+ 1 - 0
mcs/class/System.Web/System.Web.dll.sources

@@ -191,6 +191,7 @@ System.Web.Configuration_2.0/IConfigMapPath.cs
 System.Web.Configuration_2.0/IConfigMapPathFactory.cs
 System.Web.Configuration_2.0/IRemoteWebConfigurationHostServer.cs
 System.Web.Configuration_2.0/LowerCaseStringConverter.cs
+System.Web.Configuration_2.0/LruCache.cs
 System.Web.Configuration_2.0/MachineKeyRegistryStorage.cs
 System.Web.Configuration_2.0/MachineKeySection.cs
 System.Web.Configuration_2.0/MachineKeyValidation.cs