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
The following commit(s) were added to refs/heads/master by this push:
new b7f8a50 Factored out WeakDictionary in favor of weak events using
Prism.Core (#613)
b7f8a50 is described below
commit b7f8a501f9096b911112f4c48b7eec5b7af5fb9b
Author: Shad Storhaug <[email protected]>
AuthorDate: Wed Feb 9 11:47:44 2022 +0700
Factored out WeakDictionary in favor of weak events using Prism.Core (#613)
* Lucene.Net.Runtime.CompilerServices: Added ConditionalWeakTableExtensions
class to provide a workaround for the lack of AddOrUpdate on .NET Standard 2.0
and prior
* Lucene.Net.TestFramework.Index.ThreadedIndexingAndSearchingTestCase:
Factored out WeakDictionary and use external locking to fixup AddOrUpdate where
it is not supported.
* Lucene.Net.TestFramework.Search.AssertingScorer: Factored out
WeakDictionary and use external locking to fixup AddOrUpdate where it is not
supported.
* Lucene.Net.Util: Added ExcludeFromRamUsageEstimation attribute to exclude
fields from being considered in the calculation
* Lucene.Net.csproj: Added package reference on Prism.Core
* Lucene.Net.Facet.Taxonomy.CachedOrdinalsReader: Use Prism event
aggregator to handle getting estimated RAM usage on .NET Standard 2.0 and .NET
Framework
* Lucene.Net.Search.CachingWrapperFilter: Use Prism event aggregator to
retrieve weak DocIdSet references on .NET Standard 2.0 and .NET Framework
* Lucene.Net.Index.IndexReader: Use Prism event aggregator to handle
getting parent readers weakly on .NET Standard 2.0 and .NET Framework
* Lucene.Net.Search.FieldCacheImpl: Use Prism event aggregator to handle
AddCacheEntries by retrieving the keys via weak events and then updating the
values one at a time on .NET Standard 2.0 and .NET Framework.
* Deleted Lucene.Net.Support.WeakDictionary + tests (closes #256, closes
#604, closes #605)
* Lucene.Net.Support.Util: Created Events static class and migrated
GetParentReadersEvent and GetCacheKeysEvent declarations to decouple references
between consumers.
* Lucene.Net.Facet.Taxonomy.CachedOrdinalsReader: Changed to use
GetCacheKeysEvent to avoid the need for a finalizer on CachedOrds and
additional event declaration
* Lucene.Net.Search.CachingWrapperFilter: Changed to use GetCacheKeysEvent
to avoid the need for a finalizer on DocIdSet and an additional event
declaration
* Lucene.Net.Index.IndexReader: Reworked Prism events so they can be
registered multiple times, and will track the registrations to clean them up
during Dispose()
---
.build/dependencies.props | 1 +
.../Taxonomy/CachedOrdinalsReader.cs | 51 +++-
.../Index/ThreadedIndexingAndSearchingTestCase.cs | 42 ++-
.../Search/AssertingScorer.cs | 58 ++--
src/Lucene.Net.Tests/Support/TestWeakDictionary.cs | 150 -----------
.../Support/TestWeakDictionaryBehavior.cs | 294 --------------------
.../Support/TestWeakDictionaryPerformance.cs | 133 ---------
src/Lucene.Net/Index/IndexReader.cs | 90 ++++++-
src/Lucene.Net/Lucene.Net.csproj | 4 +-
src/Lucene.Net/Search/CachingWrapperFilter.cs | 65 +++--
src/Lucene.Net/Search/DocIdSet.cs | 2 +-
src/Lucene.Net/Search/FieldCacheImpl.cs | 37 ++-
.../ConditionalWeakTableExtensions.cs | 53 ++++
src/Lucene.Net/Support/Util/Events.cs | 62 +++++
.../Util/ExcludeFromRamUsageEstimationAttribute.cs | 29 ++
src/Lucene.Net/Support/WeakDictionary.cs | 300 ---------------------
src/Lucene.Net/Util/RamUsageEstimator.cs | 3 +-
17 files changed, 419 insertions(+), 955 deletions(-)
diff --git a/.build/dependencies.props b/.build/dependencies.props
index 04da283..3245a77 100644
--- a/.build/dependencies.props
+++ b/.build/dependencies.props
@@ -71,6 +71,7 @@
<NUnit3TestAdapterPackageVersion>3.17.0</NUnit3TestAdapterPackageVersion>
<NUnitPackageVersion>3.13.1</NUnitPackageVersion>
<OpenNLPNETPackageVersion>1.9.1.1</OpenNLPNETPackageVersion>
+ <PrismCorePackageVersion>7.2.0.1422</PrismCorePackageVersion>
<RandomizedTestingGeneratorsPackageVersion>2.7.8</RandomizedTestingGeneratorsPackageVersion>
<SharpZipLibPackageVersion>1.1.0</SharpZipLibPackageVersion>
<Spatial4nCorePackageVersion>0.4.1</Spatial4nCorePackageVersion>
diff --git a/src/Lucene.Net.Facet/Taxonomy/CachedOrdinalsReader.cs
b/src/Lucene.Net.Facet/Taxonomy/CachedOrdinalsReader.cs
index c9a9748..cd02336 100644
--- a/src/Lucene.Net.Facet/Taxonomy/CachedOrdinalsReader.cs
+++ b/src/Lucene.Net.Facet/Taxonomy/CachedOrdinalsReader.cs
@@ -1,10 +1,14 @@
// Lucene version compatibility level 4.8.1
using Lucene.Net.Support;
using Lucene.Net.Support.Threading;
+using Lucene.Net.Util;
+#if !FEATURE_CONDITIONALWEAKTABLE_ENUMERATOR
+using Prism.Events;
+#endif
using System;
+using System.Collections.Generic;
using System.Diagnostics.CodeAnalysis;
using System.Runtime.CompilerServices;
-using System.Threading;
namespace Lucene.Net.Facet.Taxonomy
{
@@ -65,11 +69,12 @@ namespace Lucene.Net.Facet.Taxonomy
{
private readonly OrdinalsReader source;
-#if FEATURE_CONDITIONALWEAKTABLE_ENUMERATOR
- private readonly ConditionalWeakTable<object, CachedOrds> ordsCache =
new ConditionalWeakTable<object, CachedOrds>();
-#else
- private readonly WeakDictionary<object, CachedOrds> ordsCache = new
WeakDictionary<object, CachedOrds>();
+#if !FEATURE_CONDITIONALWEAKTABLE_ENUMERATOR
+ // LUCENENET specific: Add weak event handler for .NET Standard 2.0
and .NET Framework, since we don't have an enumerator to use
+ private readonly IEventAggregator eventAggregator = new
EventAggregator();
#endif
+
+ private readonly ConditionalWeakTable<object, CachedOrds> ordsCache =
new ConditionalWeakTable<object, CachedOrds>();
private readonly object syncLock = new object();
/// <summary>
@@ -96,6 +101,10 @@ namespace Lucene.Net.Facet.Taxonomy
// which also means we don't need conditional compilation
because ConditionalWeakTable
// doesn't support this[index].
ordsCache.Add(cacheKey, ords);
+#if !FEATURE_CONDITIONALWEAKTABLE_ENUMERATOR
+ // LUCENENET specific: Add weak event handler for .NET
Standard 2.0 and .NET Framework, since we don't have an enumerator to use
+
context.Reader.SubscribeToGetCacheKeysEvent(eventAggregator.GetEvent<Events.GetCacheKeysEvent>());
+#endif
}
return ords;
}
@@ -204,21 +213,41 @@ namespace Lucene.Net.Facet.Taxonomy
public virtual long RamBytesUsed()
{
+ // LUCENENET specific - moved Reflection calls outside of the lock
(similar to CachingWrapperFilter) to improve performance.
+
+ // Sync only to pull the current set of values:
+ IList<CachedOrds> cachedOrdsList;
UninterruptableMonitor.Enter(syncLock);
try
{
- long bytes = 0;
+ cachedOrdsList = new List<CachedOrds>();
+#if FEATURE_CONDITIONALWEAKTABLE_ENUMERATOR
foreach (var pair in ordsCache)
- {
- bytes += pair.Value.RamBytesUsed();
- }
-
- return bytes;
+ cachedOrdsList.Add(pair.Value);
+#else
+ // LUCENENET specific - since .NET Standard 2.0 and .NET
Framework don't have a CondtionalWeakTable enumerator,
+ // we use a weak event to retrieve the CachedOrds instances.
We look each of these up here to avoid the need
+ // to attach events to the CachedOrds instances themselves
(thus using the existing IndexReader.Dispose()
+ // method to detach the events rather than using a finalizer
in CachedOrds to ensure they are cleaned up).
+ var e = new Events.GetCacheKeysEventArgs();
+
eventAggregator.GetEvent<Events.GetCacheKeysEvent>().Publish(e);
+ foreach (var key in e.CacheKeys)
+ if (ordsCache.TryGetValue(key, out CachedOrds value))
+ cachedOrdsList.Add(value);
+#endif
}
finally
{
UninterruptableMonitor.Exit(syncLock);
}
+
+ long total = 0;
+ foreach (CachedOrds ord in cachedOrdsList)
+ {
+ total += ord.RamBytesUsed();
+ }
+
+ return total;
}
}
}
\ No newline at end of file
diff --git
a/src/Lucene.Net.TestFramework/Index/ThreadedIndexingAndSearchingTestCase.cs
b/src/Lucene.Net.TestFramework/Index/ThreadedIndexingAndSearchingTestCase.cs
index daf3fda..b2c2d82 100644
--- a/src/Lucene.Net.TestFramework/Index/ThreadedIndexingAndSearchingTestCase.cs
+++ b/src/Lucene.Net.TestFramework/Index/ThreadedIndexingAndSearchingTestCase.cs
@@ -4,6 +4,7 @@ using Lucene.Net.Analysis;
using Lucene.Net.Diagnostics;
using Lucene.Net.Documents;
using Lucene.Net.Index.Extensions;
+using Lucene.Net.Runtime.CompilerServices;
using Lucene.Net.Search;
using Lucene.Net.Store;
using Lucene.Net.Support;
@@ -461,9 +462,21 @@ namespace Lucene.Net.Index
assertNotNull(source);
if (source.Equals("merge",
StringComparison.Ordinal))
{
- assertTrue("sub reader " + sub + " wasn't
warmed: warmed=" + outerInstance.warmed + " diagnostics=" + diagnostics + "
si=" + segReader.SegmentInfo,
- // LUCENENET: ConditionalWeakTable
doesn't have ContainsKey, so we normalize to TryGetValue
-
!outerInstance.m_assertMergedSegmentsWarmed ||
outerInstance.warmed.TryGetValue(segReader.core, out BooleanRef _));
+#if !FEATURE_CONDITIONALWEAKTABLE_ADDORUPDATE
+
UninterruptableMonitor.Enter(outerInstance.warmedLock);
+ try
+ {
+#endif
+ assertTrue("sub reader " + sub + "
wasn't warmed: warmed=" + outerInstance.warmed + " diagnostics=" + diagnostics
+ " si=" + segReader.SegmentInfo,
+ // LUCENENET: ConditionalWeakTable
doesn't have ContainsKey, so we normalize to TryGetValue
+
!outerInstance.m_assertMergedSegmentsWarmed ||
outerInstance.warmed.TryGetValue(segReader.core, out BooleanRef _));
+#if !FEATURE_CONDITIONALWEAKTABLE_ADDORUPDATE
+ }
+ finally
+ {
+
UninterruptableMonitor.Exit(outerInstance.warmedLock);
+ }
+#endif
}
}
if (s.IndexReader.NumDocs > 0)
@@ -541,11 +554,10 @@ namespace Lucene.Net.Index
protected bool m_assertMergedSegmentsWarmed = true;
-#if FEATURE_CONDITIONALWEAKTABLE_ADDORUPDATE
- private readonly ConditionalWeakTable<SegmentCoreReaders, BooleanRef>
warmed = new ConditionalWeakTable<SegmentCoreReaders, BooleanRef>();
-#else
- private readonly IDictionary<SegmentCoreReaders, BooleanRef> warmed =
new WeakDictionary<SegmentCoreReaders, BooleanRef>().AsConcurrent();
+#if !FEATURE_CONDITIONALWEAKTABLE_ADDORUPDATE
+ private readonly object warmedLock = new object();
#endif
+ private readonly ConditionalWeakTable<SegmentCoreReaders, BooleanRef>
warmed = new ConditionalWeakTable<SegmentCoreReaders, BooleanRef>();
public virtual void RunTest(string testName)
{
@@ -799,10 +811,18 @@ namespace Lucene.Net.Index
{
Console.WriteLine("TEST: now warm merged reader=" +
reader);
}
-#if FEATURE_CONDITIONALWEAKTABLE_ADDORUPDATE
- outerInstance.warmed.AddOrUpdate(((SegmentReader)reader).core,
true);
-#else
- outerInstance.warmed[((SegmentReader)reader).core] = true;
+#if !FEATURE_CONDITIONALWEAKTABLE_ADDORUPDATE
+ UninterruptableMonitor.Enter(outerInstance.warmedLock);
+ try
+ {
+#endif
+
outerInstance.warmed.AddOrUpdate(((SegmentReader)reader).core, true);
+#if !FEATURE_CONDITIONALWEAKTABLE_ADDORUPDATE
+ }
+ finally
+ {
+ UninterruptableMonitor.Exit(outerInstance.warmedLock);
+ }
#endif
int maxDoc = reader.MaxDoc;
IBits liveDocs = reader.LiveDocs;
diff --git a/src/Lucene.Net.TestFramework/Search/AssertingScorer.cs
b/src/Lucene.Net.TestFramework/Search/AssertingScorer.cs
index 67ea723..4cb0d49 100644
--- a/src/Lucene.Net.TestFramework/Search/AssertingScorer.cs
+++ b/src/Lucene.Net.TestFramework/Search/AssertingScorer.cs
@@ -1,6 +1,8 @@
using Lucene.Net.Diagnostics;
using Lucene.Net.Index;
+using Lucene.Net.Runtime.CompilerServices;
using Lucene.Net.Support;
+using Lucene.Net.Support.Threading;
using System;
using System.Collections.Generic;
using System.Runtime.CompilerServices;
@@ -32,13 +34,11 @@ namespace Lucene.Net.Search
// we need to track scorers using a weak hash map because otherwise we
// could loose references because of eg.
// AssertingScorer.Score(Collector) which needs to delegate to work
correctly
-#if FEATURE_CONDITIONALWEAKTABLE_ADDORUPDATE
- private static readonly ConditionalWeakTable<Scorer,
WeakReference<AssertingScorer>> ASSERTING_INSTANCES =
- new ConditionalWeakTable<Scorer, WeakReference<AssertingScorer>>();
-#else
- private static readonly IDictionary<Scorer,
WeakReference<AssertingScorer>> ASSERTING_INSTANCES =
- new WeakDictionary<Scorer,
WeakReference<AssertingScorer>>().AsConcurrent();
+#if !FEATURE_CONDITIONALWEAKTABLE_ADDORUPDATE
+ private static readonly object assertingInstancesLock = new object();
#endif
+ private static readonly ConditionalWeakTable<Scorer,
WeakReference<AssertingScorer>> assertingInstances =
+ new ConditionalWeakTable<Scorer, WeakReference<AssertingScorer>>();
public static Scorer Wrap(Random random, Scorer other)
{
@@ -47,10 +47,18 @@ namespace Lucene.Net.Search
return other;
}
AssertingScorer assertScorer = new AssertingScorer(random, other);
-#if FEATURE_CONDITIONALWEAKTABLE_ADDORUPDATE
- ASSERTING_INSTANCES.AddOrUpdate(other, new
WeakReference<AssertingScorer>(assertScorer));
-#else
- ASSERTING_INSTANCES[other] = new
WeakReference<AssertingScorer>(assertScorer);
+#if !FEATURE_CONDITIONALWEAKTABLE_ADDORUPDATE
+ UninterruptableMonitor.Enter(assertingInstancesLock);
+ try
+ {
+#endif
+ assertingInstances.AddOrUpdate(other, new
WeakReference<AssertingScorer>(assertScorer));
+#if !FEATURE_CONDITIONALWEAKTABLE_ADDORUPDATE
+ }
+ finally
+ {
+ UninterruptableMonitor.Exit(assertingInstancesLock);
+ }
#endif
return assertScorer;
@@ -62,19 +70,31 @@ namespace Lucene.Net.Search
{
return other;
}
- if (!ASSERTING_INSTANCES.TryGetValue(other, out
WeakReference<AssertingScorer> assertingScorerRef) || assertingScorerRef ==
null ||
- !assertingScorerRef.TryGetTarget(out AssertingScorer
assertingScorer) || assertingScorer == null)
+#if !FEATURE_CONDITIONALWEAKTABLE_ADDORUPDATE
+ UninterruptableMonitor.Enter(assertingInstancesLock);
+ try
{
- // can happen in case of memory pressure or if
- // scorer1.Score(collector) calls
- // collector.setScorer(scorer2) with scorer1 != scorer2, such
as
- // BooleanScorer. In that case we can't enable all assertions
- return new AssertingScorer(random, other);
+#endif
+ if (!assertingInstances.TryGetValue(other, out
WeakReference<AssertingScorer> assertingScorerRef) || assertingScorerRef ==
null ||
+ !assertingScorerRef.TryGetTarget(out AssertingScorer
assertingScorer) || assertingScorer == null)
+ {
+ // can happen in case of memory pressure or if
+ // scorer1.Score(collector) calls
+ // collector.setScorer(scorer2) with scorer1 != scorer2,
such as
+ // BooleanScorer. In that case we can't enable all
assertions
+ return new AssertingScorer(random, other);
+ }
+ else
+ {
+ return assertingScorer;
+ }
+#if !FEATURE_CONDITIONALWEAKTABLE_ADDORUPDATE
}
- else
+ finally
{
- return assertingScorer;
+ UninterruptableMonitor.Exit(assertingInstancesLock);
}
+#endif
}
internal readonly Random random;
diff --git a/src/Lucene.Net.Tests/Support/TestWeakDictionary.cs
b/src/Lucene.Net.Tests/Support/TestWeakDictionary.cs
deleted file mode 100644
index f059139..0000000
--- a/src/Lucene.Net.Tests/Support/TestWeakDictionary.cs
+++ /dev/null
@@ -1,150 +0,0 @@
-#if !FEATURE_CONDITIONALWEAKTABLE_ENUMERATOR
-using Lucene.Net.Attributes;
-using Lucene.Net.Util;
-using NUnit.Framework;
-using System;
-using System.Collections;
-using System.Collections.Generic;
-using Assert = Lucene.Net.TestFramework.Assert;
-
-namespace Lucene.Net.Support
-{
- /*
- * 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 TestWeakDictionary : LuceneTestCase
- {
- [Test, LuceneNetSpecific]
- public void A_TestBasicOps()
- {
- IDictionary<object, object> weakDictionary =
TestWeakDictionaryBehavior.CreateDictionary();// new
SupportClass.TjWeakHashTable();
- Hashtable realHashTable = new Hashtable();
-
- SmallObject[] so = new SmallObject[100];
- for (int i = 0; i < 20000; i++)
- {
- SmallObject key = new SmallObject(i);
- SmallObject value = key;
- so[i / 200] = key;
- realHashTable.Add(key, value);
- weakDictionary.Add(key, value);
- }
-
- Assert.AreEqual(weakDictionary.Count, realHashTable.Count);
-
- ICollection keys = (ICollection)realHashTable.Keys;
-
- foreach (SmallObject key in keys)
- {
- Assert.AreEqual(((SmallObject)realHashTable[key]).i,
- ((SmallObject)weakDictionary[key]).i);
-
- Assert.IsTrue(realHashTable[key].Equals(weakDictionary[key]));
- }
-
-
- ICollection values1 = (ICollection)weakDictionary.Values;
- ICollection values2 = (ICollection)realHashTable.Values;
- Assert.AreEqual(values1.Count, values2.Count);
-
- realHashTable.Remove(new SmallObject(10000));
- weakDictionary.Remove(new SmallObject(10000));
- Assert.AreEqual(weakDictionary.Count, 20000);
- Assert.AreEqual(realHashTable.Count, 20000);
-
- for (int i = 0; i < so.Length; i++)
- {
- realHashTable.Remove(so[i]);
- weakDictionary.Remove(so[i]);
- Assert.AreEqual(weakDictionary.Count, 20000 - i - 1);
- Assert.AreEqual(realHashTable.Count, 20000 - i - 1);
- }
-
- //After removals, compare the collections again.
- ICollection keys2 = (ICollection)realHashTable.Keys;
- foreach (SmallObject o in keys2)
- {
- Assert.AreEqual(((SmallObject)realHashTable[o]).i,
- ((SmallObject)weakDictionary[o]).i);
- Assert.IsTrue(realHashTable[o].Equals(weakDictionary[o]));
- }
- }
-
- [Test, LuceneNetSpecific]
- [Slow]
- public void B_TestOutOfMemory()
- {
- var wht = TestWeakDictionaryBehavior.CreateDictionary();
- int OOMECount = 0;
-
- for (int i = 0; i < 1024 * 24 + 32; i++) // total requested Mem. >
24GB
- {
- try
- {
- wht.Add(new BigObject(i), i);
- if (i % 1024 == 0) Console.WriteLine("Requested Mem: " +
i.ToString() + " MB");
- OOMECount = 0;
- }
- catch (Exception oom) when (oom.IsOutOfMemoryError())
- {
- if (OOMECount++ > 10) throw new Exception("Memory
Allocation Error in B_TestOutOfMemory");
- //Try Again. GC will eventually release some memory.
- Console.WriteLine("OOME WHEN i=" + i.ToString() + ". Try
Again");
- System.Threading.Thread.Sleep(10);
- i--;
- continue;
- }
- }
-
- GC.Collect();
- Console.WriteLine("Passed out of memory exception.");
- }
-
- private long GetMemUsageInKB()
- {
- return System.Diagnostics.Process.GetCurrentProcess().WorkingSet64
/ 1024;
- }
-
- [Test, LuceneNetSpecific]
- [Slow]
- public void C_TestMemLeakage()
- {
-
- var wht = TestWeakDictionaryBehavior.CreateDictionary(); //new
SupportClass.TjWeakHashTable();
-
- GC.Collect();
- long initialMemUsage = GetMemUsageInKB();
-
- Console.WriteLine("Initial MemUsage=" + initialMemUsage);
- for (int i = 0; i < 10000; i++)
- {
- wht.Add(new BigObject(i), i);
- if (i % 100 == 0)
- {
- long mu = GetMemUsageInKB();
- Console.WriteLine(i.ToString() + ") MemUsage=" + mu);
- }
- }
-
- GC.Collect();
- long memUsage = GetMemUsageInKB();
- Assert.IsFalse(memUsage > initialMemUsage * 2, "Memory
Leakage.MemUsage = " + memUsage);
- }
- }
-}
-#endif
\ No newline at end of file
diff --git a/src/Lucene.Net.Tests/Support/TestWeakDictionaryBehavior.cs
b/src/Lucene.Net.Tests/Support/TestWeakDictionaryBehavior.cs
deleted file mode 100644
index 40dbfba..0000000
--- a/src/Lucene.Net.Tests/Support/TestWeakDictionaryBehavior.cs
+++ /dev/null
@@ -1,294 +0,0 @@
-#if !FEATURE_CONDITIONALWEAKTABLE_ENUMERATOR
-using Lucene.Net.Attributes;
-using Lucene.Net.Util;
-using NUnit.Framework;
-using System;
-using System.Collections.Generic;
-using Assert = Lucene.Net.TestFramework.Assert;
-
-namespace Lucene.Net.Support
-{
- /*
- * 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 TestWeakDictionaryBehavior : LuceneTestCase
- {
- IDictionary<object, object> dictionary;
-
- public static IDictionary<object, object> CreateDictionary()
- {
- return new WeakDictionary<object, object>();
- }
-
-
- private void CallGC()
- {
- for (int i = 0; i < 10; i++)
- {
- GC.Collect();
- GC.WaitForPendingFinalizers();
- }
- }
-
- [SetUp]
- public void Setup()
- {
- dictionary = CreateDictionary();
- }
-
- [Test, LuceneNetSpecific]
- public void Test_Dictionary_Add()
- {
- string key = "A";
-
- dictionary.Add(key, "value");
- Assert.IsTrue(dictionary.ContainsKey(key));
- Assert.AreEqual("value", dictionary[key]);
- Assert.AreEqual(1, dictionary.Count);
-
- CollectionAssert.AreEquivalent(dictionary.Values, new object[] {
"value" });
- }
-
- [Test, LuceneNetSpecific]
- public void Test_Dictionary_Add_2()
- {
- string key = "A";
- string key2 = "B";
-
- dictionary.Add(key, "value");
- dictionary.Add(key2, "value2");
- Assert.IsTrue(dictionary.ContainsKey(key));
- Assert.IsTrue(dictionary.ContainsKey(key2));
- Assert.AreEqual("value", dictionary[key]);
- Assert.AreEqual("value2", dictionary[key2]);
- Assert.AreEqual(2, dictionary.Count);
-
- CollectionAssert.AreEquivalent(dictionary.Values, new object[] {
"value", "value2" });
- }
-
- [Test, LuceneNetSpecific]
- public void Test_Keys()
- {
- string key = "A";
- string key2 = "B";
-
- dictionary.Add(key, "value");
- CollectionAssert.AreEquivalent(dictionary.Keys, new object[] { key
});
-
- dictionary.Add(key2, "value2");
- CollectionAssert.AreEquivalent(dictionary.Keys, new object[] {
key, key2 });
- }
-
- [Test, LuceneNetSpecific]
- public void Test_Dictionary_Add_Null()
- {
- Assert.Throws<ArgumentNullException>(() => dictionary.Add(null,
"value"));
- }
-
- [Test, LuceneNetSpecific]
- public void Test_Dictionary_Set_Null()
- {
- Assert.Throws<ArgumentNullException>(() => dictionary[null] =
"value");
- }
-
- [Test, LuceneNetSpecific]
- public void Test_Dictionary_AddReplace()
- {
- string key = "A";
- string key2 = "a".ToUpperInvariant();
-
- dictionary.Add(key, "value");
- dictionary[key2] = "value2";
-
- Assert.AreEqual(1, dictionary.Count);
- Assert.IsTrue(dictionary.ContainsKey(key));
- Assert.AreEqual("value2", dictionary[key]);
- }
-
- [Test, LuceneNetSpecific]
- public void Test_Dictionary_AddRemove()
- {
- string key = "A";
-
- dictionary.Add(key, "value");
- dictionary.Remove(key);
-
- Assert.AreEqual(0, dictionary.Count);
- Assert.IsFalse(dictionary.ContainsKey(key));
- Assert.IsFalse(dictionary.TryGetValue(key, out _));
- Assert.Throws<KeyNotFoundException>(() => { var x =
dictionary[key]; });
- }
-
- [Test, LuceneNetSpecific]
- public void Test_Dictionary_Clear()
- {
- string key = "A";
-
- dictionary.Add(key, "value");
- dictionary.Clear();
-
- Assert.AreEqual(0, dictionary.Count);
- Assert.IsFalse(dictionary.ContainsKey(key));
- Assert.IsFalse(dictionary.TryGetValue(key, out _));
- Assert.Throws<KeyNotFoundException>(() => { var x =
dictionary[key]; });
- }
-
- [Test, LuceneNetSpecific]
- public void Test_Dictionary_AddRemove_2()
- {
- string key = "A";
-
- dictionary.Add(key, "value");
- dictionary.Remove(key);
- dictionary.Remove(key);
-
- Assert.AreEqual(0, dictionary.Count);
- Assert.IsFalse(dictionary.ContainsKey(key));
- Assert.IsFalse(dictionary.TryGetValue(key, out _));
- Assert.Throws<KeyNotFoundException>(() => { var x =
dictionary[key]; });
- }
-
- [Test, LuceneNetSpecific]
- public void Test_Dictionary_Get_Null()
- {
- object value;
- Assert.Throws<ArgumentNullException>(() => value =
dictionary[null]);
- }
-
- [Test, LuceneNetSpecific]
- public void Test_Dictionary_Remove_Null()
- {
- Assert.Throws<ArgumentNullException>(() =>
dictionary.Remove(null));
- }
-
- [Test, LuceneNetSpecific]
- public void Test_Dictionary_GetEnumerator()
- {
- string key = "A";
-
- dictionary.Add(key, "value");
-
- var de = dictionary.GetEnumerator();
- Assert.IsTrue(de.MoveNext());
- Assert.AreEqual(key, de.Current.Key);
- Assert.AreEqual("value", de.Current.Value);
- }
-
- [Test, LuceneNetSpecific]
- public void Test_Dictionary_ForEach()
- {
- string key = "A";
-
- dictionary.Add(key, "value");
-
- foreach (var de in dictionary)
- {
- Assert.AreEqual(key, de.Key);
- Assert.AreEqual("value", de.Value);
- }
- }
-
- [Test, LuceneNetSpecific]
- public void Test_Collisions()
- {
- //Create 2 keys with same hashcode but that are not equal
- CollisionTester key1 = new CollisionTester(1, 100);
- CollisionTester key2 = new CollisionTester(2, 100);
-
- dictionary.Add(key1, "value1");
- dictionary.Add(key2, "value2");
-
- Assert.AreEqual("value1", dictionary[key1]);
- Assert.AreEqual("value2", dictionary[key2]);
-
- dictionary.Remove(key1);
- Assert.IsFalse(dictionary.TryGetValue(key1, out _));
- Assert.Throws<KeyNotFoundException>(() => { var x =
dictionary[key1]; });
- }
-
- [Test, LuceneNetSpecific]
- public void Test_Weak_1()
- {
- BigObject key = new BigObject(1);
- BigObject key2 = new BigObject(2);
-
- dictionary.Add(key, "value");
- Assert.AreEqual("value", dictionary[key]);
-
- key = null;
- CallGC();
-
- dictionary.Add(key2, "value2");
- Assert.AreEqual(1, dictionary.Count);
- }
-
- [Test, LuceneNetSpecific]
- public void Test_Weak_2()
- {
- BigObject key = new BigObject(1);
- BigObject key2 = new BigObject(2);
- BigObject key3 = new BigObject(3);
-
- dictionary.Add(key, "value");
- dictionary.Add(key2, "value2");
- Assert.AreEqual("value", dictionary[key]);
-
- key = null;
- CallGC();
-
- dictionary.Add(key3, "value3");
-
- Assert.AreEqual(2, dictionary.Count);
- Assert.IsNotNull(key2);
- }
-
- [Test, LuceneNetSpecific]
- [Slow]
- public void Test_Weak_ForEach()
- {
- BigObject[] keys1 = new BigObject[20];
- BigObject[] keys2 = new BigObject[20];
-
- for (int i = 0; i < keys1.Length; i++)
- {
- keys1[i] = new BigObject(i);
- dictionary.Add(keys1[i], "value");
- }
- for (int i = 0; i < keys2.Length; i++)
- {
- keys2[i] = new BigObject(i);
- dictionary.Add(keys2[i], "value");
- }
-
- Assert.AreEqual(40, dictionary.Count);
-
- keys2 = null;
- int count = 0;
- foreach (var de in dictionary)
- {
- CallGC();
- count++;
- }
-
- Assert.LessOrEqual(20, count);
- Assert.Greater(40, count);
- Assert.IsNotNull(keys1);
- }
- }
-}
-#endif
\ No newline at end of file
diff --git a/src/Lucene.Net.Tests/Support/TestWeakDictionaryPerformance.cs
b/src/Lucene.Net.Tests/Support/TestWeakDictionaryPerformance.cs
deleted file mode 100644
index 68e1827..0000000
--- a/src/Lucene.Net.Tests/Support/TestWeakDictionaryPerformance.cs
+++ /dev/null
@@ -1,133 +0,0 @@
-#if !FEATURE_CONDITIONALWEAKTABLE_ENUMERATOR
-using Lucene.Net.Attributes;
-using NUnit.Framework;
-using System.Collections.Generic;
-
-namespace Lucene.Net.Support
-{
- /*
- * 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 TestWeakDictionaryPerformance
- {
- IDictionary<object, object> dictionary;
- SmallObject[] keys;
-
-
- [SetUp]
- public void Setup()
- {
- dictionary = TestWeakDictionaryBehavior.CreateDictionary();
- }
-
- private void Fill(IDictionary<object, object> dictionary)
- {
- foreach (SmallObject key in keys)
- dictionary.Add(key, "value");
- }
-
- [OneTimeSetUp]
- public void TestSetup()
- {
- keys = new SmallObject[100000];
- for (int i = 0; i < keys.Length; i++)
- keys[i] = new SmallObject(i);
- }
-
- [Test, LuceneNetSpecific]
- public void Test_Performance_Add()
- {
- for (int i = 0; i < 10; i++)
- {
- dictionary.Clear();
- Fill(dictionary);
- }
- }
-
- [Test, LuceneNetSpecific]
- public void Test_Performance_Remove()
- {
- for (int i = 0; i < 10; i++)
- {
- Fill(dictionary);
- foreach (SmallObject key in keys)
- dictionary.Remove(key);
- }
- }
-
- [Test, LuceneNetSpecific]
- public void Test_Performance_Replace()
- {
- for (int i = 0; i < 10; i++)
- {
- foreach (SmallObject key in keys)
- dictionary[key] = "value2";
- }
- }
-
- [Test, LuceneNetSpecific]
- public void Test_Performance_Access()
- {
- Fill(dictionary);
- for (int i = 0; i < 10; i++)
- {
- foreach (SmallObject key in keys)
- {
- object value = dictionary[key];
- }
- }
- }
-
- [Test, LuceneNetSpecific]
- public void Test_Performance_Contains()
- {
- Fill(dictionary);
- for (int i = 0; i < 10; i++)
- {
- foreach (SmallObject key in keys)
- {
- dictionary.ContainsKey(key);
- }
- }
- }
-
- [Test, LuceneNetSpecific]
- public void Test_Performance_Keys()
- {
- Fill(dictionary);
- for (int i = 0; i < 100; i++)
- {
- var keys = dictionary.Keys;
- }
- }
-
- [Test, LuceneNetSpecific]
- public void Test_Performance_ForEach()
- {
- Fill(dictionary);
- for (int i = 0; i < 10; i++)
- {
- foreach (var de in dictionary)
- {
-
- }
- }
- }
- }
-}
-#endif
\ No newline at end of file
diff --git a/src/Lucene.Net/Index/IndexReader.cs
b/src/Lucene.Net/Index/IndexReader.cs
index 77777ec..1dfaeee 100644
--- a/src/Lucene.Net/Index/IndexReader.cs
+++ b/src/Lucene.Net/Index/IndexReader.cs
@@ -3,6 +3,9 @@ using Lucene.Net.Documents;
using Lucene.Net.Support;
using Lucene.Net.Support.Threading;
using Lucene.Net.Util;
+#if !FEATURE_CONDITIONALWEAKTABLE_ENUMERATOR
+using Prism.Events;
+#endif
using System;
using System.Collections;
using System.Collections.Generic;
@@ -86,6 +89,11 @@ namespace Lucene.Net.Index
}
}
+#if !FEATURE_CONDITIONALWEAKTABLE_ENUMERATOR
+ // LUCENENET specific: Add weak event handler for .NET Standard 2.0
and .NET Framework, since we don't have an enumerator to use
+ private readonly IEventAggregator eventAggregator = new
EventAggregator();
+#endif
+
/// <summary>
/// A custom listener that's invoked when the <see cref="IndexReader"/>
/// is closed.
@@ -101,12 +109,9 @@ namespace Lucene.Net.Index
private readonly ISet<IReaderClosedListener> readerClosedListeners =
new JCG.LinkedHashSet<IReaderClosedListener>().AsConcurrent();
-#if FEATURE_CONDITIONALWEAKTABLE_ENUMERATOR
private readonly ConditionalWeakTable<IndexReader, object>
parentReaders = new ConditionalWeakTable<IndexReader, object>();
-#else
- private readonly WeakDictionary<IndexReader, object> parentReaders =
new WeakDictionary<IndexReader, object>();
-#endif
- // LUCENENET specific - since neither WeakDictionary nor
ConditionalWeakTable synchronize
+
+ // LUCENENET specific - since ConditionalWeakTable doesn't synchronize
// on the enumerator, we need to do external synchronization to make
them threadsafe.
private readonly object parentReadersLock = new object();
@@ -144,16 +149,24 @@ namespace Lucene.Net.Index
public void RegisterParentReader(IndexReader reader)
{
EnsureOpen();
- // LUCENENET specific - since neither WeakDictionary nor
ConditionalWeakTable synchronize
+ // LUCENENET specific - since ConditionalWeakTable doesn't
synchronize
// on the enumerator, we need to do external synchronization to
make them threadsafe.
UninterruptableMonitor.Enter(parentReadersLock);
try
{
+#if FEATURE_CONDITIONALWEAKTABLE_ENUMERATOR
// LUCENENET: Since there is a set Add operation (unique) in
Lucene, the equivalent
// operation in .NET is AddOrUpdate, which effectively does
nothing if the key exists.
// Null is passed as a value, since it is not used anyway and
.NET doesn't have a boolean
// reference type.
parentReaders.AddOrUpdate(key: reader, value: null);
+#else
+ if (!parentReaders.TryGetValue(key: reader, out _))
+ {
+ parentReaders.Add(key: reader, value: null);
+
reader.SubscribeToGetParentReadersEvent(eventAggregator.GetEvent<Events.GetParentReadersEvent>());
+ }
+#endif
}
finally
{
@@ -195,15 +208,21 @@ namespace Lucene.Net.Index
private void ReportCloseToParentReaders()
{
- // LUCENENET specific - since neither WeakDictionary nor
ConditionalWeakTable synchronize
+ // LUCENENET specific - since ConditionalWeakTable doesn't
synchronize
// on the enumerator, we need to do external synchronization to
make them threadsafe.
UninterruptableMonitor.Enter(parentReadersLock);
try
{
+#if FEATURE_CONDITIONALWEAKTABLE_ENUMERATOR
foreach (var kvp in parentReaders)
{
IndexReader target = kvp.Key;
-
+#else
+ var e = new Events.GetParentReadersEventArgs();
+
eventAggregator.GetEvent<Events.GetParentReadersEvent>().Publish(e);
+ foreach (var target in e.ParentReaders)
+ {
+#endif
// LUCENENET: This probably can't happen, but we are being
defensive to avoid exceptions
if (target != null)
{
@@ -602,12 +621,65 @@ namespace Lucene.Net.Index
UninterruptableMonitor.Exit(this);
}
}
+
+#if !FEATURE_CONDITIONALWEAKTABLE_ENUMERATOR
+ // LUCENENET specific - since .NET Standard 2.0 and .NET Framework
don't have a CondtionalWeakTable enumerator,
+ // we use a weak event to retrieve the ConditionalWeakTable items
+ foreach (var getParentReadersEvent in getParentReadersEvents)
+ getParentReadersEvent.Unsubscribe(OnGetParentReaders);
+ getParentReadersEvents.Clear();
+
+ foreach (var getCacheKeysEvent in getCacheKeysEvents)
+ getCacheKeysEvent.Unsubscribe(OnGetCacheKeys);
+ getCacheKeysEvents.Clear();
+#endif
}
/// <summary>
/// Implements close. </summary>
protected internal abstract void DoClose();
+#if !FEATURE_CONDITIONALWEAKTABLE_ENUMERATOR
+ // LUCENENET specific - since .NET Standard 2.0 and .NET Framework
don't have a CondtionalWeakTable enumerator,
+ // we use a weak event to retrieve the ConditionalWeakTable items
+ [ExcludeFromRamUsageEstimation]
+ private readonly ISet<Events.GetParentReadersEvent>
getParentReadersEvents = new JCG.HashSet<Events.GetParentReadersEvent>();
+ [ExcludeFromRamUsageEstimation]
+ private readonly ISet<Events.GetCacheKeysEvent> getCacheKeysEvents =
new JCG.HashSet<Events.GetCacheKeysEvent>();
+ internal void
SubscribeToGetParentReadersEvent(Events.GetParentReadersEvent
getParentReadersEvent)
+ {
+ if (getParentReadersEvent is null)
+ throw new ArgumentNullException(nameof(getParentReadersEvent));
+ if (getParentReadersEvents.Add(getParentReadersEvent))
+ getParentReadersEvent.Subscribe(OnGetParentReaders);
+ }
+
+ internal void SubscribeToGetCacheKeysEvent(Events.GetCacheKeysEvent
getCacheKeysEvent)
+ {
+ if (getCacheKeysEvent is null)
+ throw new ArgumentNullException(nameof(getCacheKeysEvent));
+ if (getCacheKeysEvents.Add(getCacheKeysEvent))
+ getCacheKeysEvent.Subscribe(OnGetCacheKeys);
+ }
+
+ // LUCENENET specific: Clean up the weak event handler if this class
goes out of scope
+ ~IndexReader()
+ {
+ Dispose(false);
+ }
+
+ // LUCENENET specific: Add weak event handler for .NET Standard 2.0
and .NET Framework, since we don't have an enumerator to use
+ private void OnGetParentReaders(Events.GetParentReadersEventArgs e)
+ {
+ e.ParentReaders.Add(this);
+ }
+
+ private void OnGetCacheKeys(Events.GetCacheKeysEventArgs e)
+ {
+ e.CacheKeys.Add(this.CoreCacheKey);
+ }
+#endif
+
/// <summary>
/// Expert: Returns the root <see cref="IndexReaderContext"/> for this
/// <see cref="IndexReader"/>'s sub-reader tree.
@@ -638,7 +710,7 @@ namespace Lucene.Net.Index
/// <summary>
/// Expert: Returns a key for this <see cref="IndexReader"/>, so
- /// <see cref="Search.IFieldCache"/>/<see
cref="Search.CachingWrapperFilter"/> can find
+ /// <see cref="Search.FieldCache"/>/<see
cref="Search.CachingWrapperFilter"/> can find
/// it again.
/// This key must not have Equals()/GetHashCode() methods,
/// so "equals" means "identical".
diff --git a/src/Lucene.Net/Lucene.Net.csproj b/src/Lucene.Net/Lucene.Net.csproj
index 09d76cb..cd9dc3f 100644
--- a/src/Lucene.Net/Lucene.Net.csproj
+++ b/src/Lucene.Net/Lucene.Net.csproj
@@ -43,7 +43,7 @@
</PropertyGroup>
<ItemGroup Label="NuGet Package Files">
- <None Include="readme-nuget.md" Pack="true" PackagePath="\readme.md"/>
+ <None Include="readme-nuget.md" Pack="true" PackagePath="\readme.md" />
<None Include="$(LuceneNetCodeAnalysisToolsDir)*.ps1" Pack="true"
PackagePath="tools" />
<None Include="$(LuceneNetCodeAnalysisCSAssemblyFile)" Pack="true"
PackagePath="analyzers/dotnet/cs" Visible="false" />
<None Include="$(LuceneNetCodeAnalysisVBAssemblyFile)" Pack="true"
PackagePath="analyzers/dotnet/vb" Visible="false" />
@@ -63,10 +63,12 @@
<ItemGroup Condition=" '$(TargetFramework)' == 'netstandard2.0' ">
<PackageReference
Include="Microsoft.Extensions.Configuration.Abstractions"
Version="$(MicrosoftExtensionsConfigurationAbstractionsPackageVersion)" />
+ <PackageReference Include="Prism.Core"
Version="$(PrismCorePackageVersion)" />
</ItemGroup>
<ItemGroup Condition=" '$(TargetFramework)' == 'net45' ">
<PackageReference
Include="Microsoft.Extensions.Configuration.Abstractions"
Version="$(MicrosoftExtensionsConfigurationAbstractionsPackageVersion)" />
+ <PackageReference Include="Prism.Core"
Version="$(PrismCorePackageVersion)" />
</ItemGroup>
<ItemGroup>
diff --git a/src/Lucene.Net/Search/CachingWrapperFilter.cs
b/src/Lucene.Net/Search/CachingWrapperFilter.cs
index b338430..eeacbae 100644
--- a/src/Lucene.Net/Search/CachingWrapperFilter.cs
+++ b/src/Lucene.Net/Search/CachingWrapperFilter.cs
@@ -1,6 +1,10 @@
using Lucene.Net.Diagnostics;
-using Lucene.Net.Support;
+using Lucene.Net.Runtime.CompilerServices;
using Lucene.Net.Support.Threading;
+using Lucene.Net.Util;
+#if !FEATURE_CONDITIONALWEAKTABLE_ENUMERATOR
+using Prism.Events;
+#endif
using System.Collections.Generic;
using System.Runtime.CompilerServices;
using JCG = J2N.Collections.Generic;
@@ -37,26 +41,26 @@ namespace Lucene.Net.Search
/// </summary>
public class CachingWrapperFilter : Filter
{
- private readonly Filter _filter;
+ private readonly Filter filter;
-#if FEATURE_CONDITIONALWEAKTABLE_ADDORUPDATE
- private readonly ConditionalWeakTable<object, DocIdSet> _cache = new
ConditionalWeakTable<object, DocIdSet>();
-#else
- private readonly IDictionary<object, DocIdSet> _cache = new
WeakDictionary<object, DocIdSet>().AsConcurrent();
+#if !FEATURE_CONDITIONALWEAKTABLE_ENUMERATOR
+ // LUCENENET specific: Add weak event handler for .NET Standard 2.0
and .NET Framework, since we don't have an enumerator to use
+ private readonly IEventAggregator eventAggregator = new
EventAggregator();
#endif
+ private readonly ConditionalWeakTable<object, DocIdSet> cache = new
ConditionalWeakTable<object, DocIdSet>();
/// <summary>
/// Wraps another filter's result and caches it. </summary>
/// <param name="filter"> Filter to cache results of </param>
public CachingWrapperFilter(Filter filter)
{
- this._filter = filter;
+ this.filter = filter;
}
/// <summary>
/// Gets the contained filter. </summary>
/// <returns> the contained filter. </returns>
- public virtual Filter Filter => _filter;
+ public virtual Filter Filter => filter;
/// <summary>
/// Provide the <see cref="DocIdSet"/> to be cached, using the <see
cref="DocIdSet"/> provided
@@ -114,19 +118,30 @@ namespace Lucene.Net.Search
var reader = context.AtomicReader;
object key = reader.CoreCacheKey;
- if (_cache.TryGetValue(key, out DocIdSet docIdSet) && docIdSet !=
null)
+ if (cache.TryGetValue(key, out DocIdSet docIdSet))
{
hitCount++;
}
else
{
missCount++;
- docIdSet = DocIdSetToCache(_filter.GetDocIdSet(context, null),
reader);
+ docIdSet = DocIdSetToCache(filter.GetDocIdSet(context, null),
reader);
if (Debugging.AssertsEnabled)
Debugging.Assert(docIdSet.IsCacheable);
-#if FEATURE_CONDITIONALWEAKTABLE_ADDORUPDATE
- _cache.AddOrUpdate(key, docIdSet);
+#if FEATURE_CONDITIONALWEAKTABLE_ENUMERATOR
+ cache.AddOrUpdate(key, docIdSet);
#else
- _cache[key] = docIdSet;
+ UninterruptableMonitor.Enter(cache);
+ try
+ {
+ cache.AddOrUpdate(key, docIdSet);
+ // LUCENENET specific - since .NET Standard 2.0 and .NET
Framework don't have a CondtionalWeakTable enumerator,
+ // we use a weak event to retrieve the DocIdSet instances
+
reader.SubscribeToGetCacheKeysEvent(eventAggregator.GetEvent<Events.GetCacheKeysEvent>());
+ }
+ finally
+ {
+ UninterruptableMonitor.Exit(cache);
+ }
#endif
}
@@ -135,19 +150,19 @@ namespace Lucene.Net.Search
public override string ToString()
{
- return this.GetType().Name + "(" + _filter + ")";
+ return this.GetType().Name + "(" + filter + ")";
}
public override bool Equals(object o)
{
if (o is null) return false;
if (!(o is CachingWrapperFilter other)) return false;
- return _filter.Equals(other._filter);
+ return filter.Equals(other.filter);
}
public override int GetHashCode()
{
- return (_filter.GetHashCode() ^ this.GetType().GetHashCode());
+ return (filter.GetHashCode() ^ this.GetType().GetHashCode());
}
/// <summary>
@@ -173,20 +188,28 @@ namespace Lucene.Net.Search
{
// Sync only to pull the current set of values:
IList<DocIdSet> docIdSets;
- UninterruptableMonitor.Enter(_cache);
+ UninterruptableMonitor.Enter(cache);
try
{
-#if FEATURE_CONDITIONALWEAKTABLE_ENUMERATOR
docIdSets = new JCG.List<DocIdSet>();
- foreach (var pair in _cache)
+#if FEATURE_CONDITIONALWEAKTABLE_ENUMERATOR
+ foreach (var pair in cache)
docIdSets.Add(pair.Value);
#else
- docIdSets = new JCG.List<DocIdSet>(_cache.Values);
+ // LUCENENET specific - since .NET Standard 2.0 and .NET
Framework don't have a CondtionalWeakTable enumerator,
+ // we use a weak event to retrieve the DocIdSet instances. We
look each of these up here to avoid the need
+ // to attach events to the DocIdSet instances themselves (thus
using the existing IndexReader.Dispose()
+ // method to detach the events rather than using a finalizer
in DocIdSet to ensure they are cleaned up).
+ var e = new Events.GetCacheKeysEventArgs();
+
eventAggregator.GetEvent<Events.GetCacheKeysEvent>().Publish(e);
+ foreach (var key in e.CacheKeys)
+ if (cache.TryGetValue(key, out DocIdSet value))
+ docIdSets.Add(value);
#endif
}
finally
{
- UninterruptableMonitor.Exit(_cache);
+ UninterruptableMonitor.Exit(cache);
}
long total = 0;
diff --git a/src/Lucene.Net/Search/DocIdSet.cs
b/src/Lucene.Net/Search/DocIdSet.cs
index 9c78d24..418fab4 100644
--- a/src/Lucene.Net/Search/DocIdSet.cs
+++ b/src/Lucene.Net/Search/DocIdSet.cs
@@ -1,4 +1,4 @@
-using System;
+using System;
using System.IO;
namespace Lucene.Net.Search
diff --git a/src/Lucene.Net/Search/FieldCacheImpl.cs
b/src/Lucene.Net/Search/FieldCacheImpl.cs
index 0628aba..cc86ee1 100644
--- a/src/Lucene.Net/Search/FieldCacheImpl.cs
+++ b/src/Lucene.Net/Search/FieldCacheImpl.cs
@@ -1,9 +1,14 @@
using J2N.Collections.Generic.Extensions;
using Lucene.Net.Diagnostics;
using Lucene.Net.Index;
+using Lucene.Net.Runtime.CompilerServices;
using Lucene.Net.Support;
using Lucene.Net.Support.IO;
using Lucene.Net.Support.Threading;
+using Lucene.Net.Util;
+#if !FEATURE_CONDITIONALWEAKTABLE_ENUMERATOR
+using Prism.Events;
+#endif
using System;
using System.Collections.Generic;
using System.IO;
@@ -163,6 +168,7 @@ namespace Lucene.Net.Search
UninterruptableMonitor.Enter(cache.readerCache);
try
{
+#if FEATURE_CONDITIONALWEAKTABLE_ADDORUPDATE
foreach (var readerCacheEntry in cache.readerCache)
{
object readerKey = readerCacheEntry.Key;
@@ -177,6 +183,23 @@ namespace Lucene.Net.Search
result.Add(new FieldCache.CacheEntry(readerKey,
entry.field, cacheType, entry.Custom, mapEntry.Value));
}
}
+#else
+ // LUCENENET specific - since .NET Standard 2.0 and .NET
Framework don't have a CondtionalWeakTable enumerator,
+ // we use a weak event to retrieve the readerKey instances and
then lookup the values in the table one by one.
+ var e = new Events.GetCacheKeysEventArgs();
+
eventAggregator.GetEvent<Events.GetCacheKeysEvent>().Publish(e);
+ foreach (object readerKey in e.CacheKeys)
+ {
+ if (cache.readerCache.TryGetValue(readerKey, out
IDictionary<TKey, object> innerCache))
+ {
+ foreach (KeyValuePair<TKey, object> mapEntry in
innerCache)
+ {
+ TKey entry = mapEntry.Key;
+ result.Add(new FieldCache.CacheEntry(readerKey,
entry.field, cacheType, entry.Custom, mapEntry.Value));
+ }
+ }
+ }
+#endif
}
finally
{
@@ -242,8 +265,18 @@ namespace Lucene.Net.Search
reader.AddReaderClosedListener(purgeReader);
}
}
+#if !FEATURE_CONDITIONALWEAKTABLE_ENUMERATOR
+ // LUCENENET specific - since .NET Standard 2.0 and .NET Framework
don't have a CondtionalWeakTable enumerator,
+ // we use a weak event to retrieve the readerKey instances
+
reader.SubscribeToGetCacheKeysEvent(eventAggregator.GetEvent<Events.GetCacheKeysEvent>());
+#endif
}
+#if !FEATURE_CONDITIONALWEAKTABLE_ENUMERATOR
+ // LUCENENET specific: Add weak event handler for .NET Standard 2.0
and .NET Framework, since we don't have an enumerator to use
+ private readonly IEventAggregator eventAggregator = new
EventAggregator();
+#endif
+
/// <summary>
/// Expert: Internal cache. </summary>
internal abstract class Cache<TKey, TValue> where TKey : CacheKey
@@ -255,11 +288,7 @@ namespace Lucene.Net.Search
internal readonly FieldCacheImpl wrapper;
-#if FEATURE_CONDITIONALWEAKTABLE_ENUMERATOR
internal ConditionalWeakTable<object, IDictionary<TKey, object>>
readerCache = new ConditionalWeakTable<object, IDictionary<TKey, object>>();
-#else
- internal WeakDictionary<object, IDictionary<TKey, object>>
readerCache = new WeakDictionary<object, IDictionary<TKey, object>>();
-#endif
protected abstract TValue CreateValue(AtomicReader reader, TKey
key, bool setDocsWithField);
diff --git
a/src/Lucene.Net/Support/Runtime/CompilerServices/ConditionalWeakTableExtensions.cs
b/src/Lucene.Net/Support/Runtime/CompilerServices/ConditionalWeakTableExtensions.cs
new file mode 100644
index 0000000..c629d32
--- /dev/null
+++
b/src/Lucene.Net/Support/Runtime/CompilerServices/ConditionalWeakTableExtensions.cs
@@ -0,0 +1,53 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Runtime.CompilerServices;
+using System.Text;
+using System.Threading.Tasks;
+
+namespace Lucene.Net.Runtime.CompilerServices
+{
+ /*
+ * 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.
+ */
+
+ internal static class ConditionalWeakTableExtensions
+ {
+#if !FEATURE_CONDITIONALWEAKTABLE_ADDORUPDATE
+ /// <summary>
+ /// AddOrUpdate-like patch for .NET Standard 2.0 and .NET Framework.
Note this method is not threadsafe,
+ /// so will require external locking to synchronize with other <see
cref="ConditionalWeakTable{TKey, TValue}"/> operations.
+ /// </summary>
+ /// <typeparam name="TKey">The key type.</typeparam>
+ /// <typeparam name="TValue">The value type.</typeparam>
+ /// <param name="table">This <see cref="ConditionalWeakTable{TKey,
TValue}"/>.</param>
+ /// <param name="key">The key to add or update. May not be
<c>null</c>.</param>
+ /// <param name="value">The value to associate with key.</param>
+ /// <exception cref="ArgumentNullException"><paramref name="table"/>
or <paramref name="key"/> is <c>null</c>.</exception>
+ public static void AddOrUpdate<TKey, TValue>(this
ConditionalWeakTable<TKey, TValue> table, TKey key, TValue value)
+ where TKey: class
+ where TValue: class
+ {
+ if (table is null)
+ throw new ArgumentNullException(nameof(table));
+
+ if (table.TryGetValue(key, out _))
+ table.Remove(key);
+ table.Add(key, value);
+ }
+#endif
+ }
+}
diff --git a/src/Lucene.Net/Support/Util/Events.cs
b/src/Lucene.Net/Support/Util/Events.cs
new file mode 100644
index 0000000..3d1203c
--- /dev/null
+++ b/src/Lucene.Net/Support/Util/Events.cs
@@ -0,0 +1,62 @@
+#if !FEATURE_CONDITIONALWEAKTABLE_ENUMERATOR
+using Lucene.Net.Index;
+using Prism.Events;
+using System.Collections.Generic;
+using System.Runtime.CompilerServices;
+
+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>
+ /// Events are used in Lucene.NET to work around the fact that <see
cref="System.Runtime.CompilerServices.ConditionalWeakTable{TKey, TValue}"/>
+ /// doesn't have an enumerator in .NET Framework or .NET Standard prior to
2.1. They are declared in this static class to avoid adding coupling.
+ /// </summary>
+ internal static class Events
+ {
+ #region GetParentReaders
+
+ public class GetParentReadersEventArgs
+ {
+ public IList<IndexReader> ParentReaders { get; } = new
List<IndexReader>();
+ }
+
+ /// <summary>
+ /// Gets strong references to the parent readers of an <see
cref="IndexReader"/>
+ /// from a <see cref="ConditionalWeakTable{TKey, TValue}"/>.
+ /// </summary>
+ public class GetParentReadersEvent :
PubSubEvent<GetParentReadersEventArgs> { }
+
+ #endregion GetParentReaders
+
+ #region GetCacheKeys
+
+ public class GetCacheKeysEventArgs
+ {
+ public IList<object> CacheKeys { get; } = new List<object>();
+ }
+
+ /// <summary>
+ /// Gets strong references to the cache keys in a <see
cref="ConditionalWeakTable{TKey, TValue}"/>.
+ /// </summary>
+ public class GetCacheKeysEvent : PubSubEvent<GetCacheKeysEventArgs> { }
+
+ #endregion GetCacheKeys
+ }
+}
+#endif
\ No newline at end of file
diff --git
a/src/Lucene.Net/Support/Util/ExcludeFromRamUsageEstimationAttribute.cs
b/src/Lucene.Net/Support/Util/ExcludeFromRamUsageEstimationAttribute.cs
new file mode 100644
index 0000000..aee34c0
--- /dev/null
+++ b/src/Lucene.Net/Support/Util/ExcludeFromRamUsageEstimationAttribute.cs
@@ -0,0 +1,29 @@
+using System;
+
+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>
+ /// Marks a field exempt from the <see
cref="RamUsageEstimator.SizeOf(object)"/> calculation.
+ /// </summary>
+ [AttributeUsage(AttributeTargets.Field, AllowMultiple = false)]
+ public class ExcludeFromRamUsageEstimationAttribute : System.Attribute
+ {
+ }
+}
diff --git a/src/Lucene.Net/Support/WeakDictionary.cs
b/src/Lucene.Net/Support/WeakDictionary.cs
deleted file mode 100644
index 1633499..0000000
--- a/src/Lucene.Net/Support/WeakDictionary.cs
+++ /dev/null
@@ -1,300 +0,0 @@
-/*
- *
- * 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.
- *
-*/
-
-#if !FEATURE_CONDITIONALWEAKTABLE_ENUMERATOR
-using System;
-using System.Collections;
-using System.Collections.Generic;
-using JCG = J2N.Collections.Generic;
-
-namespace Lucene.Net.Support
-{
-#if FEATURE_SERIALIZABLE
- [Serializable]
-#endif
- internal sealed class WeakDictionary<TKey, TValue> : IDictionary<TKey,
TValue> where TKey : class
- {
- private IDictionary<WeakKey<TKey>, TValue> _hm;
- private int _gcCollections = 0;
-
- public WeakDictionary(int initialCapacity)
- : this(initialCapacity, Arrays.Empty<KeyValuePair<TKey, TValue>>())
- { }
-
- public WeakDictionary()
- : this(32, Arrays.Empty<KeyValuePair<TKey, TValue>>())
- { }
-
- public WeakDictionary(IEnumerable<KeyValuePair<TKey, TValue>>
otherDictionary)
- : this(32, otherDictionary)
- { }
-
- private WeakDictionary(int initialCapacity,
IEnumerable<KeyValuePair<TKey, TValue>> otherDict)
- {
- _hm = new JCG.Dictionary<WeakKey<TKey>, TValue>(initialCapacity);
- foreach (var kvp in otherDict)
- {
- _hm.Add(new WeakKey<TKey>(kvp.Key), kvp.Value);
- }
- }
-
- // LUCENENET NOTE: Added AddOrUpdate method so we don't need so many
conditional compilation blocks.
- // This is just to cascade the call to this[key] = value
- public void AddOrUpdate(TKey key, TValue value) => this[key] = value;
-
- private void Clean()
- {
- if (_hm.Count == 0) return;
- var newHm = new JCG.Dictionary<WeakKey<TKey>, TValue>(_hm.Count);
- foreach (var kvp in _hm)
- {
- if (kvp.Key.TryGetTarget(out TKey _))
- {
- // LUCENENET: There is a tiny chance that a call to remove
the item
- // from the dictionary can happen before this line is
executed. Therefore,
- // just discard the reference and add it as is, even if it
is no longer valid
- // in this edge case. It is far more efficient to re-use
the same instances, anyway.
- newHm.Add(kvp.Key, kvp.Value);
- }
- }
- _hm = newHm;
- }
-
- private void CleanIfNeeded()
- {
- int currentColCount = GC.CollectionCount(0);
- if (currentColCount > _gcCollections)
- {
- Clean();
- _gcCollections = currentColCount;
- }
- }
-
- public IEnumerator<KeyValuePair<TKey, TValue>> GetEnumerator()
- {
- foreach (var kvp in _hm)
- {
- if (kvp.Key.TryGetTarget(out TKey key))
- {
- yield return new KeyValuePair<TKey, TValue>(key,
kvp.Value);
- }
- }
- }
-
- IEnumerator IEnumerable.GetEnumerator()
- {
- return GetEnumerator();
- }
-
- void ICollection<KeyValuePair<TKey, TValue>>.Add(KeyValuePair<TKey,
TValue> item)
- {
- CleanIfNeeded();
- ((ICollection<KeyValuePair<WeakKey<TKey>, TValue>>)_hm).Add(
- new KeyValuePair<WeakKey<TKey>, TValue>(new
WeakKey<TKey>(item.Key), item.Value));
- }
-
- public void Clear()
- {
- _hm.Clear();
- }
-
- bool ICollection<KeyValuePair<TKey,
TValue>>.Contains(KeyValuePair<TKey, TValue> item)
- {
- return ((ICollection<KeyValuePair<WeakKey<TKey>,
TValue>>)_hm).Contains(
- new KeyValuePair<WeakKey<TKey>, TValue>(new
WeakKey<TKey>(item.Key), item.Value));
- }
-
- bool ICollection<KeyValuePair<TKey, TValue>>.Remove(KeyValuePair<TKey,
TValue> item)
- {
- return ((ICollection<KeyValuePair<WeakKey<TKey>,
TValue>>)_hm).Remove(
- new KeyValuePair<WeakKey<TKey>, TValue>(new
WeakKey<TKey>(item.Key), item.Value));
- }
-
- public int Count
- {
- get
- {
- CleanIfNeeded();
- return _hm.Count;
- }
- }
-
- public bool IsReadOnly => false;
-
- public bool ContainsKey(TKey key)
- {
- return _hm.ContainsKey(new WeakKey<TKey>(key));
- }
-
- public void Add(TKey key, TValue value)
- {
- CleanIfNeeded();
- _hm.Add(new WeakKey<TKey>(key), value);
- }
-
- public bool Remove(TKey key)
- {
- return _hm.Remove(new WeakKey<TKey>(key));
- }
-
- public bool TryGetValue(TKey key, out TValue value)
- {
- return _hm.TryGetValue(new WeakKey<TKey>(key), out value);
- }
-
- public TValue this[TKey key]
- {
- get => _hm[new WeakKey<TKey>(key)];
- set
- {
- CleanIfNeeded();
- _hm[new WeakKey<TKey>(key)] = value;
- }
- }
-
- public ICollection<TKey> Keys
- {
- get
- {
- CleanIfNeeded();
- return new KeyCollection(_hm);
- }
- }
-
- public ICollection<TValue> Values
- {
- get
- {
- CleanIfNeeded();
- return _hm.Values;
- }
- }
-
- void ICollection<KeyValuePair<TKey, TValue>>.CopyTo(KeyValuePair<TKey,
TValue>[] array, int arrayIndex)
- {
- throw new NotSupportedException();
- }
-
- #region Nested Class: KeyCollection
-
- private class KeyCollection : ICollection<TKey>
- {
- private readonly IDictionary<WeakKey<TKey>, TValue> _internalDict;
-
- public KeyCollection(IDictionary<WeakKey<TKey>, TValue> dict)
- {
- _internalDict = dict;
- }
-
- public IEnumerator<TKey> GetEnumerator()
- {
- foreach (var key in _internalDict.Keys)
- {
- if (key.TryGetTarget(out TKey target))
- yield return target;
- }
- }
-
- IEnumerator IEnumerable.GetEnumerator()
- {
- return GetEnumerator();
- }
-
- public void CopyTo(TKey[] array, int arrayIndex)
- {
- throw new NotImplementedException("Implement this as needed");
- }
-
- public int Count => _internalDict.Count + 1;
-
- public bool IsReadOnly => true;
-
- #region Explicit Interface Definitions
-
- bool ICollection<TKey>.Contains(TKey item)
- {
- throw new NotSupportedException();
- }
-
- void ICollection<TKey>.Add(TKey item)
- {
- throw new NotSupportedException();
- }
-
- void ICollection<TKey>.Clear()
- {
- throw new NotSupportedException();
- }
-
- bool ICollection<TKey>.Remove(TKey item)
- {
- throw new NotSupportedException();
- }
-
-#endregion Explicit Interface Definitions
- }
-
- #endregion Nested Class: KeyCollection
-
- /// <summary>
- /// A weak reference wrapper for the hashtable keys. Whenever a
key\value pair
- /// is added to the hashtable, the key is wrapped using a WeakKey.
WeakKey saves the
- /// value of the original object hashcode for fast comparison.
- /// </summary>
- private class WeakKey<T> where T : class
- {
- private readonly WeakReference<T> reference;
- private readonly int hashCode;
-
- public WeakKey(T key)
- {
- if (key is null)
- throw new ArgumentNullException(nameof(key));
-
- hashCode = key is null ? 0 : key.GetHashCode();
- reference = new WeakReference<T>(key);
- }
-
- public override int GetHashCode()
- {
- return hashCode;
- }
-
- public override bool Equals(object obj)
- {
- if (obj is null) return false;
- if (obj is WeakKey<T> other)
- {
- bool gotThis = this.TryGetTarget(out T thisTarget),
gotOther = other.TryGetTarget(out T otherTarget);
- if (gotThis && gotOther && thisTarget.Equals(otherTarget))
- return true;
- else if (gotThis == false && gotOther == false)
- return true;
- }
-
- return false;
- }
-
- public bool TryGetTarget(out T target) =>
reference.TryGetTarget(out target);
- }
- }
-}
-#endif
\ No newline at end of file
diff --git a/src/Lucene.Net/Util/RamUsageEstimator.cs
b/src/Lucene.Net/Util/RamUsageEstimator.cs
index e1b210a..4cdd125 100644
--- a/src/Lucene.Net/Util/RamUsageEstimator.cs
+++ b/src/Lucene.Net/Util/RamUsageEstimator.cs
@@ -627,7 +627,8 @@ namespace Lucene.Net.Util
BindingFlags.Static);
foreach (FieldInfo f in fields)
{
- if (!f.IsStatic)
+ // LUCENENET specific - exclude fields that are marked
with the ExcludeFromRamUsageEstimationAttribute
+ if (!f.IsStatic &&
f.GetCustomAttribute<ExcludeFromRamUsageEstimationAttribute>(inherit: false) is
null)
{
shallowInstanceSize =
AdjustForField(shallowInstanceSize, f);