blob: 4e1fc20dd46247592d7eb4ef8c5b098aa8656a1f [file] [log] [blame]
using System;
using System.Runtime.CompilerServices;
using System.Threading;
#nullable enable
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 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
{
private readonly object _lock = new object();
/// <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()
{
while (!UninterruptableMonitor.TryEnter(_lock))
Thread.Yield(); // Allow non-queued threads to win
}
/// <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()
{
while (!Monitor.TryEnter(_lock))
Thread.Yield(); // Allow non-queued threads to win
}
/// <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()
{
UninterruptableMonitor.Exit(_lock);
}
/// <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()
{
return UninterruptableMonitor.TryEnter(_lock);
}
/// <summary>
/// Returns a value indicating whether the lock is held by the current thread.
/// </summary>
public bool IsHeldByCurrentThread => UninterruptableMonitor.IsEntered(_lock);
}
}