// // ConditionalWeakTableTest.cs // // Author: // Rodrigo Kumpera // // Copyright (C) 2010 Novell, Inc (http://www.novell.com) // // 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 NUnit.Framework; using System; using System.Reflection; using System.Runtime.CompilerServices; using System.Runtime.InteropServices; using System.Runtime.Serialization; using System.Security.Permissions; using System.Collections.Generic; using System.Threading; namespace MonoTests.System.Runtime.CompilerServices { [TestFixture] public class ConditionalWeakTableTest { public class Link { public object obj; public Link(object obj) { this.obj = obj; } } class Key { public int Foo; public override string ToString () { return "key-" + Foo; } } class Val { public int Foo; public override string ToString () { return "value-" + Foo; } } [Test] public void GetValue () { var cwt = new ConditionalWeakTable (); try { cwt.GetValue (null, k => null); Assert.Fail ("#0"); } catch (ArgumentNullException) {} try { cwt.GetValue (20, null); Assert.Fail ("#1"); } catch (ArgumentNullException) {} object key = "foo"; object val = cwt.GetValue (key, k => new Link (k)); Assert.IsTrue (val != null, "#2"); Assert.AreEqual (typeof (Link), val.GetType () , "#3"); Assert.AreEqual (val, cwt.GetValue (key, k => new object ()), "#4"); } [Test] public void GetOrCreateValue () { var cwt = new ConditionalWeakTable (); try { cwt.GetOrCreateValue (null); Assert.Fail ("#0"); } catch (ArgumentNullException) {} object key = "foo"; object val = cwt.GetOrCreateValue (key); Assert.IsTrue (val != null, "#2"); Assert.AreEqual (typeof (object), val.GetType () , "#3"); Assert.AreEqual (val, cwt.GetOrCreateValue (key), "#4"); var cwt2 = new ConditionalWeakTable (); try { cwt2.GetOrCreateValue (key); Assert.Fail ("#5"); } catch (MissingMethodException) {} } [Test] public void Remove () { var cwt = new ConditionalWeakTable (); object c = new Key (); cwt.Add (c, "x"); try { cwt.Remove (null); Assert.Fail ("#0"); } catch (ArgumentNullException) {} Assert.IsFalse (cwt.Remove ("x"), "#1"); Assert.IsTrue (cwt.Remove (c), "#2"); Assert.IsFalse (cwt.Remove (c), "#3"); } [Test] public void Add () { var cwt = new ConditionalWeakTable (); object c = new Key (); cwt.Add (c, new Link (c)); try { cwt.Add (c, new Link (c)); Assert.Fail ("#0"); } catch (ArgumentException) {} cwt.Add ("zzz", null);//ok try { cwt.Add (null, new Link (c)); Assert.Fail ("#1"); } catch (ArgumentNullException) {} } [Test] public void TryGetValue () { var cwt = new ConditionalWeakTable (); object res; object c = new Key (); cwt.Add (c, "foo"); try { cwt.TryGetValue (null, out res); Assert.Fail ("#0"); } catch (ArgumentNullException) {} Assert.IsFalse (cwt.TryGetValue ("foo", out res), "#1"); Assert.IsTrue (cwt.TryGetValue (c, out res), "#2"); Assert.AreEqual ("foo", res, "#3"); } static void FillStuff (ConditionalWeakTable cwt, out List keepAlive, out List keys) { keepAlive = new List (); keys = new List (); object a = new Key (); object b = new Key (); object c = new Key (); cwt.Add (a, new string ("str0".ToCharArray ())); cwt.Add (b, "str1"); cwt.Add (c, new Link (a)); keepAlive.Add (c); keys.Add (new WeakReference(a)); keys.Add (new WeakReference(b)); keys.Add (new WeakReference(c)); } [Test] public void Reachability () { if (GC.MaxGeneration == 0) /*Boehm doesn't handle ephemerons */ Assert.Ignore ("Not working on Boehm."); var cwt = new ConditionalWeakTable (); List keepAlive = null; List keys = null; Thread t = new Thread (delegate () { FillStuff (cwt, out keepAlive, out keys); }); t.Start (); t.Join (); GC.Collect (); Assert.IsTrue (keys [0].IsAlive, "r0"); Assert.IsFalse (keys [1].IsAlive, "r1"); Assert.IsTrue (keys [2].IsAlive, "r2"); object res; Assert.IsTrue (cwt.TryGetValue (keepAlive [0], out res), "ka0"); Assert.IsTrue (res is Link, "ka1"); Link link = res as Link; Assert.IsTrue (cwt.TryGetValue (link.obj, out res), "ka2"); Assert.AreEqual ("str0", res, "ka3"); } static List FillWithNetwork (ConditionalWeakTable cwt) { const int K = 500; object[] keys = new object [K]; for (int i = 0; i < K; ++i) keys[i] = new object (); Random rand = new Random (); /*produce a complex enough network of links*/ for (int i = 0; i < K; ++i) cwt.Add (keys [i], new Link (keys [rand.Next (K)])); var res = new List (); for (int i = 0; i < 10; ++i) res.Add (new WeakReference (keys [rand.Next (K)])); Array.Clear (keys, 0, keys.Length); return res; } [Test] public void InsertStress () { if (GC.MaxGeneration == 0) /*Boehm doesn't handle ephemerons */ Assert.Ignore ("Not working on Boehm."); var cwt = new ConditionalWeakTable (); var a = new object (); var b = new object (); cwt.Add (a, new object ()); cwt.Add (b, new object ()); List res = null; ThreadStart dele = () => { res = FillWithNetwork (cwt); }; var th = new Thread(dele); th.Start (); th.Join (); GC.Collect (); GC.Collect (); for (int i = 0; i < res.Count; ++i) Assert.IsFalse (res [i].IsAlive, "#r" + i); } static List FillWithNetwork2 (ConditionalWeakTable [] cwt) { if (cwt [0] == null) cwt[0] = new ConditionalWeakTable (); var res = FillWithNetwork (cwt[0]); return res; } static void ForcePromotion () { var o = new object[64000]; for (int i = 0; i < 64000; ++i) o[i] = new int[10]; } static List FillReachable (ConditionalWeakTable [] cwt) { var res = new List (); for (int i = 0; i < 10; ++i) { res.Add (new object ()); cwt[0].Add (res [i], i); } return res; } [Test] public void OldGenStress () { if (GC.MaxGeneration == 0) /*Boehm doesn't handle ephemerons */ Assert.Ignore ("Not working on Boehm."); var cwt = new ConditionalWeakTable [1]; List k = null; List res, res2; res = res2 = null; ThreadStart dele = () => { res = FillWithNetwork2 (cwt); ForcePromotion (); k = FillReachable (cwt); res2 = FillWithNetwork2 (cwt); }; var th = new Thread(dele); th.Start (); th.Join (); GC.Collect (); for (int i = 0; i < res.Count; ++i) Assert.IsFalse (res [i].IsAlive, "#r0-" + i); for (int i = 0; i < res2.Count; ++i) Assert.IsFalse (res2 [i].IsAlive, "#r1-" + i); for (int i = 0; i < k.Count; ++i) { object val; Assert.IsTrue (cwt[0].TryGetValue (k [i], out val), "k0-" + i); Assert.AreEqual (i, val, "k1-" + i); } } static List FillTable3 (ConditionalWeakTable cwt) { var handles = new List (); var a = (object)10; var b = (object)20; var k1 = (object)30; var k2 = (object)40; handles.Add (GCHandle.Alloc (a, GCHandleType.Pinned)); handles.Add (GCHandle.Alloc (b, GCHandleType.Pinned)); handles.Add (GCHandle.Alloc (k1, GCHandleType.Pinned)); handles.Add (GCHandle.Alloc (k2, GCHandleType.Pinned)); cwt.Add (a, k1); cwt.Add (b, k2); return handles; } static void MakeObjMovable (List handles) { for (int i = 0; i < handles.Count; ++i) { object o = handles[i].Target; handles[i].Free (); handles[i] = GCHandle.Alloc (o, GCHandleType.Normal); } } static void ForceMinor () { for (int i = 0; i < 64000; ++i) new object (); } public void PromotedCwtPointingToYoungStuff () { var cwt = new ConditionalWeakTable (); var handles = FillTable3 (cwt); GC.Collect (0); /*Be 100% sure it will be on the young gen*/ /*cwt array now will be on old gen*/ ForceMinor (); ForceMinor (); ForceMinor (); //Make them non pinned MakeObjMovable (handles); GC.Collect (0); //Force a minor GC - this should cause ForceMinor (); ForceMinor (); ForceMinor (); ForceMinor (); GC.Collect (0); object r1, r2; Assert.IsTrue (cwt.TryGetValue (handles[0].Target, out r1), "#1"); Assert.IsTrue (cwt.TryGetValue (handles[1].Target, out r2), "#2"); GC.Collect (); cwt.GetHashCode (); } static object _lock1 = new object (); static object _lock2 = new object (); static int reachable = 0; public class FinalizableLink { // The sole purpose of this object is to keep a reference to another object, so it is fine to not use it. #pragma warning disable 414 object obj; int id; #pragma warning restore 414 ConditionalWeakTable cwt; public FinalizableLink (int id, object obj, ConditionalWeakTable cwt) { this.id = id; this.obj = obj; this.cwt = cwt; } ~FinalizableLink () { lock (_lock1) { ; } object obj; bool res = cwt.TryGetValue (this, out obj); if (res) ++reachable; if (reachable == 20) lock (_lock2) { Monitor.Pulse (_lock2); } } } static void FillWithFinalizable (ConditionalWeakTable cwt) { object a = new object (); object b = new FinalizableLink (0, a, cwt); cwt.Add (a, "foo"); cwt.Add (b, "bar"); for (int i = 1; i < 20; ++i) { b = new FinalizableLink (i, b, cwt); cwt.Add (b, i); } } [Test] public void FinalizableObjectsThatRetainDeadKeys () { if (GC.MaxGeneration == 0) /*Boehm doesn't handle ephemerons */ Assert.Ignore ("Not working on Boehm."); lock (_lock1) { var cwt = new ConditionalWeakTable (); ThreadStart dele = () => { FillWithFinalizable (cwt); }; var th = new Thread(dele); th.Start (); th.Join (); GC.Collect (); GC.Collect (); Assert.AreEqual (0, reachable, "#1"); } GC.Collect (); GC.Collect (); lock (_lock2) { Monitor.Wait (_lock2, 1000); } Assert.AreEqual (20, reachable, "#1"); } [Test] public void OldGenKeysMakeNewGenObjectsReachable () { if (GC.MaxGeneration == 0) /*Boehm doesn't handle ephemerons */ Assert.Ignore ("Not working on Boehm."); ConditionalWeakTable table = new ConditionalWeakTable(); List keys = new List(); // // This list references all keys for the duration of the program, so none // should be collected ever. // for (int x = 0; x < 1000; x++) keys.Add (new Key () { Foo = x }); for (int i = 0; i < 1000; ++i) { // Insert all keys into the ConditionalWeakTable foreach (var key in keys) table.Add (key, new Val () { Foo = key.Foo }); // Look up all keys to verify that they are still there Val val; foreach (var key in keys) Assert.IsTrue (table.TryGetValue (key, out val), "#1-" + i + "-k-" + key); // Remove all keys from the ConditionalWeakTable foreach (var key in keys) Assert.IsTrue (table.Remove (key), "#2-" + i + "-k-" + key); } } } }