diff --git a/src/Lucene.Net/Support/Threading/UninterruptableMonitor.cs b/src/Lucene.Net/Support/Threading/UninterruptableMonitor.cs index a6196e0a6b..a9eeeaef17 100644 --- a/src/Lucene.Net/Support/Threading/UninterruptableMonitor.cs +++ b/src/Lucene.Net/Support/Threading/UninterruptableMonitor.cs @@ -25,19 +25,63 @@ namespace Lucene.Net.Support.Threading /// A drop-in replacement for that doesn't throw /// 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. + /// + /// NOTE: 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 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 + /// and using it is discouraged. + /// See https://github.com/apache/lucenenet/issues/526. /// internal static class UninterruptableMonitor { + /// + /// Acquires an exclusive lock on the specified object, and atomically sets a + /// value that indicates whether the lock was taken. See + /// for more details. + /// + /// If the lock is interrupted, this method will not throw a + /// . Instead, + /// it will reset the interrupt state. This matches the behavior of the + /// synchronized keyword in Java, which never throws when the current + /// thread is in an interrupted state. It allows us to catch + /// in a specific part + /// of the application, rather than allowing it to be thrown anywhere we atempt + /// to lock. + /// + /// NOTE: 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 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 + /// and using it is discouraged. + /// See https://github.com/apache/lucenenet/issues/526. + /// 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 @@ -45,30 +89,51 @@ public static void Enter(object obj, ref bool lockTaken) } } - 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); - } - } - + /// + /// Acquires an exclusive lock on the specified object. See + /// for more details. + /// + /// If the lock is interrupted, this method will not throw a + /// . Instead, + /// it will reset the interrupt state. This matches the behavior of the + /// synchronized keyword in Java, which never throws when the current + /// thread is in an interrupted state. It allows us to catch + /// in a specific part + /// of the application, rather than allowing it to be thrown anywhere we atempt + /// to lock. + /// + /// NOTE: 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 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 + /// and using it is discouraged. + /// See https://github.com/apache/lucenenet/issues/526. + /// 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 @@ -76,104 +141,180 @@ public static void Enter(object obj) } } - 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); - } - } - + /// + /// Cascades the call to . + /// + /// Releases an exclusive lock on the specified object. + /// [MethodImpl(MethodImplOptions.AggressiveInlining)] public static void Exit(object obj) { Monitor.Exit(obj); } + /// + /// Cascades the call to . + /// + /// Determines whether the current thread holds the lock on the + /// specified object. + /// [MethodImpl(MethodImplOptions.AggressiveInlining)] public static bool IsEntered(object obj) { return Monitor.IsEntered(obj); } + /// + /// Cascades the call to . + /// + /// Attempts to acquire an exclusive lock on the specified object. + /// [MethodImpl(MethodImplOptions.AggressiveInlining)] public static bool TryEnter(object obj) { return Monitor.TryEnter(obj); } + /// + /// Cascades the call to . + /// + /// Attempts to acquire an exclusive lock on the specified object, and atomically + /// sets a value that indicates whether the lock was taken. + /// [MethodImpl(MethodImplOptions.AggressiveInlining)] public static void TryEnter(object obj, ref bool lockTaken) { Monitor.TryEnter(obj, ref lockTaken); } + /// + /// Cascades the call to . + /// + /// Attempts, for the specified number of milliseconds, to acquire an + /// exclusive lock on the specified object. + /// [MethodImpl(MethodImplOptions.AggressiveInlining)] public static bool TryEnter(object obj, int millisecondsTimeout) { return Monitor.TryEnter(obj, millisecondsTimeout); } + /// + /// Cascades the call to . + /// + /// Attempts, for the specified amount of time, to acquire an exclusive + /// lock on the specified object. + /// [MethodImpl(MethodImplOptions.AggressiveInlining)] public static bool TryEnter(object obj, TimeSpan timeout) { return Monitor.TryEnter(obj, timeout); } + /// + /// Cascades the call to . + /// + /// 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. + /// [MethodImpl(MethodImplOptions.AggressiveInlining)] public static void TryEnter(object obj, int millisecondsTimeout, ref bool lockTaken) { Monitor.TryEnter(obj, millisecondsTimeout, ref lockTaken); } + /// + /// Cascades the call to . + /// + /// 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. + /// [MethodImpl(MethodImplOptions.AggressiveInlining)] public static void TryEnter(object obj, TimeSpan timeout, ref bool lockTaken) { Monitor.TryEnter(obj, timeout, ref lockTaken); } + /// + /// Cascades the call to . + /// + /// Notifies a thread in the waiting queue of a change in the locked object's state. + /// [MethodImpl(MethodImplOptions.AggressiveInlining)] public static void Pulse(object obj) { Monitor.Pulse(obj); } + /// + /// Cascades the call to . + /// + /// Notifies all waiting threads of a change in the object's state. + /// [MethodImpl(MethodImplOptions.AggressiveInlining)] public static void PulseAll(object obj) { Monitor.PulseAll(obj); } + /// + /// Cascades the call to . + /// + /// Releases the lock on an object and blocks the current thread until it reacquires the lock. + /// [MethodImpl(MethodImplOptions.AggressiveInlining)] public static void Wait(object obj) { Monitor.Wait(obj); } + /// + /// Cascades the call to . + /// + /// 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. + /// [MethodImpl(MethodImplOptions.AggressiveInlining)] public static void Wait(object obj, int millisecondsTimeout) { Monitor.Wait(obj, millisecondsTimeout); } + /// + /// Cascades the call to . + /// + /// 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. + /// [MethodImpl(MethodImplOptions.AggressiveInlining)] public static void Wait(object obj, TimeSpan timeout) { Monitor.Wait(obj, timeout); } + /// + /// Cascades the call to . + /// + /// 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. + /// [MethodImpl(MethodImplOptions.AggressiveInlining)] public static void Wait(object obj, int millisecondsTimeout, bool exitContext) { Monitor.Wait(obj, millisecondsTimeout, exitContext); } + /// + /// Cascades the call to . + /// + /// 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. + /// [MethodImpl(MethodImplOptions.AggressiveInlining)] public static void Wait(object obj, TimeSpan timeout, bool exitContext) {