You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@lucenenet.apache.org by ni...@apache.org on 2020/01/23 17:19:31 UTC
[lucenenet] 01/02: Factored out WeakIdentityMap in favor of
ConditionalWeakTable (fixes LUCENENET-640, closes #236)
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 <sh...@shadstorhaug.com>
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 <sh...@shadstorhaug.com>
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 <sh...@shadstorhaug.com>
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 <sh...@shadstorhaug.com>
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 <sh...@shadstorhaug.com>
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 (!@ref.TryGetTarget(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