This is an automated email from the ASF dual-hosted git repository.
paulirwin 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 d3e7bf95f BUG: Fix for DocumentsWriter concurrency (fixes #935, closes
#886) (#940)
d3e7bf95f is described below
commit d3e7bf95f60a894ef74859ff9054bbdd1de55b21
Author: Shad Storhaug <[email protected]>
AuthorDate: Tue Aug 13 23:31:58 2024 +0700
BUG: Fix for DocumentsWriter concurrency (fixes #935, closes #886) (#940)
* Lucene.Net.Index.DocumentsWriterFlushControl: Reverted changes from
963e10ca259570451f953331e4a9d62fabaffa32 that made the documents writer
non-concurrent (but threadsafe)
* BUG: Lucene.Net.Index.DocumentsWriterFlushControl::AddFlushableState():
The DocumentsWriterThreadPool.Reset() method must be called within the context
of a lock because there is a possible race condition with other callers of
DocumentsWriterThreadPool.Reset(). See #935.
* BUG: Lucene.Net.Support.Threading.ReentrantLock: In Java, the
ReentrantLock.tryLock() method barges to the front of the queue instead of
returning false like Monitor.TryEnter(). Use Monitor.Enter(object, ref bool)
instead, which always returns true. We get locks in a different order, but I am
not sure whether that matters. Fixes #935. Closes #886.
* Lucene.Net.Index.TestRollingUpdates::TestUpdateSameDoc(): Added
[Repeat(1000)] to try to reproduce on Azure DevOps (to be reverted).
* Lucene.Net.Index.DocumentsWriterPerThreadPool: Removed
MinContendedThreadState() method because it has no callers. This simplifies the
design of ReentrantLock, since we don't need to artificially keep track of
"queued threads".
* Lucene.Net.Support: Added aggressive inlining for ReentrantLock and
UninterruptableMonitor on cascaded methods.
* run-tests-on-os.yml: Increase blame hang timeout so we can run a longer
test (to be reverted)
* Lucene.Net.Support.Threading.ReentrantLock: Use TryEnter() instead of
Enter(). Enter() causes deadlocks in other tests, so need to localize this
change to DocumentsWriterFlushControl.
*
Lucene.Net.Index.DocumentsWriterFlushControl::InternalTryCheckoutForFlush():
Use ReentrantLock.Lock() instead of ReentrantLock.TryLock() because Java relies
on "barging" behavior instead of returning false when the current thread isn't
next in the queue. We cannot do that, but we can wait for the lock to become
available instead.
* Lucene.Net.Support.Threading.ReeentrantLock(): Added an overload of
TryLock() that accepts a timeout (TimeSpan)
* Lucene.Net.Index.DocumentsWriterFlushControl: Use timeouts to allow some
time for threads to reach the beginning of the wait queue. In Java, they are
automatically put at the beginning of the queue, but since we cannot do that in
.NET, we wait a little bit.
* Lucene.Net.Index.DocumentsWriterFlushControl: Base the number of
milliseconds to wait on whether the current process is 64 or 32 bit.
* Lucene.Net.Index::DocumentsWriter: Added a constant
TryLockTimeoutMilliseconds that is used by callers of ReentrantLock.TryLock()
to set the default value.
* Lucene.Net.Support: Added QueueExtensions class to polyfill the missing
TryDequeue() and TryPeek() methods on netstandard2.0 and .NET Framework
* SWEEP: Lucene.Net.Index: Removed timeouts for ReentrantLock.TryLock().
* Lucene.Net.Support.Threading.ReentrantLock: Changed the implementation to
use unfair locking similar to how it was done in Java. We track the queue and
use ManualResetEventSlim to control entry into the lock for queued tasks.
Ported some of the ReentrantLock tests from Apache Harmony.
* Revert "Lucene.Net.Support: Added QueueExtensions class to polyfill the
missing TryDequeue() and TryPeek() methods on netstandard2.0 and .NET Framework"
This reverts commit e5a65e9cd8bbf996fb599ff76e8d6b9f90babe4b.
* Lucene.Net.csproj: Removed dependency on Microsoft.Extensions.ObjectPool
* Lucene.Net.Support.Threading.ReentrantLock::Lock(): Use
UninterruptableMonitor.TryEnter() instead of UninterruptableMonitor.Enter() so
we can control what happens while the thread waits. We simply call
Thread.Yield() to allow TryLock() to proceed before any waiting threads.
Commented tests that depend on IsLocked because the property was removed.
* Revert "run-tests-on-os.yml: Increase blame hang timeout so we can run a
longer test (to be reverted)"
This reverts commit b30e4abb576c8bfde3337a92de927a10747f88ae.
* Revert "Lucene.Net.Index.TestRollingUpdates::TestUpdateSameDoc(): Added
[Repeat(1000)] to try to reproduce on Azure DevOps (to be reverted)."
This reverts commit d8fca410dafd1bf5529e8200034e1e2e5be83f07.
*
Lucene.Net.Support.Threading.ReentrantLockTest::TestUnlock_IllegalMonitorStateException()
Removed unused exception variable and added a comment to indicate success so
we don't have to suppress warnings
* Lucene.Net.Support.Threading.UninterruptableMonitor: Elminated
RetryEnter() recursive methods to avoid overflowing the stack and moved the
logic into the catch blocks. Also added documentation.
---
.../Support/Threading/JSR166TestCase.cs | 369 +++++
.../Support/Threading/ReentrantLockTest.cs | 1648 ++++++++++++++++++++
src/Lucene.Net/Index/DocumentsWriter.cs | 3 +-
src/Lucene.Net/Index/DocumentsWriterDeleteQueue.cs | 2 +-
.../Index/DocumentsWriterFlushControl.cs | 78 +-
src/Lucene.Net/Index/DocumentsWriterFlushQueue.cs | 2 +-
.../Index/DocumentsWriterPerThreadPool.cs | 23 +-
src/Lucene.Net/Support/Threading/ReentrantLock.cs | 97 +-
.../Support/Threading/UninterruptableMonitor.cs | 223 ++-
9 files changed, 2317 insertions(+), 128 deletions(-)
diff --git a/src/Lucene.Net.Tests/Support/Threading/JSR166TestCase.cs
b/src/Lucene.Net.Tests/Support/Threading/JSR166TestCase.cs
new file mode 100644
index 000000000..bdd136294
--- /dev/null
+++ b/src/Lucene.Net.Tests/Support/Threading/JSR166TestCase.cs
@@ -0,0 +1,369 @@
+using Lucene.Net.Util;
+using System;
+
+namespace Lucene.Net.Support.Threading
+{
+ /*
+ * 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.
+ */
+
+ /**
+ * Base class for JSR166 Junit TCK tests. Defines some constants,
+ * utility methods and classes, as well as a simple framework for
+ * helping to make sure that assertions failing in generated threads
+ * cause the associated test that generated them to itself fail (which
+ * JUnit does not otherwise arrange). The rules for creating such
+ * tests are:
+ *
+ * <ol>
+ *
+ * <li> All assertions in code running in generated threads must use
+ * the forms {@link #threadFail}, {@link #threadAssertTrue}, {@link
+ * #threadAssertEquals}, or {@link #threadAssertNull}, (not
+ * <tt>fail</tt>, <tt>assertTrue</tt>, etc.) It is OK (but not
+ * particularly recommended) for other code to use these forms too.
+ * Only the most typically used JUnit assertion methods are defined
+ * this way, but enough to live with.</li>
+ *
+ * <li> If you override {@link #setUp} or {@link #tearDown}, make sure
+ * to invoke <tt>super.setUp</tt> and <tt>super.tearDown</tt> within
+ * them. These methods are used to clear and check for thread
+ * assertion failures.</li>
+ *
+ * <li>All delays and timeouts must use one of the constants <tt>
+ * SHORT_DELAY_MS</tt>, <tt> SMALL_DELAY_MS</tt>, <tt>
MEDIUM_DELAY_MS</tt>,
+ * <tt> LONG_DELAY_MS</tt>. The idea here is that a SHORT is always
+ * discriminable from zero time, and always allows enough time for the
+ * small amounts of computation (creating a thread, calling a few
+ * methods, etc) needed to reach a timeout point. Similarly, a SMALL
+ * is always discriminable as larger than SHORT and smaller than
+ * MEDIUM. And so on. These constants are set to conservative values,
+ * but even so, if there is ever any doubt, they can all be increased
+ * in one spot to rerun tests on slower platforms.</li>
+ *
+ * <li> All threads generated must be joined inside each test case
+ * method (or <tt>fail</tt> to do so) before returning from the
+ * method. The <tt> joinPool</tt> method can be used to do this when
+ * using Executors.</li>
+ *
+ * </ol>
+ *
+ * <p> <b>Other notes</b>
+ * <ul>
+ *
+ * <li> Usually, there is one testcase method per JSR166 method
+ * covering "normal" operation, and then as many exception-testing
+ * methods as there are exceptions the method can throw. Sometimes
+ * there are multiple tests per JSR166 method when the different
+ * "normal" behaviors differ significantly. And sometimes testcases
+ * cover multiple methods when they cannot be tested in
+ * isolation.</li>
+ *
+ * <li> The documentation style for testcases is to provide as javadoc
+ * a simple sentence or two describing the property that the testcase
+ * method purports to test. The javadocs do not say anything about how
+ * the property is tested. To find out, read the code.</li>
+ *
+ * <li> These tests are "conformance tests", and do not attempt to
+ * test throughput, latency, scalability or other performance factors
+ * (see the separate "jtreg" tests for a set intended to check these
+ * for the most central aspects of functionality.) So, most tests use
+ * the smallest sensible numbers of threads, collection sizes, etc
+ * needed to check basic conformance.</li>
+ *
+ * <li>The test classes currently do not declare inclusion in
+ * any particular package to simplify things for people integrating
+ * them in TCK test suites.</li>
+ *
+ * <li> As a convenience, the <tt>main</tt> of this class (JSR166TestCase)
+ * runs all JSR166 unit tests.</li>
+ *
+ * </ul>
+ */
+ public class JSR166TestCase : LuceneTestCase
+ {
+ ///**
+ // * Runs all JSR166 unit tests using junit.textui.TestRunner
+ // */
+ //public static void main(String[] args)
+ //{
+ // int iters = 1;
+ // if (args.length > 0)
+ // iters = Integer.parseInt(args[0]);
+ // Test s = suite();
+ // for (int i = 0; i < iters; ++i)
+ // {
+ // junit.textui.TestRunner.run(s);
+ // System.gc();
+ // System.runFinalization();
+ // }
+ // System.exit(0);
+ //}
+
+ ///**
+ // * Collects all JSR166 unit tests as one suite
+ // */
+ //public static Test suite()
+ //{
+ // TestSuite suite = new TestSuite("JSR166 Unit Tests");
+
+ // suite.addTest(new TestSuite(AbstractExecutorServiceTest.class));
+ // suite.addTest(new TestSuite(AbstractQueueTest.class));
+ // suite.addTest(new
TestSuite(AbstractQueuedSynchronizerTest.class));
+ // suite.addTest(new TestSuite(ArrayBlockingQueueTest.class));
+ // suite.addTest(new TestSuite(AtomicBooleanTest.class));
+ // suite.addTest(new TestSuite(AtomicIntegerArrayTest.class));
+ // suite.addTest(new
TestSuite(AtomicIntegerFieldUpdaterTest.class));
+ // suite.addTest(new TestSuite(AtomicIntegerTest.class));
+ // suite.addTest(new TestSuite(AtomicLongArrayTest.class));
+ // suite.addTest(new TestSuite(AtomicLongFieldUpdaterTest.class));
+ // suite.addTest(new TestSuite(AtomicLongTest.class));
+ // suite.addTest(new TestSuite(AtomicMarkableReferenceTest.class));
+ // suite.addTest(new TestSuite(AtomicReferenceArrayTest.class));
+ // suite.addTest(new
TestSuite(AtomicReferenceFieldUpdaterTest.class));
+ // suite.addTest(new TestSuite(AtomicReferenceTest.class));
+ // suite.addTest(new TestSuite(AtomicStampedReferenceTest.class));
+ // suite.addTest(new TestSuite(ConcurrentHashMapTest.class));
+ // suite.addTest(new TestSuite(ConcurrentLinkedQueueTest.class));
+ // suite.addTest(new TestSuite(CopyOnWriteArrayListTest.class));
+ // suite.addTest(new TestSuite(CopyOnWriteArraySetTest.class));
+ // suite.addTest(new TestSuite(CountDownLatchTest.class));
+ // suite.addTest(new TestSuite(CyclicBarrierTest.class));
+ // suite.addTest(new TestSuite(DelayQueueTest.class));
+ // suite.addTest(new TestSuite(ExchangerTest.class));
+ // suite.addTest(new TestSuite(ExecutorsTest.class));
+ // suite.addTest(new
TestSuite(ExecutorCompletionServiceTest.class));
+ // suite.addTest(new TestSuite(FutureTaskTest.class));
+ // suite.addTest(new TestSuite(LinkedBlockingQueueTest.class));
+ // suite.addTest(new TestSuite(LinkedListTest.class));
+ // suite.addTest(new TestSuite(LockSupportTest.class));
+ // suite.addTest(new TestSuite(PriorityBlockingQueueTest.class));
+ // suite.addTest(new TestSuite(PriorityQueueTest.class));
+ // suite.addTest(new TestSuite(ReentrantLockTest.class));
+ // suite.addTest(new TestSuite(ReentrantReadWriteLockTest.class));
+ // suite.addTest(new TestSuite(ScheduledExecutorTest.class));
+ // suite.addTest(new TestSuite(SemaphoreTest.class));
+ // suite.addTest(new TestSuite(SynchronousQueueTest.class));
+ // suite.addTest(new TestSuite(SystemTest.class));
+ // suite.addTest(new TestSuite(ThreadLocalTest.class));
+ // suite.addTest(new TestSuite(ThreadPoolExecutorTest.class));
+ // suite.addTest(new TestSuite(ThreadTest.class));
+ // suite.addTest(new TestSuite(TimeUnitTest.class));
+
+ // return suite;
+ //}
+
+ public static int SHORT_DELAY_MS;
+ public static int SMALL_DELAY_MS;
+ public static int MEDIUM_DELAY_MS;
+ public static int LONG_DELAY_MS;
+
+ /**
+ * Returns the shortest timed delay. This could
+ * be reimplemented to use for example a Property.
+ */
+ protected int getShortDelay()
+ {
+ return 50;
+ }
+
+
+ /**
+ * Sets delays as multiples of SHORT_DELAY.
+ */
+ protected void setDelays()
+ {
+ SHORT_DELAY_MS = getShortDelay();
+ SMALL_DELAY_MS = SHORT_DELAY_MS * 5;
+ MEDIUM_DELAY_MS = SHORT_DELAY_MS * 10;
+ LONG_DELAY_MS = SHORT_DELAY_MS * 50;
+ }
+
+ /**
+ * Flag set true if any threadAssert methods fail
+ */
+ internal volatile bool threadFailed;
+
+ /**
+ * Initializes test to indicate that no thread assertions have failed
+ */
+ public override void SetUp()
+ {
+ base.SetUp();
+ setDelays();
+ threadFailed = false;
+ }
+
+ /**
+ * Triggers test case failure if any thread assertions have failed
+ */
+ public override void TearDown()
+ {
+ assertFalse(threadFailed);
+ base.TearDown();
+ }
+
+ /**
+ * Fail, also setting status to indicate current testcase should fail
+ */
+ public void threadFail(string reason)
+ {
+ threadFailed = true;
+ fail(reason);
+ }
+
+ /**
+ * If expression not true, set status to indicate current testcase
+ * should fail
+ */
+ public void threadAssertTrue(bool b)
+ {
+ if (!b)
+ {
+ threadFailed = true;
+ assertTrue(b);
+ }
+ }
+
+ /**
+ * If expression not false, set status to indicate current testcase
+ * should fail
+ */
+ public void threadAssertFalse(bool b)
+ {
+ if (b)
+ {
+ threadFailed = true;
+ assertFalse(b);
+ }
+ }
+
+ /**
+ * If argument not null, set status to indicate current testcase
+ * should fail
+ */
+ public void threadAssertNull(object x)
+ {
+ if (x != null)
+ {
+ threadFailed = true;
+ assertNull(x);
+ }
+ }
+
+ /**
+ * If arguments not equal, set status to indicate current testcase
+ * should fail
+ */
+ public void threadAssertEquals(long x, long y)
+ {
+ if (x != y)
+ {
+ threadFailed = true;
+ assertEquals(x, y);
+ }
+ }
+
+ /**
+ * If arguments not equal, set status to indicate current testcase
+ * should fail
+ */
+ public void threadAssertEquals(object x, object y)
+ {
+ if (x != y && (x == null || !x.equals(y)))
+ {
+ threadFailed = true;
+ assertEquals(x, y);
+ }
+ }
+
+ /**
+ * threadFail with message "should throw exception"
+ */
+ public void threadShouldThrow()
+ {
+ //try
+ //{
+ threadFailed = true;
+ fail("should throw exception");
+ //}
+ //catch (AssertionFailedError e)
+ //{
+ // e.printStackTrace();
+ // throw e;
+ //}
+ }
+
+ /**
+ * threadFail with message "Unexpected exception"
+ */
+ public void threadUnexpectedException()
+ {
+ threadFailed = true;
+ fail("Unexpected exception");
+ }
+
+ /**
+ * threadFail with message "Unexpected exception", with argument
+ */
+ public void threadUnexpectedException(Exception ex)
+ {
+ threadFailed = true;
+ ex.printStackTrace();
+ fail("Unexpected exception: " + ex);
+ }
+
+ ///**
+ // * Wait out termination of a thread pool or fail doing so
+ // */
+ //public void joinPool(ExecutorService exec)
+ //{
+ // try
+ // {
+ // exec.shutdown();
+ // assertTrue(exec.awaitTermination(LONG_DELAY_MS,
TimeUnit.MILLISECONDS));
+ // }
+ // catch (SecurityException ok)
+ // {
+ // // Allowed in case test doesn't have privs
+ // }
+ // catch (InterruptedException ie)
+ // {
+ // fail("Unexpected exception");
+ // }
+ //}
+
+
+ /**
+ * fail with message "should throw exception"
+ */
+ public void shouldThrow()
+ {
+ fail("Should throw exception");
+ }
+
+ /**
+ * fail with message "Unexpected exception"
+ */
+ public void unexpectedException()
+ {
+ fail("Unexpected exception");
+ }
+
+
+ // LUCENENET TODO: Complete port
+ }
+}
diff --git a/src/Lucene.Net.Tests/Support/Threading/ReentrantLockTest.cs
b/src/Lucene.Net.Tests/Support/Threading/ReentrantLockTest.cs
new file mode 100644
index 000000000..21bc3f56f
--- /dev/null
+++ b/src/Lucene.Net.Tests/Support/Threading/ReentrantLockTest.cs
@@ -0,0 +1,1648 @@
+using J2N.Threading;
+using J2N.Threading.Atomic;
+using Lucene.Net.Attributes;
+using Lucene.Net.Support.Threading;
+using Lucene.Net.Util;
+using NUnit.Framework;
+using RandomizedTesting.Generators;
+using System;
+using System.Collections.Generic;
+using System.Drawing;
+using System.Threading;
+using System.Threading.Tasks;
+using Assert = Lucene.Net.TestFramework.Assert;
+using Console = Lucene.Net.Util.SystemConsole;
+
+namespace Lucene.Net.Support.Threading
+{
+ /*
+ * 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 ReentrantLockTest : JSR166TestCase
+ {
+ //public static void main(String[] args)
+ //{
+ // junit.textui.TestRunner.run(suite());
+ //}
+ //public static Test suite()
+ //{
+ // return new TestSuite(ReentrantLockTest.class);
+ //}
+
+ /**
+ * A runnable calling lockInterruptibly
+ */
+ private class InterruptibleLockRunnable : ThreadJob
+ {
+ private readonly ReentrantLock @lock;
+ public InterruptibleLockRunnable(ReentrantLock l)
+ {
+ @lock = l;
+ }
+
+ public override void Run()
+ {
+ try
+ {
+#pragma warning disable 612, 618
+ @lock.LockInterruptibly();
+#pragma warning restore 612, 618
+ }
+ catch (System.Threading.ThreadInterruptedException)
+ {
+ // success
+ }
+ }
+ }
+
+ /**
+ * A runnable calling lockInterruptibly that expects to be
+ * interrupted
+ */
+ private class InterruptedLockRunnable : ThreadJob
+ {
+ private readonly ReentrantLockTest outerInstance;
+ private readonly ReentrantLock @lock;
+ public InterruptedLockRunnable(ReentrantLockTest outerInstance,
ReentrantLock l)
+ {
+ this.outerInstance = outerInstance;
+ @lock = l;
+ }
+
+ public override void Run()
+ {
+ try
+ {
+#pragma warning disable 612, 618
+ @lock.LockInterruptibly();
+#pragma warning restore 612, 618
+ outerInstance.threadShouldThrow();
+ }
+ catch (System.Threading.ThreadInterruptedException)
+ {
+ // success
+ }
+ }
+ }
+
+ ///**
+ // * Subclass to expose protected methods
+ // */
+ //static class PublicReentrantLock extends ReentrantLock
+ //{
+ // PublicReentrantLock() { super(); }
+ // public Collection<Thread> getQueuedThreads()
+ // {
+ // return super.getQueuedThreads();
+ // }
+ // public Collection<Thread> getWaitingThreads(Condition c)
+ // {
+ // return super.getWaitingThreads(c);
+ // }
+
+
+ //}
+
+ ///**
+ // * Constructor sets given fairness
+ // */
+ //public void testConstructor()
+ //{
+ // ReentrantLock rl = new ReentrantLock();
+ // assertFalse(rl.isFair());
+ // ReentrantLock r2 = new ReentrantLock(true);
+ // assertTrue(r2.isFair());
+ //}
+
+ ///**
+ // * locking an unlocked lock succeeds
+ // */
+ //[Test]
+ //public void TestLock()
+ //{
+ // ReentrantLock rl = new ReentrantLock();
+ // rl.Lock();
+ // assertTrue(rl.IsLocked);
+ // rl.Unlock();
+ //}
+
+ ///**
+ // * locking an unlocked fair lock succeeds
+ // */
+ //public void testFairLock()
+ //{
+ // ReentrantLock rl = new ReentrantLock(true);
+ // rl.lock () ;
+ // assertTrue(rl.isLocked());
+ // rl.unlock();
+ //}
+
+
+ /**
+ * Unlocking an unlocked lock throws IllegalMonitorStateException
+ */
+ [Test]
+ public void TestUnlock_IllegalMonitorStateException()
+ {
+ ReentrantLock rl = new ReentrantLock();
+
+ try
+ {
+ rl.Unlock();
+ shouldThrow();
+ }
+ catch (SynchronizationLockException)
+ {
+ // success
+ }
+ }
+
+ ///**
+ // * tryLock on an unlocked lock succeeds
+ // */
+ //[Test]
+ //public void TestTryLock()
+ //{
+ // ReentrantLock rl = new ReentrantLock();
+ // assertTrue(rl.TryLock());
+ // assertTrue(rl.IsLocked);
+ // rl.Unlock();
+ //}
+
+
+ ///**
+ // * hasQueuedThreads reports whether there are waiting threads
+ // */
+ //[Test]
+ //[Ignore("Behavior differs from Java around interrupts, but
Lucene.NET doesn't support interrupts or use this property.")]
+ //public void TesthasQueuedThreads()
+ //{
+ // ReentrantLock @lock = new ReentrantLock();
+ // ThreadJob t1 = new InterruptedLockRunnable(this, @lock);
+ // ThreadJob t2 = new InterruptibleLockRunnable(@lock);
+ // try
+ // {
+ // assertFalse(@lock.HasQueuedThreads);
+ // @lock.Lock();
+ // t1.Start();
+ // Thread.Sleep(SHORT_DELAY_MS);
+ // assertTrue(@lock.HasQueuedThreads);
+ // t2.Start();
+ // Thread.Sleep(SHORT_DELAY_MS);
+ // assertTrue(@lock.HasQueuedThreads);
+ // t1.Interrupt();
+ // Thread.Sleep(SHORT_DELAY_MS);
+ // assertTrue(@lock.HasQueuedThreads);
+ // @lock.Unlock();
+ // Thread.Sleep(SHORT_DELAY_MS);
+ // //assertFalse(@lock.HasQueuedThreads); // LUCENENET:
Behavior differs from Java around interrupts, but Lucene.NET doesn't support
interrupts or use this property.
+ // t1.Join();
+ // t2.Join();
+
+ // assertFalse(@lock.HasQueuedThreads); // LUCENENET: Added
assert
+ // }
+ // catch (Exception e) when (e.IsException())
+ // {
+ // unexpectedException();
+ // }
+ //}
+
+ ///**
+ // * getQueueLength reports number of waiting threads
+ // */
+ //[Test]
+ //[Ignore("Behavior differs from Java around interrupts, but
Lucene.NET doesn't support interrupts or use this property.")]
+ //public void TestGetQueueLength()
+ //{
+ // ReentrantLock @lock = new ReentrantLock();
+ // ThreadJob t1 = new InterruptedLockRunnable(this, @lock);
+ // ThreadJob t2 = new InterruptibleLockRunnable(@lock);
+ // try
+ // {
+ // assertEquals(0, @lock.QueueLength);
+ // @lock.Lock();
+ // t1.Start();
+ // Thread.Sleep(SHORT_DELAY_MS);
+ // assertEquals(1, @lock.QueueLength);
+ // t2.Start();
+ // Thread.Sleep(SHORT_DELAY_MS);
+ // assertEquals(2, @lock.QueueLength);
+ // t1.Interrupt();
+ // Thread.Sleep(SHORT_DELAY_MS);
+ // assertEquals(1, @lock.QueueLength);
+ // @lock.Unlock();
+ // Thread.Sleep(SHORT_DELAY_MS);
+ // //assertEquals(0, @lock.QueueLength); // LUCENENET: Behavior
differs from Java around interrupts, but Lucene.NET doesn't support interrupts
or use this property.
+ // t1.Join();
+ // t2.Join();
+
+ // assertEquals(0, @lock.QueueLength); // LUCENENET: Added
assert
+ // }
+ // catch (Exception e) when (e.IsException())
+ // {
+ // unexpectedException();
+ // }
+ //}
+
+ ///**
+ // * getQueueLength reports number of waiting threads
+ // */
+ //public void testGetQueueLength_fair()
+ //{
+ // final ReentrantLock lock = new ReentrantLock(true);
+ // Thread t1 = new Thread(new InterruptedLockRunnable(lock));
+ // Thread t2 = new Thread(new InterruptibleLockRunnable(lock));
+ // try
+ // {
+ // assertEquals(0, lock.getQueueLength()) ;
+ // lock.lock () ;
+ // t1.start();
+ // Thread.sleep(SHORT_DELAY_MS);
+ // assertEquals(1, lock.getQueueLength()) ;
+ // t2.start();
+ // Thread.sleep(SHORT_DELAY_MS);
+ // assertEquals(2, lock.getQueueLength()) ;
+ // t1.interrupt();
+ // Thread.sleep(SHORT_DELAY_MS);
+ // assertEquals(1, lock.getQueueLength()) ;
+ // lock.unlock();
+ // Thread.sleep(SHORT_DELAY_MS);
+ // assertEquals(0, lock.getQueueLength()) ;
+ // t1.join();
+ // t2.join();
+ // }
+ // catch (Exception e)
+ // {
+ // unexpectedException();
+ // }
+ //}
+
+ ///**
+ // * hasQueuedThread(null) throws NPE
+ // */
+ //public void testHasQueuedThreadNPE()
+ //{
+ // final ReentrantLock sync = new ReentrantLock();
+ // try
+ // {
+ // sync.hasQueuedThread(null);
+ // shouldThrow();
+ // }
+ // catch (NullPointerException success)
+ // {
+ // }
+ //}
+
+ ///**
+ // * hasQueuedThread reports whether a thread is queued.
+ // */
+ //public void testHasQueuedThread()
+ //{
+ // final ReentrantLock sync = new ReentrantLock();
+ // Thread t1 = new Thread(new InterruptedLockRunnable(sync));
+ // Thread t2 = new Thread(new InterruptibleLockRunnable(sync));
+ // try
+ // {
+ // assertFalse(sync.hasQueuedThread(t1));
+ // assertFalse(sync.hasQueuedThread(t2));
+ // sync.lock () ;
+ // t1.start();
+ // Thread.sleep(SHORT_DELAY_MS);
+ // assertTrue(sync.hasQueuedThread(t1));
+ // t2.start();
+ // Thread.sleep(SHORT_DELAY_MS);
+ // assertTrue(sync.hasQueuedThread(t1));
+ // assertTrue(sync.hasQueuedThread(t2));
+ // t1.interrupt();
+ // Thread.sleep(SHORT_DELAY_MS);
+ // assertFalse(sync.hasQueuedThread(t1));
+ // assertTrue(sync.hasQueuedThread(t2));
+ // sync.unlock();
+ // Thread.sleep(SHORT_DELAY_MS);
+ // assertFalse(sync.hasQueuedThread(t1));
+ // Thread.sleep(SHORT_DELAY_MS);
+ // assertFalse(sync.hasQueuedThread(t2));
+ // t1.join();
+ // t2.join();
+ // }
+ // catch (Exception e)
+ // {
+ // unexpectedException();
+ // }
+ //}
+
+
+ ///**
+ // * getQueuedThreads includes waiting threads
+ // */
+ //public void testGetQueuedThreads()
+ //{
+ // final PublicReentrantLock lock = new PublicReentrantLock();
+ // Thread t1 = new Thread(new InterruptedLockRunnable(lock));
+ // Thread t2 = new Thread(new InterruptibleLockRunnable(lock));
+ // try
+ // {
+ // assertTrue(lock.getQueuedThreads().isEmpty()) ;
+ // lock.lock () ;
+ // assertTrue(lock.getQueuedThreads().isEmpty()) ;
+ // t1.start();
+ // Thread.sleep(SHORT_DELAY_MS);
+ // assertTrue(lock.getQueuedThreads().contains(t1)) ;
+ // t2.start();
+ // Thread.sleep(SHORT_DELAY_MS);
+ // assertTrue(lock.getQueuedThreads().contains(t1)) ;
+ // assertTrue(lock.getQueuedThreads().contains(t2)) ;
+ // t1.interrupt();
+ // Thread.sleep(SHORT_DELAY_MS);
+ // assertFalse(lock.getQueuedThreads().contains(t1)) ;
+ // assertTrue(lock.getQueuedThreads().contains(t2)) ;
+ // lock.unlock();
+ // Thread.sleep(SHORT_DELAY_MS);
+ // assertTrue(lock.getQueuedThreads().isEmpty()) ;
+ // t1.join();
+ // t2.join();
+ // }
+ // catch (Exception e)
+ // {
+ // unexpectedException();
+ // }
+ //}
+
+
+ ///**
+ // * timed tryLock is interruptible.
+ // */
+ //public void testInterruptedException2()
+ //{
+ // final ReentrantLock lock = new ReentrantLock();
+ // lock.lock () ;
+ // Thread t = new Thread(new Runnable()
+ // {
+ // public void run()
+ // {
+ // try
+ // {
+ // lock.tryLock(MEDIUM_DELAY_MS, TimeUnit.MILLISECONDS);
+ // threadShouldThrow();
+ // }
+ // catch (InterruptedException success) { }
+ // }
+ // });
+ // try {
+ // t.start();
+ // t.interrupt();
+ // } catch(Exception e){
+ // unexpectedException();
+ // }
+ //}
+
+ /**
+ * TryLock on a locked lock fails
+ */
+ [Test]
+ public void TestTryLockWhenLocked()
+ {
+ ReentrantLock @lock = new ReentrantLock();
+ @lock.Lock();
+ Thread t = new Thread(() =>
+ {
+ threadAssertFalse(@lock.TryLock());
+ });
+ try
+ {
+ t.Start();
+ t.Join();
+ @lock.Unlock();
+ }
+ catch (Exception e) when (e.IsException())
+ {
+ unexpectedException();
+ }
+ }
+
+ ///**
+ // * Timed tryLock on a locked lock times out
+ // */
+ //public void testTryLock_Timeout()
+ //{
+ // final ReentrantLock lock = new ReentrantLock();
+ // lock.lock () ;
+ // Thread t = new Thread(new Runnable()
+ // {
+ // public void run()
+ // {
+ // try
+ // {
+ // threadAssertFalse(lock.tryLock(1,
TimeUnit.MILLISECONDS)) ;
+ // }
+ // catch (Exception ex)
+ // {
+ // threadUnexpectedException();
+ // }
+ // }
+ // });
+ // try {
+ // t.start();
+ // t.join();
+ // lock.unlock();
+ // } catch(Exception e){
+ // unexpectedException();
+ // }
+ //}
+
+ ///**
+ // * getHoldCount returns number of recursive holds
+ // */
+ //[Test]
+ //public void testGetHoldCount()
+ //{
+ // ReentrantLock @lock = new ReentrantLock();
+ // for (int i = 1; i <= SIZE; i++)
+ // {
+ // @lock.lock () ;
+ // assertEquals(i, @lock.getHoldCount()) ;
+ // }
+ // for (int i = SIZE; i > 0; i--)
+ // {
+ // @lock.Unlock();
+ // assertEquals(i - 1, @lock.getHoldCount()) ;
+ // }
+ //}
+
+
+ ///**
+ // * isLocked is true when locked and false when not
+ // */
+ //[Test]
+ //public void TestIsLocked()
+ //{
+ // ReentrantLock @lock = new ReentrantLock();
+ // @lock.Lock();
+ // assertTrue(@lock.IsLocked);
+ // @lock.Unlock();
+ // assertFalse(@lock.IsLocked);
+ // Thread t = new Thread(() =>
+ // {
+ // @lock.Lock();
+ // try
+ // {
+ // Thread.Sleep(SMALL_DELAY_MS);
+ // }
+ // catch (Exception e)
+ // {
+ // threadUnexpectedException();
+ // }
+ // @lock.Unlock();
+ // });
+
+ // try
+ // {
+ // t.Start();
+ // Thread.Sleep(SHORT_DELAY_MS);
+ // assertTrue(@lock.IsLocked);
+ // t.Join();
+ // assertFalse(@lock.IsLocked);
+ // }
+ // catch (Exception e) when (e.IsException())
+ // {
+ // unexpectedException();
+ // }
+ //}
+
+ /**
+ * lockInterruptibly is interruptible.
+ */
+ [Test]
+ [Ignore("LUCENENET: LockInterruptibly() is broken, but it is not in
use anywhere but in the tests. Technically, Lucene.NET does not support
Thread.Interrupt().")]
+ public void TestLockInterruptibly1()
+ {
+ ReentrantLock @lock = new ReentrantLock();
+ @lock.Lock();
+ ThreadJob t = new InterruptedLockRunnable(this, @lock);
+ try
+ {
+ t.Start();
+ Thread.Sleep(SHORT_DELAY_MS);
+ t.Interrupt();
+ Thread.Sleep(SHORT_DELAY_MS);
+ @lock.Unlock();
+ t.Join();
+ }
+ catch (Exception e) when (e.IsException())
+ {
+ unexpectedException();
+ }
+ }
+
+ ///**
+ // * lockInterruptibly succeeds when unlocked, else is interruptible
+ // */
+ //[Test]
+ //[Ignore("LUCENENET: LockInterruptibly() is broken, but it is not in
use anywhere but in the tests. Technically, Lucene.NET does not support
Thread.Interrupt().")]
+ //public void TestLockInterruptibly2()
+ //{
+ // ReentrantLock @lock = new ReentrantLock();
+ // try
+ // {
+ // @lock.LockInterruptibly();
+ // }
+ // catch (Exception e) when (e.IsException())
+ // {
+ // unexpectedException();
+ // }
+ // ThreadJob t = new InterruptedLockRunnable(this, @lock);
+ // try
+ // {
+ // t.Start();
+ // t.Interrupt();
+ // assertTrue(@lock.IsLocked);
+ // assertTrue(@lock.IsHeldByCurrentThread);
+ // t.Join();
+ // }
+ // catch (Exception e) when (e.IsException())
+ // {
+ // unexpectedException();
+ // }
+ //}
+
+
+
+
+
+
+ ///**
+ // * Calling await without holding lock throws
IllegalMonitorStateException
+ // */
+ //public void testAwait_IllegalMonitor()
+ //{
+ // final ReentrantLock lock = new ReentrantLock();
+ // final Condition c = lock.newCondition();
+ // try
+ // {
+ // c.await();
+ // shouldThrow();
+ // }
+ // catch (IllegalMonitorStateException success)
+ // {
+ // }
+ // catch (Exception ex)
+ // {
+ // unexpectedException();
+ // }
+ //}
+
+ ///**
+ // * Calling signal without holding lock throws
IllegalMonitorStateException
+ // */
+ //public void testSignal_IllegalMonitor()
+ //{
+ // final ReentrantLock lock = new ReentrantLock();
+ // final Condition c = lock.newCondition();
+ // try
+ // {
+ // c.signal();
+ // shouldThrow();
+ // }
+ // catch (IllegalMonitorStateException success)
+ // {
+ // }
+ // catch (Exception ex)
+ // {
+ // unexpectedException();
+ // }
+ //}
+
+ ///**
+ // * awaitNanos without a signal times out
+ // */
+ //public void testAwaitNanos_Timeout()
+ //{
+ // final ReentrantLock lock = new ReentrantLock();
+ // final Condition c = lock.newCondition();
+ // try
+ // {
+ // lock.lock () ;
+ // long t = c.awaitNanos(100);
+ // assertTrue(t <= 0);
+ // lock.unlock();
+ // }
+ // catch (Exception ex)
+ // {
+ // unexpectedException();
+ // }
+ //}
+
+ ///**
+ // * timed await without a signal times out
+ // */
+ //public void testAwait_Timeout()
+ //{
+ // final ReentrantLock lock = new ReentrantLock();
+ // final Condition c = lock.newCondition();
+ // try
+ // {
+ // lock.lock () ;
+ // c.await(SHORT_DELAY_MS, TimeUnit.MILLISECONDS);
+ // lock.unlock();
+ // }
+ // catch (Exception ex)
+ // {
+ // unexpectedException();
+ // }
+ //}
+
+ ///**
+ // * awaitUntil without a signal times out
+ // */
+ //public void testAwaitUntil_Timeout()
+ //{
+ // final ReentrantLock lock = new ReentrantLock();
+ // final Condition c = lock.newCondition();
+ // try
+ // {
+ // lock.lock () ;
+ // java.util.Date d = new java.util.Date();
+ // c.awaitUntil(new java.util.Date(d.getTime() + 10));
+ // lock.unlock();
+ // }
+ // catch (Exception ex)
+ // {
+ // unexpectedException();
+ // }
+ //}
+
+ ///**
+ // * await returns when signalled
+ // */
+ //public void testAwait()
+ //{
+ // final ReentrantLock lock = new ReentrantLock();
+ // final Condition c = lock.newCondition();
+ // Thread t = new Thread(new Runnable()
+ // {
+
+ // public void run()
+ // {
+ // try
+ // {
+ // lock.lock () ;
+ // c.await();
+ // lock.unlock();
+ // }
+ // catch (InterruptedException e)
+ // {
+ // threadUnexpectedException();
+ // }
+ // }
+ // });
+
+ // try {
+ // t.start();
+ // Thread.sleep(SHORT_DELAY_MS);
+ // lock.lock();
+ // c.signal();
+ // lock.unlock();
+ // t.join(SHORT_DELAY_MS);
+ // assertFalse(t.isAlive());
+ // }
+ // catch (Exception ex) {
+ // unexpectedException();
+ // }
+ //}
+
+ ///**
+ // * hasWaiters throws NPE if null
+ // */
+ //public void testHasWaitersNPE()
+ //{
+ // final ReentrantLock lock = new ReentrantLock();
+ // try
+ // {
+ // lock.hasWaiters(null);
+ // shouldThrow();
+ // }
+ // catch (NullPointerException success)
+ // {
+ // }
+ // catch (Exception ex)
+ // {
+ // unexpectedException();
+ // }
+ //}
+
+ ///**
+ // * getWaitQueueLength throws NPE if null
+ // */
+ //public void testGetWaitQueueLengthNPE()
+ //{
+ // final ReentrantLock lock = new ReentrantLock();
+ // try
+ // {
+ // lock.getWaitQueueLength(null);
+ // shouldThrow();
+ // }
+ // catch (NullPointerException success)
+ // {
+ // }
+ // catch (Exception ex)
+ // {
+ // unexpectedException();
+ // }
+ //}
+
+
+ ///**
+ // * getWaitingThreads throws NPE if null
+ // */
+ //public void testGetWaitingThreadsNPE()
+ //{
+ // final PublicReentrantLock lock = new PublicReentrantLock();
+ // try
+ // {
+ // lock.getWaitingThreads(null);
+ // shouldThrow();
+ // }
+ // catch (NullPointerException success)
+ // {
+ // }
+ // catch (Exception ex)
+ // {
+ // unexpectedException();
+ // }
+ //}
+
+
+ ///**
+ // * hasWaiters throws IAE if not owned
+ // */
+ //public void testHasWaitersIAE()
+ //{
+ // final ReentrantLock lock = new ReentrantLock();
+ // final Condition c = (lock.newCondition()) ;
+ // final ReentrantLock lock2 = new ReentrantLock();
+ // try
+ // {
+ // lock2.hasWaiters(c);
+ // shouldThrow();
+ // }
+ // catch (IllegalArgumentException success)
+ // {
+ // }
+ // catch (Exception ex)
+ // {
+ // unexpectedException();
+ // }
+ //}
+
+ ///**
+ // * hasWaiters throws IMSE if not locked
+ // */
+ //public void testHasWaitersIMSE()
+ //{
+ // final ReentrantLock lock = new ReentrantLock();
+ // final Condition c = (lock.newCondition()) ;
+ // try
+ // {
+ // lock.hasWaiters(c);
+ // shouldThrow();
+ // }
+ // catch (IllegalMonitorStateException success)
+ // {
+ // }
+ // catch (Exception ex)
+ // {
+ // unexpectedException();
+ // }
+ //}
+
+
+ ///**
+ // * getWaitQueueLength throws IAE if not owned
+ // */
+ //public void testGetWaitQueueLengthIAE()
+ //{
+ // final ReentrantLock lock = new ReentrantLock();
+ // final Condition c = (lock.newCondition()) ;
+ // final ReentrantLock lock2 = new ReentrantLock();
+ // try
+ // {
+ // lock2.getWaitQueueLength(c);
+ // shouldThrow();
+ // }
+ // catch (IllegalArgumentException success)
+ // {
+ // }
+ // catch (Exception ex)
+ // {
+ // unexpectedException();
+ // }
+ //}
+
+ ///**
+ // * getWaitQueueLength throws IMSE if not locked
+ // */
+ //public void testGetWaitQueueLengthIMSE()
+ //{
+ // final ReentrantLock lock = new ReentrantLock();
+ // final Condition c = (lock.newCondition()) ;
+ // try
+ // {
+ // lock.getWaitQueueLength(c);
+ // shouldThrow();
+ // }
+ // catch (IllegalMonitorStateException success)
+ // {
+ // }
+ // catch (Exception ex)
+ // {
+ // unexpectedException();
+ // }
+ //}
+
+
+ ///**
+ // * getWaitingThreads throws IAE if not owned
+ // */
+ //public void testGetWaitingThreadsIAE()
+ //{
+ // final PublicReentrantLock lock = new PublicReentrantLock();
+ // final Condition c = (lock.newCondition()) ;
+ // final PublicReentrantLock lock2 = new PublicReentrantLock();
+ // try
+ // {
+ // lock2.getWaitingThreads(c);
+ // shouldThrow();
+ // }
+ // catch (IllegalArgumentException success)
+ // {
+ // }
+ // catch (Exception ex)
+ // {
+ // unexpectedException();
+ // }
+ //}
+
+ ///**
+ // * getWaitingThreads throws IMSE if not locked
+ // */
+ //public void testGetWaitingThreadsIMSE()
+ //{
+ // final PublicReentrantLock lock = new PublicReentrantLock();
+ // final Condition c = (lock.newCondition()) ;
+ // try
+ // {
+ // lock.getWaitingThreads(c);
+ // shouldThrow();
+ // }
+ // catch (IllegalMonitorStateException success)
+ // {
+ // }
+ // catch (Exception ex)
+ // {
+ // unexpectedException();
+ // }
+ //}
+
+
+
+ ///**
+ // * hasWaiters returns true when a thread is waiting, else false
+ // */
+ //public void testHasWaiters()
+ //{
+ // final ReentrantLock lock = new ReentrantLock();
+ // final Condition c = lock.newCondition();
+ // Thread t = new Thread(new Runnable()
+ // {
+
+ // public void run()
+ // {
+ // try
+ // {
+ // lock.lock () ;
+ // threadAssertFalse(lock.hasWaiters(c)) ;
+ // threadAssertEquals(0, lock.getWaitQueueLength(c)) ;
+ // c.await();
+ // lock.unlock();
+ // }
+ // catch (InterruptedException e)
+ // {
+ // threadUnexpectedException();
+ // }
+ // }
+ // });
+
+ // try
+ // {
+ // t.start();
+ // Thread.sleep(SHORT_DELAY_MS);
+ // lock.lock () ;
+ // assertTrue(lock.hasWaiters(c)) ;
+ // assertEquals(1, lock.getWaitQueueLength(c)) ;
+ // c.signal();
+ // lock.unlock();
+ // Thread.sleep(SHORT_DELAY_MS);
+ // lock.lock () ;
+ // assertFalse(lock.hasWaiters(c)) ;
+ // assertEquals(0, lock.getWaitQueueLength(c)) ;
+ // lock.unlock();
+ // t.join(SHORT_DELAY_MS);
+ // assertFalse(t.isAlive());
+ // }
+ // catch (Exception ex)
+ // {
+ // unexpectedException();
+ // }
+ //}
+
+ ///**
+ // * getWaitQueueLength returns number of waiting threads
+ // */
+ //public void testGetWaitQueueLength()
+ //{
+ // final ReentrantLock lock = new ReentrantLock();
+ // final Condition c = lock.newCondition();
+ // Thread t1 = new Thread(new Runnable()
+ // {
+
+ // public void run()
+ // {
+ // try
+ // {
+ // lock.lock () ;
+ // threadAssertFalse(lock.hasWaiters(c)) ;
+ // threadAssertEquals(0, lock.getWaitQueueLength(c)) ;
+ // c.await();
+ // lock.unlock();
+ // }
+ // catch (InterruptedException e)
+ // {
+ // threadUnexpectedException();
+ // }
+ // }
+ // });
+
+ // Thread t2 = new Thread(new Runnable()
+ // {
+
+ // public void run()
+ // {
+ // try
+ // {
+ // lock.lock () ;
+ // threadAssertTrue(lock.hasWaiters(c)) ;
+ // threadAssertEquals(1, lock.getWaitQueueLength(c)) ;
+ // c.await();
+ // lock.unlock();
+ // }
+ // catch (InterruptedException e)
+ // {
+ // threadUnexpectedException();
+ // }
+ // }
+ // });
+
+ // try
+ // {
+ // t1.start();
+ // Thread.sleep(SHORT_DELAY_MS);
+ // t2.start();
+ // Thread.sleep(SHORT_DELAY_MS);
+ // lock.lock () ;
+ // assertTrue(lock.hasWaiters(c)) ;
+ // assertEquals(2, lock.getWaitQueueLength(c)) ;
+ // c.signalAll();
+ // lock.unlock();
+ // Thread.sleep(SHORT_DELAY_MS);
+ // lock.lock () ;
+ // assertFalse(lock.hasWaiters(c)) ;
+ // assertEquals(0, lock.getWaitQueueLength(c)) ;
+ // lock.unlock();
+ // t1.join(SHORT_DELAY_MS);
+ // t2.join(SHORT_DELAY_MS);
+ // assertFalse(t1.isAlive());
+ // assertFalse(t2.isAlive());
+ // }
+ // catch (Exception ex)
+ // {
+ // unexpectedException();
+ // }
+ //}
+
+ ///**
+ // * getWaitingThreads returns only and all waiting threads
+ // */
+ //public void testGetWaitingThreads()
+ //{
+ // final PublicReentrantLock lock = new PublicReentrantLock();
+ // final Condition c = lock.newCondition();
+ // Thread t1 = new Thread(new Runnable()
+ // {
+
+ // public void run()
+ // {
+ // try
+ // {
+ // lock.lock () ;
+ //
threadAssertTrue(lock.getWaitingThreads(c).isEmpty()) ;
+ // c.await();
+ // lock.unlock();
+ // }
+ // catch (InterruptedException e)
+ // {
+ // threadUnexpectedException();
+ // }
+ // }
+ // });
+
+ // Thread t2 = new Thread(new Runnable()
+ // {
+
+ // public void run()
+ // {
+ // try
+ // {
+ // lock.lock () ;
+ //
threadAssertFalse(lock.getWaitingThreads(c).isEmpty()) ;
+ // c.await();
+ // lock.unlock();
+ // }
+ // catch (InterruptedException e)
+ // {
+ // threadUnexpectedException();
+ // }
+ // }
+ // });
+
+ // try
+ // {
+ // lock.lock () ;
+ // assertTrue(lock.getWaitingThreads(c).isEmpty()) ;
+ // lock.unlock();
+ // t1.start();
+ // Thread.sleep(SHORT_DELAY_MS);
+ // t2.start();
+ // Thread.sleep(SHORT_DELAY_MS);
+ // lock.lock () ;
+ // assertTrue(lock.hasWaiters(c)) ;
+ // assertTrue(lock.getWaitingThreads(c).contains(t1)) ;
+ // assertTrue(lock.getWaitingThreads(c).contains(t2)) ;
+ // c.signalAll();
+ // lock.unlock();
+ // Thread.sleep(SHORT_DELAY_MS);
+ // lock.lock () ;
+ // assertFalse(lock.hasWaiters(c)) ;
+ // assertTrue(lock.getWaitingThreads(c).isEmpty()) ;
+ // lock.unlock();
+ // t1.join(SHORT_DELAY_MS);
+ // t2.join(SHORT_DELAY_MS);
+ // assertFalse(t1.isAlive());
+ // assertFalse(t2.isAlive());
+ // }
+ // catch (Exception ex)
+ // {
+ // unexpectedException();
+ // }
+ //}
+
+ ///** A helper class for uninterruptible wait tests */
+ //class UninterruptableThread extends Thread
+ //{
+ // private ReentrantLock lock;
+ // private Condition c;
+
+ // public volatile boolean canAwake = false;
+ // public volatile boolean interrupted = false;
+ // public volatile boolean lockStarted = false;
+
+ // public UninterruptableThread(ReentrantLock lock, Condition c)
+ // {
+ // this.lock = lock;
+ // this.c = c;
+ // }
+
+ // public synchronized void run()
+ // {
+ // lock.lock () ;
+ // lockStarted = true;
+
+ // while (!canAwake)
+ // {
+ // c.awaitUninterruptibly();
+ // }
+
+ // interrupted = isInterrupted();
+ // lock.unlock();
+ // }
+ //}
+
+ ///**
+ // * awaitUninterruptibly doesn't abort on interrupt
+ // */
+ //public void testAwaitUninterruptibly()
+ //{
+ // final ReentrantLock lock = new ReentrantLock();
+ // final Condition c = lock.newCondition();
+ // UninterruptableThread thread = new UninterruptableThread(lock,
c);
+
+ // try
+ // {
+ // thread.start();
+
+ // while (!thread.lockStarted)
+ // {
+ // Thread.sleep(100);
+ // }
+
+ // lock.lock () ;
+ // try
+ // {
+ // thread.interrupt();
+ // thread.canAwake = true;
+ // c.signal();
+ // }
+ // finally
+ // {
+ // lock.unlock();
+ // }
+
+ // thread.join();
+ // assertTrue(thread.interrupted);
+ // assertFalse(thread.isAlive());
+ // }
+ // catch (Exception ex)
+ // {
+ // unexpectedException();
+ // }
+ //}
+
+ ///**
+ // * await is interruptible
+ // */
+ //public void testAwait_Interrupt()
+ //{
+ // final ReentrantLock lock = new ReentrantLock();
+ // final Condition c = lock.newCondition();
+ // Thread t = new Thread(new Runnable()
+ // {
+
+ // public void run()
+ // {
+ // try
+ // {
+ // lock.lock () ;
+ // c.await();
+ // lock.unlock();
+ // threadShouldThrow();
+ // }
+ // catch (InterruptedException success)
+ // {
+ // }
+ // }
+ // });
+
+ // try {
+ // t.start();
+ // Thread.sleep(SHORT_DELAY_MS);
+ // t.interrupt();
+ // t.join(SHORT_DELAY_MS);
+ // assertFalse(t.isAlive());
+ // }
+ // catch (Exception ex) {
+ // unexpectedException();
+ // }
+ //}
+
+ ///**
+ // * awaitNanos is interruptible
+ // */
+ //public void testAwaitNanos_Interrupt()
+ //{
+ // final ReentrantLock lock = new ReentrantLock();
+ // final Condition c = lock.newCondition();
+ // Thread t = new Thread(new Runnable()
+ // {
+
+ // public void run()
+ // {
+ // try
+ // {
+ // lock.lock () ;
+ // c.awaitNanos(1000 * 1000 * 1000); // 1 sec
+ // lock.unlock();
+ // threadShouldThrow();
+ // }
+ // catch (InterruptedException success)
+ // {
+ // }
+ // }
+ // });
+
+ // try
+ // {
+ // t.start();
+ // Thread.sleep(SHORT_DELAY_MS);
+ // t.interrupt();
+ // t.join(SHORT_DELAY_MS);
+ // assertFalse(t.isAlive());
+ // }
+ // catch (Exception ex)
+ // {
+ // unexpectedException();
+ // }
+ //}
+
+ ///**
+ // * awaitUntil is interruptible
+ // */
+ //public void testAwaitUntil_Interrupt()
+ //{
+ // final ReentrantLock lock = new ReentrantLock();
+ // final Condition c = lock.newCondition();
+ // Thread t = new Thread(new Runnable()
+ // {
+
+ // public void run()
+ // {
+ // try
+ // {
+ // lock.lock () ;
+ // java.util.Date d = new java.util.Date();
+ // c.awaitUntil(new java.util.Date(d.getTime() +
10000));
+ // lock.unlock();
+ // threadShouldThrow();
+ // }
+ // catch (InterruptedException success)
+ // {
+ // }
+ // }
+ // });
+
+ // try
+ // {
+ // t.start();
+ // Thread.sleep(SHORT_DELAY_MS);
+ // t.interrupt();
+ // t.join(SHORT_DELAY_MS);
+ // assertFalse(t.isAlive());
+ // }
+ // catch (Exception ex)
+ // {
+ // unexpectedException();
+ // }
+ //}
+
+ ///**
+ // * signalAll wakes up all threads
+ // */
+ //public void testSignalAll()
+ //{
+ // final ReentrantLock lock = new ReentrantLock();
+ // final Condition c = lock.newCondition();
+ // Thread t1 = new Thread(new Runnable()
+ // {
+
+ // public void run()
+ // {
+ // try
+ // {
+ // lock.lock () ;
+ // c.await();
+ // lock.unlock();
+ // }
+ // catch (InterruptedException e)
+ // {
+ // threadUnexpectedException();
+ // }
+ // }
+ // });
+
+ // Thread t2 = new Thread(new Runnable()
+ // {
+
+ // public void run()
+ // {
+ // try
+ // {
+ // lock.lock () ;
+ // c.await();
+ // lock.unlock();
+ // }
+ // catch (InterruptedException e)
+ // {
+ // threadUnexpectedException();
+ // }
+ // }
+ // });
+
+ // try
+ // {
+ // t1.start();
+ // t2.start();
+ // Thread.sleep(SHORT_DELAY_MS);
+ // lock.lock () ;
+ // c.signalAll();
+ // lock.unlock();
+ // t1.join(SHORT_DELAY_MS);
+ // t2.join(SHORT_DELAY_MS);
+ // assertFalse(t1.isAlive());
+ // assertFalse(t2.isAlive());
+ // }
+ // catch (Exception ex)
+ // {
+ // unexpectedException();
+ // }
+ //}
+
+ ///**
+ // * await after multiple reentrant locking preserves lock count
+ // */
+ //public void testAwaitLockCount()
+ //{
+ // final ReentrantLock lock = new ReentrantLock();
+ // final Condition c = lock.newCondition();
+ // Thread t1 = new Thread(new Runnable()
+ // {
+
+ // public void run()
+ // {
+ // try
+ // {
+ // lock.lock () ;
+ // threadAssertEquals(1, lock.getHoldCount()) ;
+ // c.await();
+ // threadAssertEquals(1, lock.getHoldCount()) ;
+ // lock.unlock();
+ // }
+ // catch (InterruptedException e)
+ // {
+ // threadUnexpectedException();
+ // }
+ // }
+ // });
+
+ // Thread t2 = new Thread(new Runnable()
+ // {
+
+ // public void run()
+ // {
+ // try
+ // {
+ // lock.lock () ;
+ // lock.lock () ;
+ // threadAssertEquals(2, lock.getHoldCount()) ;
+ // c.await();
+ // threadAssertEquals(2, lock.getHoldCount()) ;
+ // lock.unlock();
+ // lock.unlock();
+ // }
+ // catch (InterruptedException e)
+ // {
+ // threadUnexpectedException();
+ // }
+ // }
+ // });
+
+ // try
+ // {
+ // t1.start();
+ // t2.start();
+ // Thread.sleep(SHORT_DELAY_MS);
+ // lock.lock () ;
+ // c.signalAll();
+ // lock.unlock();
+ // t1.join(SHORT_DELAY_MS);
+ // t2.join(SHORT_DELAY_MS);
+ // assertFalse(t1.isAlive());
+ // assertFalse(t2.isAlive());
+ // }
+ // catch (Exception ex)
+ // {
+ // unexpectedException();
+ // }
+ //}
+
+ ///**
+ // * A serialized lock deserializes as unlocked
+ // */
+ //public void testSerialization()
+ //{
+ // ReentrantLock l = new ReentrantLock();
+ // l.lock () ;
+ // l.unlock();
+
+ // try
+ // {
+ // ByteArrayOutputStream bout = new
ByteArrayOutputStream(10000);
+ // ObjectOutputStream out = new ObjectOutputStream(new
BufferedOutputStream(bout));
+ // out.writeObject(l);
+ // out.close();
+
+ // ByteArrayInputStream bin = new
ByteArrayInputStream(bout.toByteArray());
+ // ObjectInputStream in = new ObjectInputStream(new
BufferedInputStream(bin));
+ // ReentrantLock r = (ReentrantLock) in.readObject();
+ // r.lock () ;
+ // r.unlock();
+ // }
+ // catch (Exception e)
+ // {
+ // e.printStackTrace();
+ // unexpectedException();
+ // }
+ //}
+
+ /**
+ * toString indicates current lock state
+ */
+ [Test]
+ [Ignore("LUCENENET: Not implemented")]
+ public void TestToString()
+ {
+ ReentrantLock @lock = new ReentrantLock();
+ string us = @lock.ToString();
+ assertTrue(us.IndexOf("Unlocked", StringComparison.Ordinal) >= 0);
+ @lock.Lock();
+ string ls = @lock.ToString();
+ assertTrue(ls.IndexOf("Locked", StringComparison.Ordinal) >= 0);
+ }
+
+ //[Test]
+ //[LuceneNetSpecific]
+ //public void TestReentry()
+ //{
+ // ReentrantLock @lock = new ReentrantLock();
+ // assertFalse(@lock.IsLocked);
+ // @lock.Lock();
+ // assertTrue(@lock.IsLocked);
+ // @lock.TryLock();
+ // assertTrue(@lock.IsLocked);
+ // @lock.Lock();
+ // assertTrue(@lock.IsLocked);
+
+ // // Now unwind the stack
+ // @lock.Unlock();
+ // assertTrue(@lock.IsLocked);
+ // @lock.Unlock();
+ // assertTrue(@lock.IsLocked);
+ // @lock.Unlock();
+ // assertFalse(@lock.IsLocked);
+
+ // Assert.Throws<SynchronizationLockException>(() =>
@lock.Unlock());
+ //}
+
+ //[Test]
+ //[LuceneNetSpecific]
+ //public async Task TestReentryWithTasks()
+ //{
+ // var @lock = new ReentrantLock();
+ // assertFalse(@lock.IsLocked);
+
+ // var task1 = Task.Run(() =>
+ // {
+ // @lock.Lock();
+ // assertEquals(1, @lock.reentrantCount.Value);
+ // assertTrue(@lock.IsLocked);
+
+ // @lock.TryLock();
+ // assertEquals(2, @lock.reentrantCount.Value);
+ // assertTrue(@lock.IsLocked);
+
+ // @lock.Lock();
+ // assertEquals(3, @lock.reentrantCount.Value);
+ // assertTrue(@lock.IsLocked);
+
+ // // Simulate work
+ // Thread.Sleep(300);
+
+ // // Now unwind the stack
+ // @lock.Unlock();
+ // assertEquals(2, @lock.reentrantCount.Value);
+ // assertTrue(@lock.IsLocked);
+
+ // @lock.Unlock();
+ // assertEquals(1, @lock.reentrantCount.Value);
+ // assertTrue(@lock.IsLocked);
+
+ // //lock (@lock.syncLock)
+ // {
+ // @lock.Unlock();
+ // assertEquals(0, @lock.reentrantCount.Value);
+ // assertFalse(@lock.IsLocked);
+ // }
+
+ // Assert.Throws<SynchronizationLockException>(() =>
@lock.Unlock());
+ // });
+
+ // var task2 = Task.Run(async () =>
+ // {
+ // // Wait a bit to ensure task1 has started and locked
+ // await Task.Delay(100);
+
+ // // Try to lock
+ // @lock.Lock();
+
+ // // Simulate work
+ // Thread.Sleep(100);
+
+ // //lock (@lock.syncLock)
+ // {
+ // @lock.Unlock();
+ // assertEquals(0, @lock.reentrantCount.Value);
+ // assertFalse(@lock.IsLocked);
+ // }
+
+ // Assert.Throws<SynchronizationLockException>(() =>
@lock.Unlock());
+
+ // });
+
+ // await Task.WhenAll(task1, task2);
+ //}
+
+ //#if DEBUG
+ // private static void DoWorkReentrant(ReentrantLock @lock)
+ // {
+ // @lock.Lock();
+ // assertEquals(1, @lock.reentrantCount.Value);
+ // assertTrue(@lock.IsLocked);
+
+ // @lock.TryLock();
+ // assertEquals(2, @lock.reentrantCount.Value);
+ // assertTrue(@lock.IsLocked);
+
+ // @lock.Lock();
+ // assertEquals(3, @lock.reentrantCount.Value);
+ // assertTrue(@lock.IsLocked);
+
+ // // Simulate work
+ // Thread.Sleep(300);
+
+ // // Now unwind the stack
+ // @lock.Unlock();
+ // assertEquals(2, @lock.reentrantCount.Value);
+ // assertTrue(@lock.IsLocked);
+
+ // @lock.Unlock();
+ // assertEquals(1, @lock.reentrantCount.Value);
+ // assertTrue(@lock.IsLocked);
+
+ // lock (@lock.syncLock)
+ // {
+ // @lock.Unlock();
+ // assertEquals(0, @lock.reentrantCount.Value);
+ // assertFalse(@lock.IsLocked);
+ // }
+
+ // Assert.Throws<SynchronizationLockException>(() =>
@lock.Unlock());
+ // }
+
+
+ // [Test]
+ // [Slow]
+ // [LuceneNetSpecific]
+ // public void TestQueueCompletionWithTasks()
+ // {
+ // var @lock = new ReentrantLock();
+ // assertEquals(0, @lock.queueCount);
+ // assertEquals(0, @lock.dequeueCount);
+ // assertEquals(0, @lock.poolReturnCount);
+
+ // var tasks = new List<Task>();
+
+ // for (int i = 0; i < 10; i++)
+ // {
+ // tasks.Add(Task.Factory.StartNew((@lock) =>
+ // {
+ // DoWorkReentrant((ReentrantLock)@lock);
+ // }, @lock));
+ // }
+
+ // // Wait for all tasks to complete
+ // Task.WaitAll(tasks.ToArray());
+
+ // // Make sure everything that was queued has also been
dequeued
+ // assertTrue(@lock.queueCount > 0);
+ // assertEquals(@lock.queueCount, @lock.dequeueCount);
+ // assertEquals(@lock.queueCount, @lock.poolReturnCount);
+
+ // Console.WriteLine($"{nameof(@lock.queueCount)}:
{@lock.queueCount}");
+ // Console.WriteLine($"{nameof(@lock.dequeueCount)}:
{@lock.dequeueCount}");
+ // Console.WriteLine($"{nameof(@lock.poolReturnCount)}:
{@lock.poolReturnCount}");
+ // }
+ //#endif
+ }
+}
diff --git a/src/Lucene.Net/Index/DocumentsWriter.cs
b/src/Lucene.Net/Index/DocumentsWriter.cs
index c967e1160..64e833ee3 100644
--- a/src/Lucene.Net/Index/DocumentsWriter.cs
+++ b/src/Lucene.Net/Index/DocumentsWriter.cs
@@ -1,6 +1,7 @@
using J2N.Threading.Atomic;
using Lucene.Net.Diagnostics;
using Lucene.Net.Support.Threading;
+using Lucene.Net.Util;
using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
@@ -935,4 +936,4 @@ namespace Lucene.Net.Index
public ConcurrentQueue<IEvent> EventQueue => events;
}
-}
\ No newline at end of file
+}
diff --git a/src/Lucene.Net/Index/DocumentsWriterDeleteQueue.cs
b/src/Lucene.Net/Index/DocumentsWriterDeleteQueue.cs
index cd2c3b340..262ec9f8e 100644
--- a/src/Lucene.Net/Index/DocumentsWriterDeleteQueue.cs
+++ b/src/Lucene.Net/Index/DocumentsWriterDeleteQueue.cs
@@ -518,4 +518,4 @@ namespace Lucene.Net.Index
return "DWDQ: [ generation: " + generation + " ]";
}
}
-}
\ No newline at end of file
+}
diff --git a/src/Lucene.Net/Index/DocumentsWriterFlushControl.cs
b/src/Lucene.Net/Index/DocumentsWriterFlushControl.cs
index b26b07378..b26588b5a 100644
--- a/src/Lucene.Net/Index/DocumentsWriterFlushControl.cs
+++ b/src/Lucene.Net/Index/DocumentsWriterFlushControl.cs
@@ -2,9 +2,9 @@
using J2N.Threading.Atomic;
using Lucene.Net.Diagnostics;
using Lucene.Net.Support.Threading;
+using Lucene.Net.Util;
using System;
using System.Collections.Generic;
-using System.Threading;
using JCG = J2N.Collections.Generic;
namespace Lucene.Net.Index
@@ -418,7 +418,6 @@ namespace Lucene.Net.Index
UninterruptableMonitor.Enter(this);
try
{
- if (Debugging.AssertsEnabled)
Debugging.Assert(perThread.IsHeldByCurrentThread); // LUCENENET specific: Since
.NET Core doesn't use unfair locking, we need to ensure the current thread has
a lock before calling InternalTryCheckoutForFlush.
return perThread.flushPending ?
InternalTryCheckOutForFlush(perThread) : null;
}
finally
@@ -453,28 +452,34 @@ namespace Lucene.Net.Index
{
if (Debugging.AssertsEnabled)
{
- // LUCENENET specific - Since we need to mimic the unfair
behavior of ReentrantLock, we need to ensure that all threads that enter here
hold the lock.
- Debugging.Assert(perThread.IsHeldByCurrentThread);
Debugging.Assert(UninterruptableMonitor.IsEntered(this));
Debugging.Assert(perThread.flushPending);
}
try
{
- // LUCENENET specific - We removed the call to
perThread.TryLock() and the try-finally below as they are no longer needed.
-
- // We are pending so all memory is already moved to flushBytes
- if (perThread.IsInitialized)
+ if (perThread.TryLock())
{
- if (Debugging.AssertsEnabled)
Debugging.Assert(perThread.IsHeldByCurrentThread);
- DocumentsWriterPerThread dwpt;
- long bytes = perThread.bytesUsed; // do that before
- // replace!
- dwpt = DocumentsWriterPerThreadPool.Reset(perThread,
closed); // LUCENENET specific - made method static per CA1822
- if (Debugging.AssertsEnabled)
Debugging.Assert(!flushingWriters.ContainsKey(dwpt), "DWPT is already
flushing");
- // Record the flushing DWPT to reduce flushBytes in
doAfterFlush
- flushingWriters[dwpt] = bytes;
- numPending--; // write access synced
- return dwpt;
+ try
+ {
+ // We are pending so all memory is already moved to
flushBytes
+ if (perThread.IsInitialized)
+ {
+ if (Debugging.AssertsEnabled)
Debugging.Assert(perThread.IsHeldByCurrentThread);
+ DocumentsWriterPerThread dwpt;
+ long bytes = perThread.bytesUsed; // do that before
+ // replace!
+ dwpt =
DocumentsWriterPerThreadPool.Reset(perThread, closed); // LUCENENET specific -
made method static per CA1822
+ if (Debugging.AssertsEnabled)
Debugging.Assert(!flushingWriters.ContainsKey(dwpt), "DWPT is already
flushing");
+ // Record the flushing DWPT to reduce flushBytes
in doAfterFlush
+ flushingWriters[dwpt] = bytes;
+ numPending--; // write access synced
+ return dwpt;
+ }
+ }
+ finally
+ {
+ perThread.Unlock();
+ }
}
return null;
}
@@ -515,19 +520,12 @@ namespace Lucene.Net.Index
for (int i = 0; i < limit && numPending > 0; i++)
{
ThreadState next = perThreadPool.GetThreadState(i);
- if (next.flushPending && next.TryLock()) // LUCENENET
specific: Since .NET Core 2+ uses fair locking, we need to ensure we have a
lock before calling InternalTryCheckoutForFlush. See #
+ if (next.flushPending)
{
- try
+ DocumentsWriterPerThread dwpt =
TryCheckoutForFlush(next);
+ if (dwpt != null)
{
- DocumentsWriterPerThread dwpt =
TryCheckoutForFlush(next);
- if (dwpt != null)
- {
- return dwpt;
- }
- }
- finally
- {
- next.Unlock();
+ return dwpt;
}
}
}
@@ -793,10 +791,16 @@ namespace Lucene.Net.Index
Debugging.Assert(fullFlush);
Debugging.Assert(dwpt.deleteQueue !=
documentsWriter.deleteQueue);
}
- if (dwpt.NumDocsInRAM > 0)
+ // LUCENENET specific - Calling
DocumentsWriterPerThreadPool.Reset() without locking this
+ // can cause an issue inside of InternalTryCheckoutForFlush() when
2 threads enter this method
+ // when the call to Reset() happens between when
perThread.IsInitialized and
+ // DocumentsWriterPerThreadPool.Reset(perThread, closed), causing
it to return null.
+ // So, we lock outside of the check for NumDocsInRAM to ensure
that DocumentsWriterPerThreadPool.Reset()
+ // is called inside of the lock.
+ UninterruptableMonitor.Enter(this);
+ try
{
- UninterruptableMonitor.Enter(this);
- try
+ if (dwpt.NumDocsInRAM > 0)
{
if (!perThread.flushPending)
{
@@ -810,14 +814,14 @@ namespace Lucene.Net.Index
}
fullFlushBuffer.Add(flushingDWPT);
}
- finally
+ else
{
- UninterruptableMonitor.Exit(this);
+ DocumentsWriterPerThreadPool.Reset(perThread, closed); //
make this state inactive // LUCENENET specific - made method static per CA1822
}
}
- else
+ finally
{
- DocumentsWriterPerThreadPool.Reset(perThread, closed); // make
this state inactive // LUCENENET specific - made method static per CA1822
+ UninterruptableMonitor.Exit(this);
}
}
@@ -1058,4 +1062,4 @@ namespace Lucene.Net.Index
/// </summary>
public InfoStream InfoStream => infoStream;
}
-}
\ No newline at end of file
+}
diff --git a/src/Lucene.Net/Index/DocumentsWriterFlushQueue.cs
b/src/Lucene.Net/Index/DocumentsWriterFlushQueue.cs
index f8f2ac8ef..0d29d8a4f 100644
--- a/src/Lucene.Net/Index/DocumentsWriterFlushQueue.cs
+++ b/src/Lucene.Net/Index/DocumentsWriterFlushQueue.cs
@@ -373,4 +373,4 @@ namespace Lucene.Net.Index
protected internal override bool CanPublish => segment != null ||
failed;
}
}
-}
\ No newline at end of file
+}
diff --git a/src/Lucene.Net/Index/DocumentsWriterPerThreadPool.cs
b/src/Lucene.Net/Index/DocumentsWriterPerThreadPool.cs
index f567a455f..aa87b0fb6 100644
--- a/src/Lucene.Net/Index/DocumentsWriterPerThreadPool.cs
+++ b/src/Lucene.Net/Index/DocumentsWriterPerThreadPool.cs
@@ -415,25 +415,8 @@ namespace Lucene.Net.Index
return threadStates[ord];
}
- /// <summary>
- /// Returns the <see cref="ThreadState"/> with the minimum estimated
number of threads
- /// waiting to acquire its lock or <c>null</c> if no <see
cref="ThreadState"/>
- /// is yet visible to the calling thread.
- /// </summary>
- internal ThreadState MinContendedThreadState()
- {
- ThreadState minThreadState = null;
- int limit = numThreadStatesActive;
- for (int i = 0; i < limit; i++)
- {
- ThreadState state = threadStates[i];
- if (minThreadState is null || state.QueueLength <
minThreadState.QueueLength)
- {
- minThreadState = state;
- }
- }
- return minThreadState;
- }
+ // LUCENENET specific - Removed MinContendedThreadState() because it
has no callers
+ // and adds overhead to ReentrantLock that we don't need.
/// <summary>
/// Returns the number of currently deactivated <see
cref="ThreadState"/> instances.
@@ -475,4 +458,4 @@ namespace Lucene.Net.Index
threadState.Deactivate();
}
}
-}
\ No newline at end of file
+}
diff --git a/src/Lucene.Net/Support/Threading/ReentrantLock.cs
b/src/Lucene.Net/Support/Threading/ReentrantLock.cs
index dd1359b1a..8d787e83f 100644
--- a/src/Lucene.Net/Support/Threading/ReentrantLock.cs
+++ b/src/Lucene.Net/Support/Threading/ReentrantLock.cs
@@ -1,4 +1,7 @@
-using System.Threading;
+using System;
+using System.Runtime.CompilerServices;
+using System.Threading;
+#nullable enable
namespace Lucene.Net.Support.Threading
{
@@ -19,56 +22,80 @@ namespace Lucene.Net.Support.Threading
* limitations under the License.
*/
+ /// <summary>
+ /// A lock that uses an unfair locking strategy, similar to how it works
in Java. This lock is unfair
+ /// in that it will aquire the lock even if there are any threads waiting
on <see cref="Lock()"/>.
+ /// <para/>
+ /// This implementation also does not use FIFO order when waiting on <see
cref="Lock()"/>. Each queued thread will continue
+ /// to acquire the lock continually, but yield between each iteration. So,
any waiting thread could be next to
+ /// aquire the lock. This differs from how it works in Java, but the
overhead of fixing this behavior with a queue
+ /// is probably not worth the cost.
+ /// </summary>
internal class ReentrantLock
{
- // .NET Port: lock object used to emulate ReentrantLock
private readonly object _lock = new object();
- // .NET Port: Estimated monitor queue length
- private int _queueLength = 0;
-
- // .NET Port: mimic ReentrantLock -- Monitor is re-entrant
+ /// <summary>
+ /// Tries to aquire the lock. If the lock is not available, the thread
will block
+ /// until it can obtain the lock.
+ /// <para/>
+ /// FIFO order is not respected on waiting locks. Also, threads that
are waiting
+ /// are not allowed to sleep. Instead, they call <see
cref="Thread.Yield()"/> and
+ /// one of them will acquire the lock as soon as there are no other
callers to
+ /// <see cref="Lock()"/> or <see cref="TryLock()"/>.
+ /// <para/>
+ /// Threads that call <see cref="Lock()"/> and <see cref="TryLock()"/>
are
+ /// allowed to obtain the lock even if there are other threads waiting
for it.
+ /// This "barging" behavior is similar to how ReentryLock works in
Java.
+ /// </summary>
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
public void Lock()
{
- // note about queue length: in java's ReentrantLock,
getQueueLength() returns the number
- // of threads waiting on entering the lock. So here, we're
incrementing the count before trying to enter,
- // meaning that until enter has completed the thread is waiting so
the queue is incremented. Once
- // we enter the lock, then we immediately decrement it because
that thread is no longer in the queue.
- // Due to race conditions, the queue length is an estimate only.
- Interlocked.Increment(ref _queueLength);
- UninterruptableMonitor.Enter(_lock);
- Interlocked.Decrement(ref _queueLength);
+ while (!UninterruptableMonitor.TryEnter(_lock))
+ Thread.Yield(); // Allow non-queued threads to win
}
- // .NET Port: mimic ReentrantLock -- Monitor is re-entrant
- public void Unlock()
+ /// <summary>
+ /// NOTE: This is not the full implementation that correctly throws
<see cref="ThreadInterruptedException"/>
+ /// after <see cref="Thread.Interrupt()"/> is called. Since this is
only used in tests and Lucene.NET doesn't
+ /// support <see cref="Thread.Interrupt()"/>, this is okay. But if
this method is ever used in production scenarios,
+ /// the approach used for this lock needs to be reevaluated.
+ /// </summary>
+ [Obsolete("WARNING: This does not correctly throw
ThreadInterruptedException and must be fixed prior to production use. This is
only sufficient for testing.")]
+ public void LockInterruptibly()
{
- UninterruptableMonitor.Exit(_lock);
+ while (!Monitor.TryEnter(_lock))
+ Thread.Yield(); // Allow non-queued threads to win
}
- public bool TryLock()
+ /// <summary>
+ /// Releases the lock when called the same number of times as <see
cref="Lock()"/>, <see cref="LockInterruptibly()"/>
+ /// and <see cref="TryLock()"/> for the current task/thread.
+ /// </summary>
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public void Unlock()
{
- Interlocked.Increment(ref _queueLength);
- bool success = UninterruptableMonitor.TryEnter(_lock);
- Interlocked.Decrement(ref _queueLength);
-
- return success;
+ UninterruptableMonitor.Exit(_lock);
}
- public int QueueLength
+ /// <summary>
+ /// Tries to aquire the lock and immediately returns a boolean value
indicating
+ /// whether the lock was obtained.
+ /// <para/>
+ /// Threads that call <see cref="Lock()"/> and <see cref="TryLock()"/>
are
+ /// allowed to obtain the lock even if there are other threads waiting
for it.
+ /// This "barging" behavior is similar to how ReentryLock works in
Java.
+ /// </summary>
+ /// <returns><c>true</c> if the lock was obtained successfully;
otherwise, <c>false</c>.</returns>
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public bool TryLock()
{
- get
- {
- // hold onto the estimate for the length of this method
- int estimate = _queueLength;
-
- // should never be < 0, but just in case, as a negative number
doesn't make sense.
- return estimate <= 0 ? 0 : estimate;
- }
+ return UninterruptableMonitor.TryEnter(_lock);
}
- public bool HasQueuedThreads => _queueLength > 0;
-
+ /// <summary>
+ /// Returns a value indicating whether the lock is held by the current
thread.
+ /// </summary>
public bool IsHeldByCurrentThread =>
UninterruptableMonitor.IsEntered(_lock);
}
-}
\ No newline at end of file
+}
diff --git a/src/Lucene.Net/Support/Threading/UninterruptableMonitor.cs
b/src/Lucene.Net/Support/Threading/UninterruptableMonitor.cs
index d145f1345..a9eeeaef1 100644
--- a/src/Lucene.Net/Support/Threading/UninterruptableMonitor.cs
+++ b/src/Lucene.Net/Support/Threading/UninterruptableMonitor.cs
@@ -1,4 +1,5 @@
using System;
+using System.Runtime.CompilerServices;
using System.Threading;
namespace Lucene.Net.Support.Threading
@@ -24,19 +25,63 @@ namespace Lucene.Net.Support.Threading
/// A drop-in replacement for <see cref="Monitor"/> that doesn't throw
<see cref="ThreadInterruptedException"/>
/// when entering locks, but defers the excepetion until a wait or sleep
occurs. This is to mimic the behavior in Java,
/// which does not throw when entering a lock.
+ /// <para/>
+ /// <b>NOTE:</b> this is just a best effort. The BCL and other libraries
we depend
+ /// on don't take such measures, so any call to an API that we don't own
could result
+ /// in a <see cref="System.Threading.ThreadInterruptedException"/> if it
attempts to
+ /// aquire a lock. It is not practical to put a try/catch block around
every 3rd party
+ /// API call that attempts to lock. As such, Lucene.NET does not support
+ /// <see cref="Thread.Interrupt()"/> and using it is discouraged.
+ /// See https://github.com/apache/lucenenet/issues/526.
/// </summary>
internal static class UninterruptableMonitor
{
+ /// <summary>
+ /// Acquires an exclusive lock on the specified object, and atomically
sets a
+ /// value that indicates whether the lock was taken. See
+ /// <see cref="Monitor.Enter(object, ref bool)"/> for more details.
+ /// <para/>
+ /// If the lock is interrupted, this method will not throw a
+ /// <see cref="System.Threading.ThreadInterruptedException"/>. Instead,
+ /// it will reset the interrupt state. This matches the behavior of the
+ /// <c>synchronized</c> keyword in Java, which never throws when the
current
+ /// thread is in an interrupted state. It allows us to catch
+ /// <see cref="System.Threading.ThreadInterruptedException"/> in a
specific part
+ /// of the application, rather than allowing it to be thrown anywhere
we atempt
+ /// to lock.
+ /// <para/>
+ /// <b>NOTE:</b> this is just a best effort. The BCL and other
libraries we depend
+ /// on don't take such measures, so any call to an API that we don't
own could result
+ /// in a <see cref="System.Threading.ThreadInterruptedException"/> if
it attempts to
+ /// aquire a lock. It is not practical to put a try/catch block around
every 3rd party
+ /// API call that attempts to lock. As such, Lucene.NET does not
support
+ /// <see cref="Thread.Interrupt()"/> and using it is discouraged.
+ /// See https://github.com/apache/lucenenet/issues/526.
+ /// </summary>
public static void Enter(object obj, ref bool lockTaken)
{
// enter the lock and ignore any
System.Threading.ThreadInterruptedException
try
{
- Monitor.Enter(obj, ref lockTaken);
+ Monitor.Enter(obj, ref lockTaken); // Fast path - don't
allocate retry on stack in this case
}
- catch (Exception ie) when(ie.IsInterruptedException())
+ catch (Exception ie) when (ie.IsInterruptedException())
{
- RetryEnter(obj, ref lockTaken);
+ do
+ {
+ try
+ {
+ // The interrupted exception may have already cleared
the flag, and this will
+ // succeed without any more exceptions
+ Monitor.Enter(obj, ref lockTaken);
+ break;
+ }
+ catch (Exception e) when (e.IsInterruptedException())
+ {
+ // try again until we succeed, since an interrupt
could have happened since it was cleared
+ }
+ }
+ while (true);
// The lock has been obtained, now reset the interrupted
status for the
// current thread
@@ -44,30 +89,51 @@ namespace Lucene.Net.Support.Threading
}
}
- private static void RetryEnter(object obj, ref bool lockTaken)
- {
- try
- {
- // An interrupted exception may have already cleared the flag,
and this will succeed without any more excpetions
- Monitor.Enter(obj, ref lockTaken);
- }
- catch (Exception ie) when (ie.IsInterruptedException())
- {
- // try again until we succeed, since an interrupt could have
happened since it was cleared
- RetryEnter(obj, ref lockTaken);
- }
- }
-
+ /// <summary>
+ /// Acquires an exclusive lock on the specified object. See
+ /// <see cref="Monitor.Enter(object)"/> for more details.
+ /// <para/>
+ /// If the lock is interrupted, this method will not throw a
+ /// <see cref="System.Threading.ThreadInterruptedException"/>. Instead,
+ /// it will reset the interrupt state. This matches the behavior of the
+ /// <c>synchronized</c> keyword in Java, which never throws when the
current
+ /// thread is in an interrupted state. It allows us to catch
+ /// <see cref="System.Threading.ThreadInterruptedException"/> in a
specific part
+ /// of the application, rather than allowing it to be thrown anywhere
we atempt
+ /// to lock.
+ /// <para/>
+ /// <b>NOTE:</b> this is just a best effort. The BCL and other
libraries we depend
+ /// on don't take such measures, so any call to an API that we don't
own could result
+ /// in a <see cref="System.Threading.ThreadInterruptedException"/> if
it attempts to
+ /// aquire a lock. It is not practical to put a try/catch block around
every 3rd party
+ /// API call that attempts to lock. As such, Lucene.NET does not
support
+ /// <see cref="Thread.Interrupt()"/> and using it is discouraged.
+ /// See https://github.com/apache/lucenenet/issues/526.
+ /// </summary>
public static void Enter(object obj)
{
// enter the lock and ignore any
System.Threading.ThreadInterruptedException
try
{
- Monitor.Enter(obj);
+ Monitor.Enter(obj); // Fast path - don't allocate retry on
stack in this case
}
catch (Exception ie) when (ie.IsInterruptedException())
{
- RetryEnter(obj);
+ do
+ {
+ try
+ {
+ // The interrupted exception may have already cleared
the flag, and this will
+ // succeed without any more exceptions
+ Monitor.Enter(obj);
+ break;
+ }
+ catch (Exception e) when (e.IsInterruptedException())
+ {
+ // try again until we succeed, since an interrupt
could have happened since it was cleared
+ }
+ }
+ while (true);
// The lock has been obtained, now reset the interrupted
status for the
// current thread
@@ -75,90 +141,181 @@ namespace Lucene.Net.Support.Threading
}
}
- private static void RetryEnter(object obj)
- {
- try
- {
- // An interrupted exception may have already cleared the flag,
and this will succeed without any more excpetions
- Monitor.Enter(obj);
- }
- catch (Exception ie) when (ie.IsInterruptedException())
- {
- // try again until we succeed, since an interrupt could have
happened since it was cleared
- RetryEnter(obj);
- }
- }
-
+ /// <summary>
+ /// Cascades the call to <see cref="Monitor.Exit(object)"/>.
+ /// <para/>
+ /// Releases an exclusive lock on the specified object.
+ /// </summary>
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
public static void Exit(object obj)
{
Monitor.Exit(obj);
}
+ /// <summary>
+ /// Cascades the call to <see cref="Monitor.IsEntered(object)"/>.
+ /// <para/>
+ /// Determines whether the current thread holds the lock on the
+ /// specified object.
+ /// </summary>
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
public static bool IsEntered(object obj)
{
return Monitor.IsEntered(obj);
}
+ /// <summary>
+ /// Cascades the call to <see cref="Monitor.TryEnter(object)"/>.
+ /// <para/>
+ /// Attempts to acquire an exclusive lock on the specified object.
+ /// </summary>
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
public static bool TryEnter(object obj)
{
return Monitor.TryEnter(obj);
}
+ /// <summary>
+ /// Cascades the call to <see cref="Monitor.TryEnter(object, ref
bool)"/>.
+ /// <para/>
+ /// Attempts to acquire an exclusive lock on the specified object, and
atomically
+ /// sets a value that indicates whether the lock was taken.
+ /// </summary>
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
public static void TryEnter(object obj, ref bool lockTaken)
{
Monitor.TryEnter(obj, ref lockTaken);
}
+ /// <summary>
+ /// Cascades the call to <see cref="Monitor.TryEnter(object, int)"/>.
+ /// <para/>
+ /// Attempts, for the specified number of milliseconds, to acquire an
+ /// exclusive lock on the specified object.
+ /// </summary>
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
public static bool TryEnter(object obj, int millisecondsTimeout)
{
return Monitor.TryEnter(obj, millisecondsTimeout);
}
+ /// <summary>
+ /// Cascades the call to <see cref="Monitor.TryEnter(object,
TimeSpan)"/>.
+ /// <para/>
+ /// Attempts, for the specified amount of time, to acquire an exclusive
+ /// lock on the specified object.
+ /// </summary>
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
public static bool TryEnter(object obj, TimeSpan timeout)
{
return Monitor.TryEnter(obj, timeout);
}
+ /// <summary>
+ /// Cascades the call to <see cref="Monitor.TryEnter(object, int, ref
bool)"/>.
+ /// <para/>
+ /// Attempts, for the specified number of milliseconds, to acquire an
exclusive lock on the specified
+ /// object, and atomically sets a value that indicates whether the
lock was taken.
+ /// </summary>
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
public static void TryEnter(object obj, int millisecondsTimeout, ref
bool lockTaken)
{
Monitor.TryEnter(obj, millisecondsTimeout, ref lockTaken);
}
+ /// <summary>
+ /// Cascades the call to <see cref="Monitor.TryEnter(object, TimeSpan,
ref bool)"/>.
+ /// <para/>
+ /// Attempts, for the specified amount of time, to acquire an
exclusive lock on the specified object,
+ /// and atomically sets a value that indicates whether the lock was
taken.
+ /// </summary>
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
public static void TryEnter(object obj, TimeSpan timeout, ref bool
lockTaken)
{
Monitor.TryEnter(obj, timeout, ref lockTaken);
}
+ /// <summary>
+ /// Cascades the call to <see cref="Monitor.Pulse(object)"/>.
+ /// <para/>
+ /// Notifies a thread in the waiting queue of a change in the locked
object's state.
+ /// </summary>
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
public static void Pulse(object obj)
{
Monitor.Pulse(obj);
}
+ /// <summary>
+ /// Cascades the call to <see cref="Monitor.PulseAll(object)"/>.
+ /// <para/>
+ /// Notifies all waiting threads of a change in the object's state.
+ /// </summary>
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
public static void PulseAll(object obj)
{
Monitor.PulseAll(obj);
}
+ /// <summary>
+ /// Cascades the call to <see cref="Monitor.Wait(object)"/>.
+ /// <para/>
+ /// Releases the lock on an object and blocks the current thread until
it reacquires the lock.
+ /// </summary>
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
public static void Wait(object obj)
{
Monitor.Wait(obj);
}
+ /// <summary>
+ /// Cascades the call to <see cref="Monitor.Wait(object, int)"/>.
+ /// <para/>
+ /// Releases the lock on an object and blocks the current thread until
it reacquires the lock.
+ /// If the specified time-out interval elapses, the thread enters the
ready queue.
+ /// </summary>
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
public static void Wait(object obj, int millisecondsTimeout)
{
Monitor.Wait(obj, millisecondsTimeout);
}
+ /// <summary>
+ /// Cascades the call to <see cref="Monitor.Wait(object, TimeSpan)"/>.
+ /// <para/>
+ /// Releases the lock on an object and blocks the current thread until
it reacquires the lock.
+ /// If the specified time-out interval elapses, the thread enters the
ready queue.
+ /// </summary>
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
public static void Wait(object obj, TimeSpan timeout)
{
Monitor.Wait(obj, timeout);
}
+ /// <summary>
+ /// Cascades the call to <see cref="Monitor.Wait(object, int, bool)"/>.
+ /// <para/>
+ /// Releases the lock on an object and blocks the current thread until
it
+ /// reacquires the lock. If the specified time-out interval elapses,
the
+ /// thread enters the ready queue. This method also specifies whether
the
+ /// synchronization domain for the context (if in a synchronized
context)
+ /// is exited before the wait and reacquired afterward.
+ /// </summary>
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
public static void Wait(object obj, int millisecondsTimeout, bool
exitContext)
{
Monitor.Wait(obj, millisecondsTimeout, exitContext);
}
+ /// <summary>
+ /// Cascades the call to <see cref="Monitor.Wait(object, TimeSpan,
bool)"/>.
+ /// <para/>
+ /// Releases the lock on an object and blocks the current thread until
it reacquires the lock
+ /// If the specified time-out interval elapses, the thread enters the
ready queue. This method
+ /// also specifies whether the synchronization domain for the context
(if in a synchronized
+ /// context) is exited before the wait and reacquired afterward.
+ /// </summary>
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
public static void Wait(object obj, TimeSpan timeout, bool exitContext)
{
Monitor.Wait(obj, timeout, exitContext);