This is an automated email from the ASF dual-hosted git repository.

nightowl888 pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/lucenenet.git

commit dd89a4fcf42f76c54c44c2decae8b4811c6e43e5
Author: Shad Storhaug <[email protected]>
AuthorDate: Wed Oct 13 12:35:13 2021 +0700

    Lucene.Net.Support.Threading: Created UninterruptableMonitor class to 
handle entering locks without throwing 
System.Threading.ThreadInterruptedException (which is what happens in Java)
---
 .../Threading/TestUninterruptableMonitor.cs        | 313 +++++++++++++++++++++
 .../Support/Threading/UninterruptableMonitor.cs    | 167 +++++++++++
 2 files changed, 480 insertions(+)

diff --git 
a/src/Lucene.Net.Tests/Support/Threading/TestUninterruptableMonitor.cs 
b/src/Lucene.Net.Tests/Support/Threading/TestUninterruptableMonitor.cs
new file mode 100644
index 0000000..8da4b04
--- /dev/null
+++ b/src/Lucene.Net.Tests/Support/Threading/TestUninterruptableMonitor.cs
@@ -0,0 +1,313 @@
+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.Threading;
+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 TestUninterruptableMonitor : LuceneTestCase
+    {
+        private class TransactionlThreadInterrupt : ThreadJob
+        {
+            private static AtomicInt32 transactionNumber = new AtomicInt32(0);
+
+            // Share locks between threads
+            private static readonly object lock1 = new object();
+            private static readonly object lock2 = new object();
+            private static readonly object lock3 = new object();
+
+            internal volatile bool failed;
+            internal volatile bool finish;
+
+            internal volatile bool allowInterrupt = false;
+            internal volatile bool transactionInProgress = false;
+            
+
+            public override void Run()
+            {
+                while (!finish)
+                {
+                    try
+                    {
+                        TransactionalMethod();
+                        TransactionalMethod();
+                        InterruptableMethod();
+                        TransactionalMethod();
+                        InterruptableMethod();
+
+                        // Make sure these don't throw 
System.Threading.ThreadInterruptedException
+                        
Assert.IsFalse(UninterruptableMonitor.IsEntered(lock1));
+                        
Assert.IsFalse(UninterruptableMonitor.IsEntered(lock2));
+                        
Assert.IsFalse(UninterruptableMonitor.IsEntered(lock3));
+
+                        if (UninterruptableMonitor.TryEnter(lock1))
+                        {
+                            try
+                            {
+                                
Assert.IsTrue(UninterruptableMonitor.IsEntered(lock1));
+                            }
+                            finally
+                            {
+                                UninterruptableMonitor.Exit(lock1);
+                            }
+                        }
+
+                        allowInterrupt = true;
+                    }
+                    catch (Util.ThreadInterruptedException re)
+                    {
+                        // Success - we received the correct exception type
+                        Console.WriteLine("TEST: got interrupt");
+                        Console.WriteLine(GetToStringFrom(re));
+
+                        Exception e = re.InnerException;
+                        Assert.IsTrue(e is 
System.Threading.ThreadInterruptedException);
+
+                        // Make sure we didn't interrupt in the middle of a 
transaction
+                        Assert.IsFalse(transactionInProgress);
+
+                        if (finish)
+                        {
+                            break;
+                        }
+                    }
+                    catch (Exception t) when (t.IsThrowable())
+                    {
+                        Console.WriteLine("FAILED; unexpected exception");
+                        Console.WriteLine(GetToStringFrom(t));
+
+                        // Make sure we didn't error in the middle of a 
transaction
+                        Assert.IsFalse(transactionInProgress);
+
+                        failed = true;
+                        break;
+                    }
+                }
+            }
+
+
+            private void TransactionalMethod()
+            {
+                Assert.IsFalse(transactionInProgress, "The prior transaction 
failed to complete (was interrupted).");
+                transactionInProgress = true;
+                int transactionId = transactionNumber.IncrementAndGet();
+                if (Verbose)
+                {
+                    Console.WriteLine($"transaction STARTED: {transactionId}");
+                }
+
+                // Nested lock test
+                UninterruptableMonitor.Enter(lock1);
+                try
+                {
+                    if (Verbose)
+                    {
+                        Console.WriteLine("acquired lock1 successfully");
+                        Console.WriteLine("sleeping...");
+                    }
+
+                    // Use SpinWait instead of Sleep to demonstrate the 
+                    // effect of calling Interrupt on a running thread.
+                    Thread.SpinWait(1000000);
+
+                    UninterruptableMonitor.Enter(lock2);
+                    try
+                    {
+                        if (Verbose)
+                        {
+                            Console.WriteLine("acquired lock2 successfully");
+                            Console.WriteLine("sleeping...");
+                        }
+
+                        // Use SpinWait instead of Sleep to demonstrate the 
+                        // effect of calling Interrupt on a running thread.
+                        Thread.SpinWait(1000000);
+                    }
+                    finally
+                    {
+                        UninterruptableMonitor.Exit(lock2);
+                    }
+                }
+                finally
+                {
+                    UninterruptableMonitor.Exit(lock1);
+                }
+
+                // inline lock test
+                UninterruptableMonitor.Enter(lock3);
+                try
+                {
+                    if (Verbose)
+                    {
+                        Console.WriteLine("acquired lock3 successfully");
+                        Console.WriteLine("sleeping...");
+                    }
+
+                    // Use SpinWait instead of Sleep to demonstrate the 
+                    // effect of calling Interrupt on a running thread.
+                    Thread.SpinWait(1000000);
+                }
+                finally
+                {
+                    UninterruptableMonitor.Exit(lock3);
+                }
+
+                if (Verbose)
+                {
+                    Console.WriteLine($"transaction COMPLETED: 
{transactionId}");
+                }
+
+                transactionInProgress = false;
+            }
+
+            private void InterruptableMethod()
+            {
+                UninterruptableMonitor.Enter(lock1);
+                try
+                {
+                    if (Verbose)
+                    {
+                        Console.WriteLine("acquired lock1 successfully");
+                        Console.WriteLine("sleeping...");
+                    }
+
+                    try
+                    {
+                        UninterruptableMonitor.Wait(lock1, 
TimeSpan.FromMilliseconds(200));
+                    }
+                    catch (Exception ie) when (ie.IsInterruptedException())
+                    {
+                        throw new Util.ThreadInterruptedException(ie);
+                    }
+                }
+                finally
+                {
+                    UninterruptableMonitor.Exit(lock1);
+                }
+            }
+
+            /// <summary>
+            /// Safely gets the ToString() of an exception while ignoring any 
System.Threading.ThreadInterruptedException and retrying.
+            /// </summary>
+            private string GetToStringFrom(Exception exception)
+            {
+                // Clear interrupt state:
+                try
+                {
+                    Thread.Sleep(0);
+                }
+                catch (Exception ie) when (ie.IsInterruptedException())
+                {
+                    // ignore
+                }
+                try
+                {
+                    return exception.ToString();
+                }
+                catch (Exception ie) when (ie.IsInterruptedException())
+                {
+                    return GetToStringFrom(exception);
+                }
+            }
+        }
+
+        [Test, LuceneNetSpecific]
+        [Slow]
+        public virtual void TestThreadInterrupt()
+        {
+            TransactionlThreadInterrupt t = new TransactionlThreadInterrupt();
+            t.IsBackground = (true);
+            t.Start();
+
+            // issue 300 interrupts to child thread
+            int numInterrupts = AtLeast(300);
+            int i = 0;
+            while (i < numInterrupts)
+            {
+                // TODO: would be nice to also sometimes interrupt the
+                // CMS merge threads too ...
+                Thread.Sleep(10);
+                if (t.allowInterrupt)
+                {
+                    i++;
+                    t.Interrupt();
+                }
+                if (!t.IsAlive)
+                {
+                    break;
+                }
+            }
+            t.finish = true;
+            t.Join();
+
+            Assert.IsFalse(t.failed);
+            Assert.IsFalse(t.transactionInProgress);
+        }
+
+        [Test, LuceneNetSpecific]
+        [Slow]
+        public virtual void TestTwoThreadsInterrupt()
+        {
+            TransactionlThreadInterrupt t1 = new TransactionlThreadInterrupt();
+            t1.IsBackground = (true);
+            t1.Start();
+
+            TransactionlThreadInterrupt t2 = new TransactionlThreadInterrupt();
+            t2.IsBackground = (true);
+            t2.Start();
+
+            // issue 300 interrupts to child thread
+            int numInterrupts = AtLeast(300);
+            int i = 0;
+            while (i < numInterrupts)
+            {
+                // TODO: would be nice to also sometimes interrupt the
+                // CMS merge threads too ...
+                Thread.Sleep(10);
+                TransactionlThreadInterrupt t = Random.NextBoolean() ? t1 : t2;
+                if (t.allowInterrupt)
+                {
+                    i++;
+                    t.Interrupt();
+                }
+                if (!t1.IsAlive && !t2.IsAlive)
+                {
+                    break;
+                }
+            }
+            t1.finish = true;
+            t2.finish = true;
+            t1.Join();
+            t2.Join();
+
+            Assert.IsFalse(t1.failed);
+            Assert.IsFalse(t2.failed);
+            Assert.IsFalse(t1.transactionInProgress);
+            Assert.IsFalse(t2.transactionInProgress);
+        }
+    }
+}
\ No newline at end of file
diff --git a/src/Lucene.Net/Support/Threading/UninterruptableMonitor.cs 
b/src/Lucene.Net/Support/Threading/UninterruptableMonitor.cs
new file mode 100644
index 0000000..d145f13
--- /dev/null
+++ b/src/Lucene.Net/Support/Threading/UninterruptableMonitor.cs
@@ -0,0 +1,167 @@
+using System;
+using System.Threading;
+
+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.
+     */
+
+    /// <summary>
+    /// 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.
+    /// </summary>
+    internal static class UninterruptableMonitor
+    {
+        public static void Enter(object obj, ref bool lockTaken)
+        {
+            // enter the lock and ignore any 
System.Threading.ThreadInterruptedException
+            try
+            {
+                Monitor.Enter(obj, ref lockTaken);
+            }
+            catch (Exception ie) when(ie.IsInterruptedException())
+            {
+                RetryEnter(obj, ref lockTaken);
+
+                // The lock has been obtained, now reset the interrupted 
status for the
+                // current thread
+                Thread.CurrentThread.Interrupt();
+            }
+        }
+
+        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);
+            }
+        }
+
+        public static void Enter(object obj)
+        {
+            // enter the lock and ignore any 
System.Threading.ThreadInterruptedException
+            try
+            {
+                Monitor.Enter(obj);
+            }
+            catch (Exception ie) when (ie.IsInterruptedException())
+            {
+                RetryEnter(obj);
+
+                // The lock has been obtained, now reset the interrupted 
status for the
+                // current thread
+                Thread.CurrentThread.Interrupt();
+            }
+        }
+
+        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);
+            }
+        }
+
+        public static void Exit(object obj)
+        {
+            Monitor.Exit(obj);
+        }
+
+        public static bool IsEntered(object obj)
+        {
+            return Monitor.IsEntered(obj);
+        }
+
+        public static bool TryEnter(object obj)
+        {
+            return Monitor.TryEnter(obj);
+        }
+
+        public static void TryEnter(object obj, ref bool lockTaken)
+        {
+            Monitor.TryEnter(obj, ref lockTaken);
+        }
+
+        public static bool TryEnter(object obj, int millisecondsTimeout)
+        {
+            return Monitor.TryEnter(obj, millisecondsTimeout);
+        }
+
+        public static bool TryEnter(object obj, TimeSpan timeout)
+        {
+            return Monitor.TryEnter(obj, timeout);
+        }
+
+        public static void TryEnter(object obj, int millisecondsTimeout, ref 
bool lockTaken)
+        {
+            Monitor.TryEnter(obj, millisecondsTimeout, ref lockTaken);
+        }
+
+        public static void TryEnter(object obj, TimeSpan timeout, ref bool 
lockTaken)
+        {
+            Monitor.TryEnter(obj, timeout, ref lockTaken);
+        }
+
+        public static void Pulse(object obj)
+        {
+            Monitor.Pulse(obj);
+        }
+
+        public static void PulseAll(object obj)
+        {
+            Monitor.PulseAll(obj);
+        }
+
+        public static void Wait(object obj)
+        {
+            Monitor.Wait(obj);
+        }
+
+        public static void Wait(object obj, int millisecondsTimeout)
+        {
+            Monitor.Wait(obj, millisecondsTimeout);
+        }
+
+        public static void Wait(object obj, TimeSpan timeout)
+        {
+            Monitor.Wait(obj, timeout);
+        }
+
+        public static void Wait(object obj, int millisecondsTimeout, bool 
exitContext)
+        {
+            Monitor.Wait(obj, millisecondsTimeout, exitContext);
+        }
+
+        public static void Wait(object obj, TimeSpan timeout, bool exitContext)
+        {
+            Monitor.Wait(obj, timeout, exitContext);
+        }
+    }
+}

Reply via email to