This is an automated email from the ASF dual-hosted git repository. nightowl888 pushed a commit to branch master in repository https://gitbox.apache.org/repos/asf/lucenenet.git
commit 7fd3de71d60a50059b6a8276bccb8d35fd6a1c83 Author: Shad Storhaug <[email protected]> AuthorDate: Thu Jan 23 20:55:36 2020 +0700 Factored out WeakIdentityMap in favor of ConditionalWeakTable (fixes LUCENENET-640, closes #236) Squashed commit of the following: commit 5303bdf7fbe196d219cfebef6f44bcc28a2692a0 Author: Shad Storhaug <[email protected]> Date: Tue Jan 21 16:22:44 2020 +0700 Lucene.Net.Util.WeakIdentityMap: Factored out and replaced with ConditionalWeakTable<TKey, TValue> (fixes LUCENENET-640) commit 7d38f1fe6375a19a19fdf6c2d9dd25ebb8797e48 Author: Shad Storhaug <[email protected]> Date: Tue Jan 21 04:49:07 2020 +0700 Lucene.Net.Util.AttributeSource: Removed lock for .NET Standard 2.1+, since we can use AddOrUpdate in the case a weak reference is no longer alive rather than using multiple calls to get the same effect. commit 387b9b27efbc530d6bfee984f5bc225cc768c558 Author: Shad Storhaug <[email protected]> Date: Tue Jan 21 00:19:54 2020 +0700 Lucene.Net.Util.AttributeSource: Factored out WeakIdentityMap in favor of ConditionalWeakTable (see LUCENENET-640) commit d9fdf766cb5c2a77dd9a693386c41c9d5fc30dc1 Author: Shad Storhaug <[email protected]> Date: Mon Jan 20 22:37:18 2020 +0700 Lucene.Net.Util.VirtualMethod: Factored out WeakIdenityMap in favor of ConditionalWeakTable (see LUCENENET-640) --- src/Lucene.Net.Tests/Util/TestWeakIdentityMap.cs | 715 ++++++++++---------- src/Lucene.Net/Store/ByteBufferIndexInput.cs | 37 +- src/Lucene.Net/Util/AttributeImpl.cs | 11 +- src/Lucene.Net/Util/AttributeSource.cs | 105 +-- src/Lucene.Net/Util/VirtualMethod.cs | 57 +- src/Lucene.Net/Util/WeakIdentityMap.cs | 801 ++++++++++++----------- 6 files changed, 909 insertions(+), 817 deletions(-) diff --git a/src/Lucene.Net.Tests/Util/TestWeakIdentityMap.cs b/src/Lucene.Net.Tests/Util/TestWeakIdentityMap.cs index 2c46d41..d98ec11 100644 --- a/src/Lucene.Net.Tests/Util/TestWeakIdentityMap.cs +++ b/src/Lucene.Net.Tests/Util/TestWeakIdentityMap.cs @@ -1,355 +1,360 @@ -using J2N.Threading; -using J2N.Threading.Atomic; -using Lucene.Net.Attributes; -using NUnit.Framework; -using System; -using System.Collections.Generic; -using System.Globalization; -using System.Threading; - -namespace Lucene.Net.Util -{ - /* - * Licensed to the Apache Software Foundation (ASF) under one or more - * contributor license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright ownership. - * The ASF licenses this file to You under the Apache License, Version 2.0 - * (the "License"); you may not use this file except in compliance with - * the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - - [TestFixture] - public class TestWeakIdentityMap : LuceneTestCase - { - - [Test, LongRunningTest] - public virtual void TestSimpleHashMap() - { - WeakIdentityMap<string, string> map = WeakIdentityMap<string, string>.NewHashMap(Random.NextBoolean()); - // we keep strong references to the keys, - // so WeakIdentityMap will not forget about them: - string key1 = "foo"; - string key2 = "test1 foo".Split(' ')[1]; - string key3 = "test2 foo".Split(' ')[1]; - - // LUCENENET NOTE: As per http://stackoverflow.com/a/543329/181087, - // the above hack is required in order to ensure the AreNotSame - // check will work. If you assign the same string to 3 different variables - // without doing some kind of manipulation from the original string, the - // AreNotSame test will fail because the references will be the same. - - Assert.AreNotSame(key1, key2); - Assert.AreEqual(key1, key2); - Assert.AreNotSame(key1, key3); - Assert.AreEqual(key1, key3); - Assert.AreNotSame(key2, key3); - Assert.AreEqual(key2, key3); - - // try null key & check its iterator also return null: - map.Put(null, "null"); - { - using (IEnumerator<string> iter = map.Keys.GetEnumerator()) - { - Assert.IsTrue(iter.MoveNext()); - Assert.IsNull(iter.Current); - Assert.IsFalse(iter.MoveNext()); - Assert.IsFalse(iter.MoveNext()); - } - } - // 2 more keys: - map.Put(key1, "bar1"); - map.Put(key2, "bar2"); - - Assert.AreEqual(3, map.Count); - - Assert.AreEqual("bar1", map.Get(key1)); - Assert.AreEqual("bar2", map.Get(key2)); - Assert.AreEqual(null, map.Get(key3)); - Assert.AreEqual("null", map.Get(null)); - - Assert.IsTrue(map.ContainsKey(key1)); - Assert.IsTrue(map.ContainsKey(key2)); - Assert.IsFalse(map.ContainsKey(key3)); - Assert.IsTrue(map.ContainsKey(null)); - - // repeat and check that we have no double entries - map.Put(key1, "bar1"); - map.Put(key2, "bar2"); - map.Put(null, "null"); - - Assert.AreEqual(3, map.Count); - - Assert.AreEqual("bar1", map.Get(key1)); - Assert.AreEqual("bar2", map.Get(key2)); - Assert.AreEqual(null, map.Get(key3)); - Assert.AreEqual("null", map.Get(null)); - - Assert.IsTrue(map.ContainsKey(key1)); - Assert.IsTrue(map.ContainsKey(key2)); - Assert.IsFalse(map.ContainsKey(key3)); - Assert.IsTrue(map.ContainsKey(null)); - - map.Remove(null); - Assert.AreEqual(2, map.Count); - map.Remove(key1); - Assert.AreEqual(1, map.Count); - map.Put(key1, "bar1"); - map.Put(key2, "bar2"); - map.Put(key3, "bar3"); - Assert.AreEqual(3, map.Count); - - int c = 0, keysAssigned = 0; - foreach (object k in map.Keys) - { - //Assert.IsTrue(iter.hasNext()); // try again, should return same result! - // LUCENENET NOTE: Need object.ReferenceEquals here because the == operator does more than check reference equality - Assert.IsTrue(object.ReferenceEquals(k, key1) || object.ReferenceEquals(k, key2) | object.ReferenceEquals(k, key3)); - keysAssigned += object.ReferenceEquals(k, key1) ? 1 : (object.ReferenceEquals(k, key2) ? 2 : 4); - c++; - } - Assert.AreEqual(3, c); - Assert.AreEqual(1 + 2 + 4, keysAssigned, "all keys must have been seen"); - - c = 0; - for (IEnumerator<string> iter = map.Values.GetEnumerator(); iter.MoveNext();) - { - string v = iter.Current; - Assert.IsTrue(v.StartsWith("bar", StringComparison.Ordinal)); - c++; - } - Assert.AreEqual(3, c); - - // clear strong refs - key1 = key2 = key3 = null; - - // check that GC does not cause problems in reap() method, wait 1 second and let GC work: - int size = map.Count; - for (int i = 0; size > 0 && i < 10; i++) - { -#if !NETSTANDARD1_6 - try - { -#endif - GC.Collect(); - int newSize = map.Count; - Assert.IsTrue(size >= newSize, "previousSize(" + size + ")>=newSize(" + newSize + ")"); - size = newSize; - Thread.Sleep(TimeSpan.FromSeconds(1)); - c = 0; - foreach (object k in map.Keys) - { - Assert.IsNotNull(k); - c++; - } - newSize = map.Count; - Assert.IsTrue(size >= c, "previousSize(" + size + ")>=iteratorSize(" + c + ")"); - Assert.IsTrue(c >= newSize, "iteratorSize(" + c + ")>=newSize(" + newSize + ")"); - size = newSize; -#if !NETSTANDARD1_6 - } -#pragma warning disable 168 - catch (ThreadInterruptedException ie) -#pragma warning restore 168 - { - } -#endif - } - - map.Clear(); - Assert.AreEqual(0, map.Count); - Assert.IsTrue(map.IsEmpty); - - using (IEnumerator<string> it = map.Keys.GetEnumerator()) - Assert.IsFalse(it.MoveNext()); - /*try - { - it.Next(); - Assert.Fail("Should throw NoSuchElementException"); - } - catch (NoSuchElementException nse) - { - }*/ - - // LUCENENET NOTE: As per http://stackoverflow.com/a/543329/181087, - // the following hack is required in order to ensure the string references - // are different. If you assign the same string to 2 different variables - // without doing some kind of manipulation from the original string, the - // references will be the same. - - key1 = "test3 foo".Split(' ')[1]; - key2 = "test4 foo".Split(' ')[1]; - map.Put(key1, "bar1"); - map.Put(key2, "bar2"); - Assert.AreEqual(2, map.Count); - - map.Clear(); - Assert.AreEqual(0, map.Count); - Assert.IsTrue(map.IsEmpty); - } - - [Test, LongRunningTest] - public virtual void TestConcurrentHashMap() - { - // don't make threadCount and keyCount random, otherwise easily OOMs or fails otherwise: - const int threadCount = 8, keyCount = 1024; - - RunnableAnonymousInnerClassHelper[] workers = new RunnableAnonymousInnerClassHelper[threadCount]; - WeakIdentityMap<object, int?> map = WeakIdentityMap<object, int?>.NewConcurrentHashMap(Random.NextBoolean()); - // we keep strong references to the keys, - // so WeakIdentityMap will not forget about them: - AtomicReferenceArray<object> keys = new AtomicReferenceArray<object>(keyCount); - for (int j = 0; j < keyCount; j++) - { - keys[j] = new object(); - } - - try - { - for (int t = 0; t < threadCount; t++) - { - Random rnd = new Random(Random.Next()); - var worker = new RunnableAnonymousInnerClassHelper(this, keyCount, map, keys, rnd); - workers[t] = worker; - worker.Start(); - } - } - finally - { - foreach (var w in workers) - { - w.Join(); - } - } - - // LUCENENET: Since assertions were done on the other threads, we need to check the - // results here. - for (int i = 0; i < workers.Length; i++) - { - assertTrue(string.Format(CultureInfo.InvariantCulture, - "worker thread {0} of {1} failed \n" + workers[i].Error, i, workers.Length), - workers[i].Error == null); - } - - - // clear strong refs - for (int j = 0; j < keyCount; j++) - { - keys[j] = null; - } - - // check that GC does not cause problems in reap() method: - int size = map.Count; - for (int i = 0; size > 0 && i < 10; i++) - { -#if !NETSTANDARD1_6 - try - { -#endif - GC.Collect(); - int newSize = map.Count; - Assert.IsTrue(size >= newSize, "previousSize(" + size + ")>=newSize(" + newSize + ")"); - size = newSize; - Thread.Sleep(new TimeSpan(100L)); - int c = 0; - foreach (object k in map.Keys) - { - Assert.IsNotNull(k); - c++; - } - newSize = map.Count; - Assert.IsTrue(size >= c, "previousSize(" + size + ")>=iteratorSize(" + c + ")"); - Assert.IsTrue(c >= newSize, "iteratorSize(" + c + ")>=newSize(" + newSize + ")"); - size = newSize; -#if !NETSTANDARD1_6 - } -#pragma warning disable 168 - catch (ThreadInterruptedException ie) -#pragma warning restore 168 - { - } -#endif - } - } - - private class RunnableAnonymousInnerClassHelper : ThreadJob - { - private readonly TestWeakIdentityMap outerInstance; - - private readonly int keyCount; - private readonly WeakIdentityMap<object, int?> map; - private AtomicReferenceArray<object> keys; - private readonly Random rnd; - private volatile Exception error; - - public RunnableAnonymousInnerClassHelper(TestWeakIdentityMap outerInstance, int keyCount, WeakIdentityMap<object, int?> map, AtomicReferenceArray<object> keys, Random rnd) - { - this.outerInstance = outerInstance; - this.keyCount = keyCount; - this.map = map; - this.keys = keys; - this.rnd = rnd; - } - - public Exception Error - { - get { return error; } - } - - - public override void Run() - { - int count = AtLeast(rnd, 10000); - try - { - for (int i = 0; i < count; i++) - { - int j = rnd.Next(keyCount); - switch (rnd.Next(5)) - { - case 0: - map.Put(keys[j], Convert.ToInt32(j)); - break; - case 1: - int? v = map.Get(keys[j]); - if (v != null) - { - Assert.AreEqual(j, (int)v); - } - break; - case 2: - map.Remove(keys[j]); - break; - case 3: - // renew key, the old one will be GCed at some time: - keys[j] = new object(); - break; - case 4: - // check iterator still working - foreach (object k in map.Keys) - { - Assert.IsNotNull(k); - } - break; - default: - Assert.Fail("Should not get here."); - break; - } - } - } - catch (Exception e) - { - e.printStackTrace(); - this.error = e; - } - } - } - } -} \ No newline at end of file +// LUCENENET specific - factored out this WeakIdentityMap<TKey, TValue> and replaced it with ConditionalWeakTable<TKey, TValue>. +// ConditionalWeakTable<TKey, TValue> is thread-safe and internally uses RuntimeHelpers.GetHashCode() +// to lookup the key, so it can be used as a direct replacement for WeakIdentityMap<TKey, TValue> +// in most cases. + +//using J2N.Threading; +//using J2N.Threading.Atomic; +//using Lucene.Net.Attributes; +//using NUnit.Framework; +//using System; +//using System.Collections.Generic; +//using System.Globalization; +//using System.Threading; + +//namespace Lucene.Net.Util +//{ +// /* +// * Licensed to the Apache Software Foundation (ASF) under one or more +// * contributor license agreements. See the NOTICE file distributed with +// * this work for additional information regarding copyright ownership. +// * The ASF licenses this file to You under the Apache License, Version 2.0 +// * (the "License"); you may not use this file except in compliance with +// * the License. You may obtain a copy of the License at +// * +// * http://www.apache.org/licenses/LICENSE-2.0 +// * +// * Unless required by applicable law or agreed to in writing, software +// * distributed under the License is distributed on an "AS IS" BASIS, +// * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// * See the License for the specific language governing permissions and +// * limitations under the License. +// */ + +// [TestFixture] +// public class TestWeakIdentityMap : LuceneTestCase +// { + +// [Test, LongRunningTest] +// public virtual void TestSimpleHashMap() +// { +// WeakIdentityMap<string, string> map = WeakIdentityMap<string, string>.NewHashMap(Random.NextBoolean()); +// // we keep strong references to the keys, +// // so WeakIdentityMap will not forget about them: +// string key1 = "foo"; +// string key2 = "test1 foo".Split(' ')[1]; +// string key3 = "test2 foo".Split(' ')[1]; + +// // LUCENENET NOTE: As per http://stackoverflow.com/a/543329/181087, +// // the above hack is required in order to ensure the AreNotSame +// // check will work. If you assign the same string to 3 different variables +// // without doing some kind of manipulation from the original string, the +// // AreNotSame test will fail because the references will be the same. + +// Assert.AreNotSame(key1, key2); +// Assert.AreEqual(key1, key2); +// Assert.AreNotSame(key1, key3); +// Assert.AreEqual(key1, key3); +// Assert.AreNotSame(key2, key3); +// Assert.AreEqual(key2, key3); + +// // try null key & check its iterator also return null: +// map.Put(null, "null"); +// { +// using (IEnumerator<string> iter = map.Keys.GetEnumerator()) +// { +// Assert.IsTrue(iter.MoveNext()); +// Assert.IsNull(iter.Current); +// Assert.IsFalse(iter.MoveNext()); +// Assert.IsFalse(iter.MoveNext()); +// } +// } +// // 2 more keys: +// map.Put(key1, "bar1"); +// map.Put(key2, "bar2"); + +// Assert.AreEqual(3, map.Count); + +// Assert.AreEqual("bar1", map.Get(key1)); +// Assert.AreEqual("bar2", map.Get(key2)); +// Assert.AreEqual(null, map.Get(key3)); +// Assert.AreEqual("null", map.Get(null)); + +// Assert.IsTrue(map.ContainsKey(key1)); +// Assert.IsTrue(map.ContainsKey(key2)); +// Assert.IsFalse(map.ContainsKey(key3)); +// Assert.IsTrue(map.ContainsKey(null)); + +// // repeat and check that we have no double entries +// map.Put(key1, "bar1"); +// map.Put(key2, "bar2"); +// map.Put(null, "null"); + +// Assert.AreEqual(3, map.Count); + +// Assert.AreEqual("bar1", map.Get(key1)); +// Assert.AreEqual("bar2", map.Get(key2)); +// Assert.AreEqual(null, map.Get(key3)); +// Assert.AreEqual("null", map.Get(null)); + +// Assert.IsTrue(map.ContainsKey(key1)); +// Assert.IsTrue(map.ContainsKey(key2)); +// Assert.IsFalse(map.ContainsKey(key3)); +// Assert.IsTrue(map.ContainsKey(null)); + +// map.Remove(null); +// Assert.AreEqual(2, map.Count); +// map.Remove(key1); +// Assert.AreEqual(1, map.Count); +// map.Put(key1, "bar1"); +// map.Put(key2, "bar2"); +// map.Put(key3, "bar3"); +// Assert.AreEqual(3, map.Count); + +// int c = 0, keysAssigned = 0; +// foreach (object k in map.Keys) +// { +// //Assert.IsTrue(iter.hasNext()); // try again, should return same result! +// // LUCENENET NOTE: Need object.ReferenceEquals here because the == operator does more than check reference equality +// Assert.IsTrue(object.ReferenceEquals(k, key1) || object.ReferenceEquals(k, key2) | object.ReferenceEquals(k, key3)); +// keysAssigned += object.ReferenceEquals(k, key1) ? 1 : (object.ReferenceEquals(k, key2) ? 2 : 4); +// c++; +// } +// Assert.AreEqual(3, c); +// Assert.AreEqual(1 + 2 + 4, keysAssigned, "all keys must have been seen"); + +// c = 0; +// for (IEnumerator<string> iter = map.Values.GetEnumerator(); iter.MoveNext();) +// { +// string v = iter.Current; +// Assert.IsTrue(v.StartsWith("bar", StringComparison.Ordinal)); +// c++; +// } +// Assert.AreEqual(3, c); + +// // clear strong refs +// key1 = key2 = key3 = null; + +// // check that GC does not cause problems in reap() method, wait 1 second and let GC work: +// int size = map.Count; +// for (int i = 0; size > 0 && i < 10; i++) +// { +//#if !NETSTANDARD1_6 +// try +// { +//#endif +// GC.Collect(); +// int newSize = map.Count; +// Assert.IsTrue(size >= newSize, "previousSize(" + size + ")>=newSize(" + newSize + ")"); +// size = newSize; +// Thread.Sleep(TimeSpan.FromSeconds(1)); +// c = 0; +// foreach (object k in map.Keys) +// { +// Assert.IsNotNull(k); +// c++; +// } +// newSize = map.Count; +// Assert.IsTrue(size >= c, "previousSize(" + size + ")>=iteratorSize(" + c + ")"); +// Assert.IsTrue(c >= newSize, "iteratorSize(" + c + ")>=newSize(" + newSize + ")"); +// size = newSize; +//#if !NETSTANDARD1_6 +// } +//#pragma warning disable 168 +// catch (ThreadInterruptedException ie) +//#pragma warning restore 168 +// { +// } +//#endif +// } + +// map.Clear(); +// Assert.AreEqual(0, map.Count); +// Assert.IsTrue(map.IsEmpty); + +// using (IEnumerator<string> it = map.Keys.GetEnumerator()) +// Assert.IsFalse(it.MoveNext()); +// /*try +// { +// it.Next(); +// Assert.Fail("Should throw NoSuchElementException"); +// } +// catch (NoSuchElementException nse) +// { +// }*/ + +// // LUCENENET NOTE: As per http://stackoverflow.com/a/543329/181087, +// // the following hack is required in order to ensure the string references +// // are different. If you assign the same string to 2 different variables +// // without doing some kind of manipulation from the original string, the +// // references will be the same. + +// key1 = "test3 foo".Split(' ')[1]; +// key2 = "test4 foo".Split(' ')[1]; +// map.Put(key1, "bar1"); +// map.Put(key2, "bar2"); +// Assert.AreEqual(2, map.Count); + +// map.Clear(); +// Assert.AreEqual(0, map.Count); +// Assert.IsTrue(map.IsEmpty); +// } + +// [Test, LongRunningTest] +// public virtual void TestConcurrentHashMap() +// { +// // don't make threadCount and keyCount random, otherwise easily OOMs or fails otherwise: +// const int threadCount = 8, keyCount = 1024; + +// RunnableAnonymousInnerClassHelper[] workers = new RunnableAnonymousInnerClassHelper[threadCount]; +// WeakIdentityMap<object, int?> map = WeakIdentityMap<object, int?>.NewConcurrentHashMap(Random.NextBoolean()); +// // we keep strong references to the keys, +// // so WeakIdentityMap will not forget about them: +// AtomicReferenceArray<object> keys = new AtomicReferenceArray<object>(keyCount); +// for (int j = 0; j < keyCount; j++) +// { +// keys[j] = new object(); +// } + +// try +// { +// for (int t = 0; t < threadCount; t++) +// { +// Random rnd = new Random(Random.Next()); +// var worker = new RunnableAnonymousInnerClassHelper(this, keyCount, map, keys, rnd); +// workers[t] = worker; +// worker.Start(); +// } +// } +// finally +// { +// foreach (var w in workers) +// { +// w.Join(); +// } +// } + +// // LUCENENET: Since assertions were done on the other threads, we need to check the +// // results here. +// for (int i = 0; i < workers.Length; i++) +// { +// assertTrue(string.Format(CultureInfo.InvariantCulture, +// "worker thread {0} of {1} failed \n" + workers[i].Error, i, workers.Length), +// workers[i].Error == null); +// } + + +// // clear strong refs +// for (int j = 0; j < keyCount; j++) +// { +// keys[j] = null; +// } + +// // check that GC does not cause problems in reap() method: +// int size = map.Count; +// for (int i = 0; size > 0 && i < 10; i++) +// { +//#if !NETSTANDARD1_6 +// try +// { +//#endif +// GC.Collect(); +// int newSize = map.Count; +// Assert.IsTrue(size >= newSize, "previousSize(" + size + ")>=newSize(" + newSize + ")"); +// size = newSize; +// Thread.Sleep(new TimeSpan(100L)); +// int c = 0; +// foreach (object k in map.Keys) +// { +// Assert.IsNotNull(k); +// c++; +// } +// newSize = map.Count; +// Assert.IsTrue(size >= c, "previousSize(" + size + ")>=iteratorSize(" + c + ")"); +// Assert.IsTrue(c >= newSize, "iteratorSize(" + c + ")>=newSize(" + newSize + ")"); +// size = newSize; +//#if !NETSTANDARD1_6 +// } +//#pragma warning disable 168 +// catch (ThreadInterruptedException ie) +//#pragma warning restore 168 +// { +// } +//#endif +// } +// } + +// private class RunnableAnonymousInnerClassHelper : ThreadJob +// { +// private readonly TestWeakIdentityMap outerInstance; + +// private readonly int keyCount; +// private readonly WeakIdentityMap<object, int?> map; +// private AtomicReferenceArray<object> keys; +// private readonly Random rnd; +// private volatile Exception error; + +// public RunnableAnonymousInnerClassHelper(TestWeakIdentityMap outerInstance, int keyCount, WeakIdentityMap<object, int?> map, AtomicReferenceArray<object> keys, Random rnd) +// { +// this.outerInstance = outerInstance; +// this.keyCount = keyCount; +// this.map = map; +// this.keys = keys; +// this.rnd = rnd; +// } + +// public Exception Error +// { +// get { return error; } +// } + + +// public override void Run() +// { +// int count = AtLeast(rnd, 10000); +// try +// { +// for (int i = 0; i < count; i++) +// { +// int j = rnd.Next(keyCount); +// switch (rnd.Next(5)) +// { +// case 0: +// map.Put(keys[j], Convert.ToInt32(j)); +// break; +// case 1: +// int? v = map.Get(keys[j]); +// if (v != null) +// { +// Assert.AreEqual(j, (int)v); +// } +// break; +// case 2: +// map.Remove(keys[j]); +// break; +// case 3: +// // renew key, the old one will be GCed at some time: +// keys[j] = new object(); +// break; +// case 4: +// // check iterator still working +// foreach (object k in map.Keys) +// { +// Assert.IsNotNull(k); +// } +// break; +// default: +// Assert.Fail("Should not get here."); +// break; +// } +// } +// } +// catch (Exception e) +// { +// e.printStackTrace(); +// this.error = e; +// } +// } +// } +// } +//} \ No newline at end of file diff --git a/src/Lucene.Net/Store/ByteBufferIndexInput.cs b/src/Lucene.Net/Store/ByteBufferIndexInput.cs index 480359a..341d370 100644 --- a/src/Lucene.Net/Store/ByteBufferIndexInput.cs +++ b/src/Lucene.Net/Store/ByteBufferIndexInput.cs @@ -4,6 +4,7 @@ using System; using System.Diagnostics; using System.IO; using System.Reflection; +using System.Runtime.CompilerServices; namespace Lucene.Net.Store { @@ -50,17 +51,29 @@ namespace Lucene.Net.Store private ByteBuffer curBuf; // redundant for speed: buffers[curBufIndex] private bool isClone = false; - private readonly WeakIdentityMap<ByteBufferIndexInput, BoolRefWrapper> clones; + // LUCENENET: Using ConditionalWeakTable rather than WeakIdenityMap. ConditionalWeakTable + // uses RuntimeHelpers.GetHashCode() to find the item, so technically, it IS an identity collection. + private ConditionalWeakTable<ByteBufferIndexInput, BoolRefWrapper> clones; private class BoolRefWrapper { + private bool value; + // .NET port: this is needed as bool is not a reference type public BoolRefWrapper(bool value) { - this.Value = value; + this.value = value; + } + + public static implicit operator bool(BoolRefWrapper value) + { + return value.value; } - public bool Value { get; private set; } + public static implicit operator BoolRefWrapper(bool value) + { + return new BoolRefWrapper(value); + } } internal ByteBufferIndexInput(string resourceDescription, ByteBuffer[] buffers, long length, int chunkSizePower, bool trackClones) @@ -70,7 +83,9 @@ namespace Lucene.Net.Store this.length = length; this.chunkSizePower = chunkSizePower; this.chunkSizeMask = (1L << chunkSizePower) - 1L; - this.clones = trackClones ? WeakIdentityMap<ByteBufferIndexInput, BoolRefWrapper>.NewConcurrentHashMap() : null; + // LUCENENET: Using ConditionalWeakTable rather than WeakIdenityMap. ConditionalWeakTable + // uses RuntimeHelpers.GetHashCode() to find the item, so technically, it IS an identity collection. + this.clones = trackClones ? new ConditionalWeakTable<ByteBufferIndexInput, BoolRefWrapper>() : null; Debug.Assert(chunkSizePower >= 0 && chunkSizePower <= 30); Debug.Assert(((long)((ulong)length >> chunkSizePower)) < int.MaxValue); @@ -298,7 +313,7 @@ namespace Lucene.Net.Store // register the new clone in our clone list to clean it up on closing: if (clones != null) { - this.clones.Put(clone, new BoolRefWrapper(true)); + this.clones.Add(clone, true); } return clone; @@ -374,16 +389,22 @@ namespace Lucene.Net.Store // for extra safety unset also all clones' buffers: if (clones != null) { - foreach (ByteBufferIndexInput clone in clones.Keys) + // LUCENENET: Since .NET will GC types that go out of scope automatically, + // this isn't strictly necessary. However, we are doing it anyway when + // the enumerator is available (.NET Standard 2.1+) +#if FEATURE_CONDITIONALWEAKTABLE_ENUMERATOR + foreach (var pair in clones) { - clone.UnsetBuffers(); + pair.Key.UnsetBuffers(); } this.clones.Clear(); +#endif + this.clones = null; // LUCENENET: de-reference the table so it can be GC'd } foreach (ByteBuffer b in bufs) { - FreeBuffer(b); + FreeBuffer(b); // LUCENENET: This calls Dispose() when necessary } } finally diff --git a/src/Lucene.Net/Util/AttributeImpl.cs b/src/Lucene.Net/Util/AttributeImpl.cs index d52147a..8df9683 100644 --- a/src/Lucene.Net/Util/AttributeImpl.cs +++ b/src/Lucene.Net/Util/AttributeImpl.cs @@ -119,21 +119,14 @@ namespace Lucene.Net.Util public virtual void ReflectWith(IAttributeReflector reflector) // LUCENENET NOTE: This method was abstract in Lucene { Type clazz = this.GetType(); - LinkedList<WeakReference> interfaces = AttributeSource.GetAttributeInterfaces(clazz); + LinkedList<WeakReference<Type>> interfaces = AttributeSource.GetAttributeInterfaces(clazz); if (interfaces.Count != 1) { throw new NotSupportedException(clazz.Name + " implements more than one Attribute interface, the default ReflectWith() implementation cannot handle this."); } - Type interf = (System.Type)interfaces.First().Target; - - /*object target = interfaces.First.Value; - - if (target == null) - return; - - Type interf = target.GetType();// as Type;*/ + interfaces.First.Value.TryGetTarget(out Type interf); //problem: the interfaces list has weak references that could have expired already diff --git a/src/Lucene.Net/Util/AttributeSource.cs b/src/Lucene.Net/Util/AttributeSource.cs index 7b46b88..3c9b80b 100644 --- a/src/Lucene.Net/Util/AttributeSource.cs +++ b/src/Lucene.Net/Util/AttributeSource.cs @@ -55,8 +55,10 @@ namespace Lucene.Net.Util private sealed class DefaultAttributeFactory : AttributeFactory { - internal static readonly WeakIdentityMap<Type, WeakReference> attClassImplMap = - WeakIdentityMap<Type, WeakReference>.NewConcurrentHashMap(false); + // LUCENENET: Using ConditionalWeakTable instead of WeakIdentityMap. A Type IS an + // identity for a class, so there is no need for an identity wrapper for it. + private static readonly ConditionalWeakTable<Type, WeakReference<Type>> attClassImplMap = + new ConditionalWeakTable<Type, WeakReference<Type>>(); internal DefaultAttributeFactory() { @@ -77,23 +79,51 @@ namespace Lucene.Net.Util internal static Type GetClassForInterface<T>() where T : IAttribute { var attClass = typeof(T); - WeakReference @ref = attClassImplMap.Get(attClass); - Type clazz = (@ref == null) ? null : (Type)@ref.Target; - if (clazz == null) + Type clazz; + +#if !FEATURE_CONDITIONALWEAKTABLE_ADDORUPDATE + // LUCENENET: If the weakreference is dead, we need to explicitly remove and re-add its key. + // We synchronize on attClassImplMap only to make the operation atomic. This does not actually + // utilize the same lock as attClassImplMap does internally, but since this is the only place + // it is used, it is fine here. + + // In .NET Standard 2.1, we can use AddOrUpdate, so don't need the lock. + lock (attClassImplMap) +#endif { - // we have the slight chance that another thread may do the same, but who cares? - try + var @ref = attClassImplMap.GetValue(attClass, (key) => { - string name = attClass.FullName.Replace(attClass.Name, attClass.Name.Substring(1)) + ", " + attClass.GetTypeInfo().Assembly.FullName; - attClassImplMap.Put(attClass, new WeakReference(clazz = Type.GetType(name, true))); - } - catch (Exception e) + return CreateAttributeWeakReference(key, out clazz); + }); + + if ([email protected](out clazz)) { - throw new System.ArgumentException("Could not find implementing class for " + attClass.Name, e); +#if FEATURE_CONDITIONALWEAKTABLE_ADDORUPDATE + // There is a small chance that multiple threads will get through here, but it doesn't matter + attClassImplMap.AddOrUpdate(attClass, CreateAttributeWeakReference(attClass, out clazz)); +#else + attClassImplMap.Remove(attClass); + attClassImplMap.Add(attClass, CreateAttributeWeakReference(attClass, out clazz)); +#endif } } + return clazz; } + + // LUCENENET specific - factored this out so we can reuse + private static WeakReference<Type> CreateAttributeWeakReference(Type attClass, out Type clazz) + { + try + { + string name = attClass.FullName.Replace(attClass.Name, attClass.Name.Substring(1)) + ", " + attClass.GetTypeInfo().Assembly.FullName; + return new WeakReference<Type>(clazz = Type.GetType(name, true)); + } + catch (Exception e) + { + throw new ArgumentException("Could not find implementing class for " + attClass.Name, e); + } + } } } @@ -147,7 +177,7 @@ namespace Lucene.Net.Util { if (input == null) { - throw new System.ArgumentException("input AttributeSource must not be null"); + throw new ArgumentException("input AttributeSource must not be null"); } this.attributes = input.attributes; this.attributeImpls = input.attributeImpls; @@ -258,36 +288,33 @@ namespace Lucene.Net.Util /// <summary> /// A cache that stores all interfaces for known implementation classes for performance (slow reflection) </summary> - private static readonly WeakIdentityMap<Type, LinkedList<WeakReference>> knownImplClasses = - WeakIdentityMap<Type, LinkedList<WeakReference>>.NewConcurrentHashMap(false); + // LUCENENET: Using ConditionalWeakTable instead of WeakIdentityMap. A Type IS an + // identity for a class, so there is no need for an identity wrapper for it. + private static readonly ConditionalWeakTable<Type, LinkedList<WeakReference<Type>>> knownImplClasses = + new ConditionalWeakTable<Type, LinkedList<WeakReference<Type>>>(); - internal static LinkedList<WeakReference> GetAttributeInterfaces(Type clazz) + internal static LinkedList<WeakReference<Type>> GetAttributeInterfaces(Type clazz) { - LinkedList<WeakReference> foundInterfaces = knownImplClasses.Get(clazz); - lock (knownImplClasses) + return knownImplClasses.GetValue(clazz, (key) => { - if (foundInterfaces == null) + LinkedList<WeakReference<Type>> foundInterfaces = new LinkedList<WeakReference<Type>>(); + // find all interfaces that this attribute instance implements + // and that extend the Attribute interface + Type actClazz = clazz; + do { - // we have the slight chance that another thread may do the same, but who cares? - foundInterfaces = new LinkedList<WeakReference>(); - // find all interfaces that this attribute instance implements - // and that extend the Attribute interface - Type actClazz = clazz; - do + foreach (Type curInterface in actClazz.GetInterfaces()) { - foreach (Type curInterface in actClazz.GetInterfaces()) + if (curInterface != typeof(IAttribute) && typeof(IAttribute).IsAssignableFrom(curInterface)) { - if (curInterface != typeof(IAttribute) && (typeof(IAttribute)).IsAssignableFrom(curInterface)) - { - foundInterfaces.AddLast(new WeakReference(curInterface)); - } + foundInterfaces.AddLast(new WeakReference<Type>(curInterface)); } - actClazz = actClazz.GetTypeInfo().BaseType; - } while (actClazz != null); - knownImplClasses.Put(clazz, foundInterfaces); - } - } - return foundInterfaces; + } + actClazz = actClazz.GetTypeInfo().BaseType; + } while (actClazz != null); + + return foundInterfaces; + }); } /// <summary> @@ -307,12 +334,12 @@ namespace Lucene.Net.Util return; } - LinkedList<WeakReference> foundInterfaces = GetAttributeInterfaces(clazz); + LinkedList<WeakReference<Type>> foundInterfaces = GetAttributeInterfaces(clazz); // add all interfaces of this Attribute to the maps - foreach (WeakReference curInterfaceRef in foundInterfaces) + foreach (var curInterfaceRef in foundInterfaces) { - Type curInterface = (Type)curInterfaceRef.Target; + curInterfaceRef.TryGetTarget(out Type curInterface); Debug.Assert(curInterface != null, "We have a strong reference on the class holding the interfaces, so they should never get evicted"); // Attribute is a superclass of this interface if (!attributes.ContainsKey(curInterface)) diff --git a/src/Lucene.Net/Util/VirtualMethod.cs b/src/Lucene.Net/Util/VirtualMethod.cs index 7113a78..dfc0f8d 100644 --- a/src/Lucene.Net/Util/VirtualMethod.cs +++ b/src/Lucene.Net/Util/VirtualMethod.cs @@ -6,6 +6,7 @@ using System.Globalization; using System.Linq; #endif using System.Reflection; +using System.Runtime.CompilerServices; namespace Lucene.Net.Util { @@ -69,7 +70,52 @@ namespace Lucene.Net.Util private readonly Type baseClass; private readonly string method; private readonly Type[] parameters; - private readonly WeakIdentityMap<Type, int> cache = WeakIdentityMap<Type, int>.NewConcurrentHashMap(false); + // LUCENENET: Replaced IdentityHashMap with ConditionalWeakTable. A Type IS an identity, so there is + // no need for the extra IdentityWeakReference. + private readonly ConditionalWeakTable<Type, Int32Ref> cache = new ConditionalWeakTable<Type, Int32Ref>(); + + // LUCENENET specific wrapper needed because ConditionalWeakTable requires a reference type. + private class Int32Ref : IEquatable<Int32Ref> + { + private int value; + + public Int32Ref(int value) + { + this.value = value; + } + + public bool Equals(Int32Ref other) + { + if (other == null) + return false; + return value.Equals(other); + } + + public override bool Equals(object obj) + { + if (obj is Int32Ref other) + return Equals(other); + if (obj is int otherInt) + return value.Equals(otherInt); + return false; + } + + public override int GetHashCode() + { + return value.GetHashCode(); + } + + public static implicit operator int(Int32Ref value) + { + return value.value; + } + + public static implicit operator Int32Ref(int value) + { + return new Int32Ref(value); + } + } + /// <summary> /// Creates a new instance for the given <paramref name="baseClass"/> and method declaration. </summary> @@ -105,13 +151,8 @@ namespace Lucene.Net.Util /// <returns> 0 if and only if not overridden, else the distance to the base class. </returns> public int GetImplementationDistance(Type subclazz) { - int distance = cache.Get(subclazz); - if (distance == default(int)) - { - // we have the slight chance that another thread may do the same, but who cares? - cache.Put(subclazz, distance = Convert.ToInt32(ReflectImplementationDistance(subclazz), CultureInfo.InvariantCulture)); - } - return (int)distance; + // LUCENENET: Replaced WeakIdentityMap with ConditionalWeakTable - This operation is simplified over Lucene. + return cache.GetValue(subclazz, (key) => Convert.ToInt32(ReflectImplementationDistance(key), CultureInfo.InvariantCulture)); } /// <summary> diff --git a/src/Lucene.Net/Util/WeakIdentityMap.cs b/src/Lucene.Net/Util/WeakIdentityMap.cs index 8444eb6..f2f55f4 100644 --- a/src/Lucene.Net/Util/WeakIdentityMap.cs +++ b/src/Lucene.Net/Util/WeakIdentityMap.cs @@ -1,398 +1,403 @@ -using System; -using System.Collections.Concurrent; -using System.Collections.Generic; -using System.Runtime.CompilerServices; -using System.Collections; - -namespace Lucene.Net.Util -{ - /* - * Licensed to the Apache Software Foundation (ASF) under one or more - * contributor license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright ownership. - * The ASF licenses this file to You under the Apache License, Version 2.0 - * (the "License"); you may not use this file except in compliance with - * the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - - /// <summary> - /// Implements a combination of <c>java.util.WeakHashMap</c> and - /// <c>java.util.IdentityHashMap</c>. - /// Useful for caches that need to key off of a <c>==</c> comparison - /// instead of a <c>.Equals(object)</c>. - /// - /// <para/>This class is not a general-purpose <see cref="IDictionary{TKey, TValue}"/> - /// implementation! It intentionally violates - /// <see cref="IDictionary{TKey, TValue}"/>'s general contract, which mandates the use of the <see cref="object.Equals(object)"/> method - /// when comparing objects. This class is designed for use only in the - /// rare cases wherein reference-equality semantics are required. - /// - /// <para/>This implementation was forked from <a href="http://cxf.apache.org/">Apache CXF</a> - /// but modified to <b>not</b> implement the <see cref="IDictionary{TKey, TValue}"/> interface and - /// without any set views on it, as those are error-prone and inefficient, - /// if not implemented carefully. The map only contains <see cref="IEnumerable{T}.GetEnumerator()"/> implementations - /// on the values and not-GCed keys. Lucene's implementation also supports <c>null</c> - /// keys, but those are never weak! - /// - /// <para/><a name="reapInfo" />The map supports two modes of operation: - /// <list type="bullet"> - /// <item><term><c>reapOnRead = true</c>:</term><description> This behaves identical to a <c>java.util.WeakHashMap</c> - /// where it also cleans up the reference queue on every read operation (<see cref="Get(object)"/>, - /// <see cref="ContainsKey(object)"/>, <see cref="Count"/>, <see cref="GetValueEnumerator()"/>), freeing map entries - /// of already GCed keys.</description></item> - /// <item><term><c>reapOnRead = false</c>:</term><description> This mode does not call <see cref="Reap()"/> on every read - /// operation. In this case, the reference queue is only cleaned up on write operations - /// (like <see cref="Put(TKey, TValue)"/>). This is ideal for maps with few entries where - /// the keys are unlikely be garbage collected, but there are lots of <see cref="Get(object)"/> - /// operations. The code can still call <see cref="Reap()"/> to manually clean up the queue without - /// doing a write operation.</description></item> - /// </list> - /// <para/> - /// @lucene.internal - /// </summary> - public sealed class WeakIdentityMap<TKey, TValue> - where TKey : class - { - private readonly IDictionary<IdentityWeakReference, TValue> backingStore; - - private readonly bool reapOnRead; - - /// <summary> - /// Creates a new <see cref="WeakIdentityMap{TKey, TValue}"/> based on a non-synchronized <see cref="Dictionary{TKey, TValue}"/>. - /// The map <a href="#reapInfo">cleans up the reference queue on every read operation</a>. - /// </summary> - public static WeakIdentityMap<TKey, TValue> NewHashMap() - { - return NewHashMap(false); - } - - /// <summary> - /// Creates a new <see cref="WeakIdentityMap{TKey, TValue}"/> based on a non-synchronized <see cref="Dictionary{TKey, TValue}"/>. </summary> - /// <param name="reapOnRead"> controls if the map <a href="#reapInfo">cleans up the reference queue on every read operation</a>. </param> - public static WeakIdentityMap<TKey, TValue> NewHashMap(bool reapOnRead) - { - return new WeakIdentityMap<TKey, TValue>(new Dictionary<IdentityWeakReference, TValue>(), reapOnRead); - } - - /// <summary> - /// Creates a new <see cref="WeakIdentityMap{TKey, TValue}"/> based on a <see cref="ConcurrentDictionary{TKey, TValue}"/>. - /// The map <a href="#reapInfo">cleans up the reference queue on every read operation</a>. - /// </summary> - public static WeakIdentityMap<TKey, TValue> NewConcurrentHashMap() - { - return NewConcurrentHashMap(true); - } - - /// <summary> - /// Creates a new <see cref="WeakIdentityMap{TKey, TValue}"/> based on a <see cref="ConcurrentDictionary{TKey, TValue}"/>. </summary> - /// <param name="reapOnRead"> controls if the map <a href="#reapInfo">cleans up the reference queue on every read operation</a>. </param> - public static WeakIdentityMap<TKey, TValue> NewConcurrentHashMap(bool reapOnRead) - { - return new WeakIdentityMap<TKey, TValue>(new ConcurrentDictionary<IdentityWeakReference, TValue>(), reapOnRead); - } - - /// <summary> - /// Private only constructor, to create use the static factory methods. </summary> - private WeakIdentityMap(IDictionary<IdentityWeakReference, TValue> backingStore, bool reapOnRead) - { - this.backingStore = backingStore; - this.reapOnRead = reapOnRead; - } - - /// <summary> - /// Removes all of the mappings from this map. </summary> - public void Clear() - { - backingStore.Clear(); - Reap(); - } - - /// <summary> - /// Returns <c>true</c> if this map contains a mapping for the specified key. </summary> - public bool ContainsKey(object key) - { - if (reapOnRead) - { - Reap(); - } - return backingStore.ContainsKey(new IdentityWeakReference(key)); - } - - /// <summary> - /// Returns the value to which the specified key is mapped. </summary> - public TValue Get(object key) - { - if (reapOnRead) - { - Reap(); - } - - TValue val; - if (backingStore.TryGetValue(new IdentityWeakReference(key), out val)) - { - return val; - } - else - { - return default(TValue); - } - } - - /// <summary> - /// Associates the specified value with the specified key in this map. - /// If the map previously contained a mapping for this key, the old value - /// is replaced. - /// </summary> - public TValue Put(TKey key, TValue value) - { - Reap(); - return backingStore[new IdentityWeakReference(key)] = value; - } - - /// <summary> - /// Gets an <see cref="IEnumerable{TKey}"/> object containing the keys of the <see cref="WeakIdentityMap{TKey, TValue}"/>. - /// </summary> - public IEnumerable<TKey> Keys - { - get - { - return new KeyWrapper(this); - } - } - - /// <summary> - /// LUCENENET specific class to allow the - /// GetEnumerator() method to be overridden - /// for the keys so we can return an enumerator - /// that is smart enough to clean up the dead keys - /// and also so that MoveNext() returns false in the - /// event there are no more values left (instead of returning - /// a null value in an extra enumeration). - /// </summary> - private class KeyWrapper : IEnumerable<TKey> - { - private readonly WeakIdentityMap<TKey, TValue> outerInstance; - public KeyWrapper(WeakIdentityMap<TKey, TValue> outerInstance) - { - this.outerInstance = outerInstance; - } - public IEnumerator<TKey> GetEnumerator() - { - outerInstance.Reap(); - return new IteratorAnonymousInnerClassHelper(outerInstance); - } - - IEnumerator IEnumerable.GetEnumerator() - { - return GetEnumerator(); - } - } - - /// <summary> - /// Gets an <see cref="IEnumerable{TKey}"/> object containing the values of the <see cref="WeakIdentityMap{TKey, TValue}"/>. - /// </summary> - public IEnumerable<TValue> Values - { - get - { - if (reapOnRead) Reap(); - return backingStore.Values; - } - } - - /// <summary> - /// Returns <c>true</c> if this map contains no key-value mappings. </summary> - public bool IsEmpty - { - get - { - return Count == 0; - } - } - - /// <summary> - /// Removes the mapping for a key from this weak hash map if it is present. - /// Returns the value to which this map previously associated the key, - /// or <c>null</c> if the map contained no mapping for the key. - /// A return value of <c>null</c> does not necessarily indicate that - /// the map contained. - /// </summary> - public bool Remove(object key) - { - Reap(); - return backingStore.Remove(new IdentityWeakReference(key)); - } - - /// <summary> - /// Returns the number of key-value mappings in this map. This result is a snapshot, - /// and may not reflect unprocessed entries that will be removed before next - /// attempted access because they are no longer referenced. - /// <para/> - /// NOTE: This was size() in Lucene. - /// </summary> - public int Count - { - get - { - if (backingStore.Count == 0) - { - return 0; - } - if (reapOnRead) - { - Reap(); - } - return backingStore.Count; - } - } - - private class IteratorAnonymousInnerClassHelper : IEnumerator<TKey> - { - private readonly WeakIdentityMap<TKey, TValue> outerInstance; - private readonly IEnumerator<KeyValuePair<IdentityWeakReference, TValue>> enumerator; - - public IteratorAnonymousInnerClassHelper(WeakIdentityMap<TKey, TValue> outerInstance) - { - this.outerInstance = outerInstance; - enumerator = outerInstance.backingStore.GetEnumerator(); - } - - // holds strong reference to next element in backing iterator: - private object next = null; - - public TKey Current - { - get - { - return (TKey)next; - } - } - - object IEnumerator.Current - { - get - { - return Current; - } - } - - - public void Dispose() - { - enumerator.Dispose(); - } - - - public bool MoveNext() - { - while (enumerator.MoveNext()) - { - next = enumerator.Current.Key.Target; - if (next != null) - { - // unfold "null" special value: - if (next == NULL) - next = null; - return true; - } - } - return false; - } - - public void Reset() - { - enumerator.Reset(); - } - } - - /// <summary> - /// Returns an iterator over all values of this map. - /// This iterator may return values whose key is already - /// garbage collected while iterator is consumed, - /// especially if <see cref="reapOnRead"/> is <c>false</c>. - /// <para/> - /// NOTE: This was valueIterator() in Lucene. - /// </summary> - public IEnumerator<TValue> GetValueEnumerator() - { - if (reapOnRead) - { - Reap(); - } - return backingStore.Values.GetEnumerator(); - } - - /// <summary> - /// This method manually cleans up the reference queue to remove all garbage - /// collected key/value pairs from the map. Calling this method is not needed - /// if <c>reapOnRead = true</c>. Otherwise it might be a good idea - /// to call this method when there is spare time (e.g. from a background thread). - /// <a href="#reapInfo">Information about the <c>reapOnRead</c> setting</a> - /// </summary> - public void Reap() - { - List<IdentityWeakReference> keysToRemove = null; - foreach (var item in backingStore) - { - if (!item.Key.IsAlive) - { - // create the list of keys to remove only if there are keys to remove. - // this reduces heap pressure - if (keysToRemove == null) - keysToRemove = new List<IdentityWeakReference>(); - keysToRemove.Add(item.Key); - } - } - - if (keysToRemove != null) - foreach (var key in keysToRemove) - { - backingStore.Remove(key); - } - } - - // we keep a hard reference to our NULL key, so map supports null keys that never get GCed: - internal static readonly object NULL = new object(); - - private sealed class IdentityWeakReference : WeakReference - { - private readonly int hash; - - internal IdentityWeakReference(object obj/*, ReferenceQueue<object> queue*/) - : base(obj == null ? NULL : obj/*, queue*/) - { - hash = RuntimeHelpers.GetHashCode(obj); - } - - public override int GetHashCode() - { - return hash; - } - - public override bool Equals(object o) - { - if (this == o) - { - return true; - } - IdentityWeakReference @ref = o as IdentityWeakReference; - if (@ref != null) - { - if (this.Target == @ref.Target) - { - return true; - } - } - return false; - } - } - } -} \ No newline at end of file +// LUCENENET specific - factored out this class and replaced it with ConditionalWeakTable<TKey, TValue>. +// ConditionalWeakTable<TKey, TValue> is thread-safe and internally uses RuntimeHelpers.GetHashCode() +// to lookup the key, so it can be used as a direct replacement for WeakIdentityMap<TKey, TValue> +// in most cases. + +//using System; +//using System.Collections.Concurrent; +//using System.Collections.Generic; +//using System.Runtime.CompilerServices; +//using System.Collections; + +//namespace Lucene.Net.Util +//{ +// /* +// * Licensed to the Apache Software Foundation (ASF) under one or more +// * contributor license agreements. See the NOTICE file distributed with +// * this work for additional information regarding copyright ownership. +// * The ASF licenses this file to You under the Apache License, Version 2.0 +// * (the "License"); you may not use this file except in compliance with +// * the License. You may obtain a copy of the License at +// * +// * http://www.apache.org/licenses/LICENSE-2.0 +// * +// * Unless required by applicable law or agreed to in writing, software +// * distributed under the License is distributed on an "AS IS" BASIS, +// * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// * See the License for the specific language governing permissions and +// * limitations under the License. +// */ + +// /// <summary> +// /// Implements a combination of <c>java.util.WeakHashMap</c> and +// /// <c>java.util.IdentityHashMap</c>. +// /// Useful for caches that need to key off of a <c>==</c> comparison +// /// instead of a <c>.Equals(object)</c>. +// /// +// /// <para/>This class is not a general-purpose <see cref="IDictionary{TKey, TValue}"/> +// /// implementation! It intentionally violates +// /// <see cref="IDictionary{TKey, TValue}"/>'s general contract, which mandates the use of the <see cref="object.Equals(object)"/> method +// /// when comparing objects. This class is designed for use only in the +// /// rare cases wherein reference-equality semantics are required. +// /// +// /// <para/>This implementation was forked from <a href="http://cxf.apache.org/">Apache CXF</a> +// /// but modified to <b>not</b> implement the <see cref="IDictionary{TKey, TValue}"/> interface and +// /// without any set views on it, as those are error-prone and inefficient, +// /// if not implemented carefully. The map only contains <see cref="IEnumerable{T}.GetEnumerator()"/> implementations +// /// on the values and not-GCed keys. Lucene's implementation also supports <c>null</c> +// /// keys, but those are never weak! +// /// +// /// <para/><a name="reapInfo" />The map supports two modes of operation: +// /// <list type="bullet"> +// /// <item><term><c>reapOnRead = true</c>:</term><description> This behaves identical to a <c>java.util.WeakHashMap</c> +// /// where it also cleans up the reference queue on every read operation (<see cref="Get(object)"/>, +// /// <see cref="ContainsKey(object)"/>, <see cref="Count"/>, <see cref="GetValueEnumerator()"/>), freeing map entries +// /// of already GCed keys.</description></item> +// /// <item><term><c>reapOnRead = false</c>:</term><description> This mode does not call <see cref="Reap()"/> on every read +// /// operation. In this case, the reference queue is only cleaned up on write operations +// /// (like <see cref="Put(TKey, TValue)"/>). This is ideal for maps with few entries where +// /// the keys are unlikely be garbage collected, but there are lots of <see cref="Get(object)"/> +// /// operations. The code can still call <see cref="Reap()"/> to manually clean up the queue without +// /// doing a write operation.</description></item> +// /// </list> +// /// <para/> +// /// @lucene.internal +// /// </summary> +// public sealed class WeakIdentityMap<TKey, TValue> +// where TKey : class +// { +// private readonly IDictionary<IdentityWeakReference, TValue> backingStore; + +// private readonly bool reapOnRead; + +// /// <summary> +// /// Creates a new <see cref="WeakIdentityMap{TKey, TValue}"/> based on a non-synchronized <see cref="Dictionary{TKey, TValue}"/>. +// /// The map <a href="#reapInfo">cleans up the reference queue on every read operation</a>. +// /// </summary> +// public static WeakIdentityMap<TKey, TValue> NewHashMap() +// { +// return NewHashMap(false); +// } + +// /// <summary> +// /// Creates a new <see cref="WeakIdentityMap{TKey, TValue}"/> based on a non-synchronized <see cref="Dictionary{TKey, TValue}"/>. </summary> +// /// <param name="reapOnRead"> controls if the map <a href="#reapInfo">cleans up the reference queue on every read operation</a>. </param> +// public static WeakIdentityMap<TKey, TValue> NewHashMap(bool reapOnRead) +// { +// return new WeakIdentityMap<TKey, TValue>(new Dictionary<IdentityWeakReference, TValue>(), reapOnRead); +// } + +// /// <summary> +// /// Creates a new <see cref="WeakIdentityMap{TKey, TValue}"/> based on a <see cref="ConcurrentDictionary{TKey, TValue}"/>. +// /// The map <a href="#reapInfo">cleans up the reference queue on every read operation</a>. +// /// </summary> +// public static WeakIdentityMap<TKey, TValue> NewConcurrentHashMap() +// { +// return NewConcurrentHashMap(true); +// } + +// /// <summary> +// /// Creates a new <see cref="WeakIdentityMap{TKey, TValue}"/> based on a <see cref="ConcurrentDictionary{TKey, TValue}"/>. </summary> +// /// <param name="reapOnRead"> controls if the map <a href="#reapInfo">cleans up the reference queue on every read operation</a>. </param> +// public static WeakIdentityMap<TKey, TValue> NewConcurrentHashMap(bool reapOnRead) +// { +// return new WeakIdentityMap<TKey, TValue>(new ConcurrentDictionary<IdentityWeakReference, TValue>(), reapOnRead); +// } + +// /// <summary> +// /// Private only constructor, to create use the static factory methods. </summary> +// private WeakIdentityMap(IDictionary<IdentityWeakReference, TValue> backingStore, bool reapOnRead) +// { +// this.backingStore = backingStore; +// this.reapOnRead = reapOnRead; +// } + +// /// <summary> +// /// Removes all of the mappings from this map. </summary> +// public void Clear() +// { +// backingStore.Clear(); +// Reap(); +// } + +// /// <summary> +// /// Returns <c>true</c> if this map contains a mapping for the specified key. </summary> +// public bool ContainsKey(object key) +// { +// if (reapOnRead) +// { +// Reap(); +// } +// return backingStore.ContainsKey(new IdentityWeakReference(key)); +// } + +// /// <summary> +// /// Returns the value to which the specified key is mapped. </summary> +// public TValue Get(object key) +// { +// if (reapOnRead) +// { +// Reap(); +// } + +// TValue val; +// if (backingStore.TryGetValue(new IdentityWeakReference(key), out val)) +// { +// return val; +// } +// else +// { +// return default(TValue); +// } +// } + +// /// <summary> +// /// Associates the specified value with the specified key in this map. +// /// If the map previously contained a mapping for this key, the old value +// /// is replaced. +// /// </summary> +// public TValue Put(TKey key, TValue value) +// { +// Reap(); +// return backingStore[new IdentityWeakReference(key)] = value; +// } + +// /// <summary> +// /// Gets an <see cref="IEnumerable{TKey}"/> object containing the keys of the <see cref="WeakIdentityMap{TKey, TValue}"/>. +// /// </summary> +// public IEnumerable<TKey> Keys +// { +// get +// { +// return new KeyWrapper(this); +// } +// } + +// /// <summary> +// /// LUCENENET specific class to allow the +// /// GetEnumerator() method to be overridden +// /// for the keys so we can return an enumerator +// /// that is smart enough to clean up the dead keys +// /// and also so that MoveNext() returns false in the +// /// event there are no more values left (instead of returning +// /// a null value in an extra enumeration). +// /// </summary> +// private class KeyWrapper : IEnumerable<TKey> +// { +// private readonly WeakIdentityMap<TKey, TValue> outerInstance; +// public KeyWrapper(WeakIdentityMap<TKey, TValue> outerInstance) +// { +// this.outerInstance = outerInstance; +// } +// public IEnumerator<TKey> GetEnumerator() +// { +// outerInstance.Reap(); +// return new IteratorAnonymousInnerClassHelper(outerInstance); +// } + +// IEnumerator IEnumerable.GetEnumerator() +// { +// return GetEnumerator(); +// } +// } + +// /// <summary> +// /// Gets an <see cref="IEnumerable{TKey}"/> object containing the values of the <see cref="WeakIdentityMap{TKey, TValue}"/>. +// /// </summary> +// public IEnumerable<TValue> Values +// { +// get +// { +// if (reapOnRead) Reap(); +// return backingStore.Values; +// } +// } + +// /// <summary> +// /// Returns <c>true</c> if this map contains no key-value mappings. </summary> +// public bool IsEmpty +// { +// get +// { +// return Count == 0; +// } +// } + +// /// <summary> +// /// Removes the mapping for a key from this weak hash map if it is present. +// /// Returns the value to which this map previously associated the key, +// /// or <c>null</c> if the map contained no mapping for the key. +// /// A return value of <c>null</c> does not necessarily indicate that +// /// the map contained. +// /// </summary> +// public bool Remove(object key) +// { +// Reap(); +// return backingStore.Remove(new IdentityWeakReference(key)); +// } + +// /// <summary> +// /// Returns the number of key-value mappings in this map. This result is a snapshot, +// /// and may not reflect unprocessed entries that will be removed before next +// /// attempted access because they are no longer referenced. +// /// <para/> +// /// NOTE: This was size() in Lucene. +// /// </summary> +// public int Count +// { +// get +// { +// if (backingStore.Count == 0) +// { +// return 0; +// } +// if (reapOnRead) +// { +// Reap(); +// } +// return backingStore.Count; +// } +// } + +// private class IteratorAnonymousInnerClassHelper : IEnumerator<TKey> +// { +// private readonly WeakIdentityMap<TKey, TValue> outerInstance; +// private readonly IEnumerator<KeyValuePair<IdentityWeakReference, TValue>> enumerator; + +// public IteratorAnonymousInnerClassHelper(WeakIdentityMap<TKey, TValue> outerInstance) +// { +// this.outerInstance = outerInstance; +// enumerator = outerInstance.backingStore.GetEnumerator(); +// } + +// // holds strong reference to next element in backing iterator: +// private object next = null; + +// public TKey Current +// { +// get +// { +// return (TKey)next; +// } +// } + +// object IEnumerator.Current +// { +// get +// { +// return Current; +// } +// } + + +// public void Dispose() +// { +// enumerator.Dispose(); +// } + + +// public bool MoveNext() +// { +// while (enumerator.MoveNext()) +// { +// next = enumerator.Current.Key.Target; +// if (next != null) +// { +// // unfold "null" special value: +// if (next == NULL) +// next = null; +// return true; +// } +// } +// return false; +// } + +// public void Reset() +// { +// enumerator.Reset(); +// } +// } + +// /// <summary> +// /// Returns an iterator over all values of this map. +// /// This iterator may return values whose key is already +// /// garbage collected while iterator is consumed, +// /// especially if <see cref="reapOnRead"/> is <c>false</c>. +// /// <para/> +// /// NOTE: This was valueIterator() in Lucene. +// /// </summary> +// public IEnumerator<TValue> GetValueEnumerator() +// { +// if (reapOnRead) +// { +// Reap(); +// } +// return backingStore.Values.GetEnumerator(); +// } + +// /// <summary> +// /// This method manually cleans up the reference queue to remove all garbage +// /// collected key/value pairs from the map. Calling this method is not needed +// /// if <c>reapOnRead = true</c>. Otherwise it might be a good idea +// /// to call this method when there is spare time (e.g. from a background thread). +// /// <a href="#reapInfo">Information about the <c>reapOnRead</c> setting</a> +// /// </summary> +// public void Reap() +// { +// List<IdentityWeakReference> keysToRemove = null; +// foreach (var item in backingStore) +// { +// if (!item.Key.IsAlive) +// { +// // create the list of keys to remove only if there are keys to remove. +// // this reduces heap pressure +// if (keysToRemove == null) +// keysToRemove = new List<IdentityWeakReference>(); +// keysToRemove.Add(item.Key); +// } +// } + +// if (keysToRemove != null) +// foreach (var key in keysToRemove) +// { +// backingStore.Remove(key); +// } +// } + +// // we keep a hard reference to our NULL key, so map supports null keys that never get GCed: +// internal static readonly object NULL = new object(); + +// private sealed class IdentityWeakReference : WeakReference +// { +// private readonly int hash; + +// internal IdentityWeakReference(object obj/*, ReferenceQueue<object> queue*/) +// : base(obj == null ? NULL : obj/*, queue*/) +// { +// hash = RuntimeHelpers.GetHashCode(obj); +// } + +// public override int GetHashCode() +// { +// return hash; +// } + +// public override bool Equals(object o) +// { +// if (this == o) +// { +// return true; +// } +// IdentityWeakReference @ref = o as IdentityWeakReference; +// if (@ref != null) +// { +// if (this.Target == @ref.Target) +// { +// return true; +// } +// } +// return false; +// } +// } +// } +//} \ No newline at end of file
