789 lines
35 KiB
C#
789 lines
35 KiB
C#
#pragma warning disable 0420
|
|
// ==++==
|
|
//
|
|
// Copyright (c) Microsoft Corporation. All rights reserved.
|
|
//
|
|
// ==--==
|
|
// =+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+
|
|
//
|
|
// SpinLock.cs
|
|
// A spin lock is a mutual exclusion lock primitive where a thread trying to acquire the lock waits in a loop ("spins")
|
|
// repeatedly checking until the lock becomes available. As the thread remains active performing a non-useful task,
|
|
// the use of such a lock is a kind of busy waiting and consumes CPU resources without performing real work.
|
|
//
|
|
// <OWNER>[....]</OWNER>
|
|
//
|
|
// =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-
|
|
using System;
|
|
using System.Diagnostics;
|
|
using System.Security.Permissions;
|
|
using System.Runtime.InteropServices;
|
|
using System.Runtime.CompilerServices;
|
|
using System.Runtime.ConstrainedExecution;
|
|
using System.Diagnostics.Contracts;
|
|
|
|
namespace System.Threading
|
|
{
|
|
|
|
/// <summary>
|
|
/// Provides a mutual exclusion lock primitive where a thread trying to acquire the lock waits in a loop
|
|
/// repeatedly checking until the lock becomes available.
|
|
/// </summary>
|
|
/// <remarks>
|
|
/// <para>
|
|
/// Spin locks can be used for leaf-level locks where the object allocation implied by using a <see
|
|
/// cref="System.Threading.Monitor"/>, in size or due to garbage collection pressure, is overly
|
|
/// expensive. Avoiding blocking is another reason that a spin lock can be useful, however if you expect
|
|
/// any significant amount of blocking, you are probably best not using spin locks due to excessive
|
|
/// spinning. Spinning can be beneficial when locks are fine grained and large in number (for example, a
|
|
/// lock per node in a linked list) as well as when lock hold times are always extremely short. In
|
|
/// general, while holding a spin lock, one should avoid blocking, calling anything that itself may
|
|
/// block, holding more than one spin lock at once, making dynamically dispatched calls (interface and
|
|
/// virtuals), making statically dispatched calls into any code one doesn't own, or allocating memory.
|
|
/// </para>
|
|
/// <para>
|
|
/// <see cref="SpinLock"/> should only be used when it's been determined that doing so will improve an
|
|
/// application's performance. It's also important to note that <see cref="SpinLock"/> is a value type,
|
|
/// for performance reasons. As such, one must be very careful not to accidentally copy a SpinLock
|
|
/// instance, as the two instances (the original and the copy) would then be completely independent of
|
|
/// one another, which would likely lead to erroneous behavior of the application. If a SpinLock instance
|
|
/// must be passed around, it should be passed by reference rather than by value.
|
|
/// </para>
|
|
/// <para>
|
|
/// Do not store <see cref="SpinLock"/> instances in readonly fields.
|
|
/// </para>
|
|
/// <para>
|
|
/// All members of <see cref="SpinLock"/> are thread-safe and may be used from multiple threads
|
|
/// concurrently.
|
|
/// </para>
|
|
/// </remarks>
|
|
[ComVisible(false)]
|
|
[HostProtection(Synchronization = true, ExternalThreading = true)]
|
|
[DebuggerTypeProxy(typeof(SystemThreading_SpinLockDebugView))]
|
|
[DebuggerDisplay("IsHeld = {IsHeld}")]
|
|
public struct SpinLock
|
|
{
|
|
// The current ownership state is a single signed int. There are two modes:
|
|
//
|
|
// 1) Ownership tracking enabled: the high bit is 0, and the remaining bits
|
|
// store the managed thread ID of the current owner. When the 31 low bits
|
|
// are 0, the lock is available.
|
|
// 2) Performance mode: when the high bit is 1, lock availability is indicated by the low bit.
|
|
// When the low bit is 1 -- the lock is held; 0 -- the lock is available.
|
|
//
|
|
// There are several masks and constants below for convenience.
|
|
|
|
private volatile int m_owner;
|
|
|
|
// The multiplier factor for the each spinning iteration
|
|
// This number has been chosen after trying different numbers on different CPUs (4, 8 and 16 ) and this provided the best results
|
|
private const int SPINNING_FACTOR = 100;
|
|
|
|
// After how many yields, call Sleep(1)
|
|
private const int SLEEP_ONE_FREQUENCY = 40;
|
|
|
|
// After how many yields, call Sleep(0)
|
|
private const int SLEEP_ZERO_FREQUENCY = 10;
|
|
|
|
// After how many yields, check the timeout
|
|
private const int TIMEOUT_CHECK_FREQUENCY = 10;
|
|
|
|
// Thr thread tracking disabled mask
|
|
private const int LOCK_ID_DISABLE_MASK = unchecked((int)0x80000000); //1000 0000 0000 0000 0000 0000 0000 0000
|
|
|
|
//the lock is held by some thread, but we don't know which
|
|
private const int LOCK_ANONYMOUS_OWNED = 0x1; //0000 0000 0000 0000 0000 0000 0000 0001
|
|
|
|
// Waiters mask if the thread tracking is disabled
|
|
private const int WAITERS_MASK = ~(LOCK_ID_DISABLE_MASK | 1); //0111 1111 1111 1111 1111 1111 1111 1110
|
|
|
|
// The Thread tacking is disabled and the lock bit is set, used in Enter fast path to make sure the id is disabled and lock is available
|
|
private const int ID_DISABLED_AND_ANONYMOUS_OWNED = unchecked((int)0x80000001); //1000 0000 0000 0000 0000 0000 0000 0001
|
|
|
|
// If the thread is unowned if:
|
|
// m_owner zero and the threa tracking is enabled
|
|
// m_owner & LOCK_ANONYMOUS_OWNED = zero and the thread tracking is disabled
|
|
private const int LOCK_UNOWNED = 0;
|
|
|
|
// The maximum number of waiters (only used if the thread tracking is disabled)
|
|
// The actual maximum waiters count is this number divided by two because each waiter increments the waiters count by 2
|
|
// The waiters count is calculated by m_owner & WAITERS_MASK 01111....110
|
|
private static int MAXIMUM_WAITERS = WAITERS_MASK;
|
|
|
|
|
|
/// <summary>
|
|
/// Initializes a new instance of the <see cref="T:System.Threading.SpinLock"/>
|
|
/// structure with the option to track thread IDs to improve debugging.
|
|
/// </summary>
|
|
/// <remarks>
|
|
/// The default constructor for <see cref="SpinLock"/> tracks thread ownership.
|
|
/// </remarks>
|
|
/// <param name="enableThreadOwnerTracking">Whether to capture and use thread IDs for debugging
|
|
/// purposes.</param>
|
|
public SpinLock(bool enableThreadOwnerTracking)
|
|
{
|
|
m_owner = LOCK_UNOWNED;
|
|
if (!enableThreadOwnerTracking)
|
|
{
|
|
m_owner |= LOCK_ID_DISABLE_MASK;
|
|
Contract.Assert(!IsThreadOwnerTrackingEnabled, "property should be false by now");
|
|
}
|
|
}
|
|
|
|
|
|
/// <summary>
|
|
/// Initializes a new instance of the <see cref="T:System.Threading.SpinLock"/>
|
|
/// structure with the option to track thread IDs to improve debugging.
|
|
/// </summary>
|
|
/// <remarks>
|
|
/// The default constructor for <see cref="SpinLock"/> tracks thread ownership.
|
|
/// </remarks>
|
|
/// <summary>
|
|
/// Acquires the lock in a reliable manner, such that even if an exception occurs within the method
|
|
/// call, <paramref name="lockTaken"/> can be examined reliably to determine whether the lock was
|
|
/// acquired.
|
|
/// </summary>
|
|
/// <remarks>
|
|
/// <see cref="SpinLock"/> is a non-reentrant lock, meaning that if a thread holds the lock, it is
|
|
/// not allowed to enter the lock again. If thread ownership tracking is enabled (whether it's
|
|
/// enabled is available through <see cref="IsThreadOwnerTrackingEnabled"/>), an exception will be
|
|
/// thrown when a thread tries to re-enter a lock it already holds. However, if thread ownership
|
|
/// tracking is disabled, attempting to enter a lock already held will result in deadlock.
|
|
/// </remarks>
|
|
/// <param name="lockTaken">True if the lock is acquired; otherwise, false. <paramref
|
|
/// name="lockTaken"/> must be initialized to false prior to calling this method.</param>
|
|
/// <exception cref="T:System.Threading.LockRecursionException">
|
|
/// Thread ownership tracking is enabled, and the current thread has already acquired this lock.
|
|
/// </exception>
|
|
/// <exception cref="T:System.ArgumentException">
|
|
/// The <paramref name="lockTaken"/> argument must be initialized to false prior to calling Enter.
|
|
/// </exception>
|
|
public void Enter(ref bool lockTaken)
|
|
{
|
|
#if !FEATURE_CORECLR
|
|
Thread.BeginCriticalRegion();
|
|
#endif
|
|
//Try to keep the code and branching in this method as small as possible in order to inline the method
|
|
int observedOwner = m_owner;
|
|
if (lockTaken || //invalid parameter
|
|
(observedOwner & ID_DISABLED_AND_ANONYMOUS_OWNED) != LOCK_ID_DISABLE_MASK || //thread tracking is enabled or the lock is already acquired
|
|
Interlocked.CompareExchange(ref m_owner, observedOwner | LOCK_ANONYMOUS_OWNED, observedOwner, ref lockTaken) != observedOwner) //acquiring the lock failed
|
|
ContinueTryEnter(Timeout.Infinite, ref lockTaken); // Then try the slow path if any of the above conditions is met
|
|
|
|
}
|
|
|
|
/// <summary>
|
|
/// Attempts to acquire the lock in a reliable manner, such that even if an exception occurs within
|
|
/// the method call, <paramref name="lockTaken"/> can be examined reliably to determine whether the
|
|
/// lock was acquired.
|
|
/// </summary>
|
|
/// <remarks>
|
|
/// Unlike <see cref="Enter"/>, TryEnter will not block waiting for the lock to be available. If the
|
|
/// lock is not available when TryEnter is called, it will return immediately without any further
|
|
/// spinning.
|
|
/// </remarks>
|
|
/// <param name="lockTaken">True if the lock is acquired; otherwise, false. <paramref
|
|
/// name="lockTaken"/> must be initialized to false prior to calling this method.</param>
|
|
/// <exception cref="T:System.Threading.LockRecursionException">
|
|
/// Thread ownership tracking is enabled, and the current thread has already acquired this lock.
|
|
/// </exception>
|
|
/// <exception cref="T:System.ArgumentException">
|
|
/// The <paramref name="lockTaken"/> argument must be initialized to false prior to calling TryEnter.
|
|
/// </exception>
|
|
public void TryEnter(ref bool lockTaken)
|
|
{
|
|
TryEnter(0, ref lockTaken);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Attempts to acquire the lock in a reliable manner, such that even if an exception occurs within
|
|
/// the method call, <paramref name="lockTaken"/> can be examined reliably to determine whether the
|
|
/// lock was acquired.
|
|
/// </summary>
|
|
/// <remarks>
|
|
/// Unlike <see cref="Enter"/>, TryEnter will not block indefinitely waiting for the lock to be
|
|
/// available. It will block until either the lock is available or until the <paramref
|
|
/// name="timeout"/>
|
|
/// has expired.
|
|
/// </remarks>
|
|
/// <param name="timeout">A <see cref="System.TimeSpan"/> that represents the number of milliseconds
|
|
/// to wait, or a <see cref="System.TimeSpan"/> that represents -1 milliseconds to wait indefinitely.
|
|
/// </param>
|
|
/// <param name="lockTaken">True if the lock is acquired; otherwise, false. <paramref
|
|
/// name="lockTaken"/> must be initialized to false prior to calling this method.</param>
|
|
/// <exception cref="T:System.Threading.LockRecursionException">
|
|
/// Thread ownership tracking is enabled, and the current thread has already acquired this lock.
|
|
/// </exception>
|
|
/// <exception cref="T:System.ArgumentException">
|
|
/// The <paramref name="lockTaken"/> argument must be initialized to false prior to calling TryEnter.
|
|
/// </exception>
|
|
/// <exception cref="T:System.ArgumentOutOfRangeException"><paramref name="timeout"/> is a negative
|
|
/// number other than -1 milliseconds, which represents an infinite time-out -or- timeout is greater
|
|
/// than <see cref="System.Int32.MaxValue"/> milliseconds.
|
|
/// </exception>
|
|
public void TryEnter(TimeSpan timeout, ref bool lockTaken)
|
|
{
|
|
// Validate the timeout
|
|
Int64 totalMilliseconds = (Int64)timeout.TotalMilliseconds;
|
|
if (totalMilliseconds < -1 || totalMilliseconds > int.MaxValue)
|
|
{
|
|
throw new System.ArgumentOutOfRangeException(
|
|
"timeout", timeout, Environment.GetResourceString("SpinLock_TryEnter_ArgumentOutOfRange"));
|
|
}
|
|
|
|
// Call reliable enter with the int-based timeout milliseconds
|
|
TryEnter((int)timeout.TotalMilliseconds, ref lockTaken);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Attempts to acquire the lock in a reliable manner, such that even if an exception occurs within
|
|
/// the method call, <paramref name="lockTaken"/> can be examined reliably to determine whether the
|
|
/// lock was acquired.
|
|
/// </summary>
|
|
/// <remarks>
|
|
/// Unlike <see cref="Enter"/>, TryEnter will not block indefinitely waiting for the lock to be
|
|
/// available. It will block until either the lock is available or until the <paramref
|
|
/// name="millisecondsTimeout"/> has expired.
|
|
/// </remarks>
|
|
/// <param name="millisecondsTimeout">The number of milliseconds to wait, or <see
|
|
/// cref="System.Threading.Timeout.Infinite"/> (-1) to wait indefinitely.</param>
|
|
/// <param name="lockTaken">True if the lock is acquired; otherwise, false. <paramref
|
|
/// name="lockTaken"/> must be initialized to false prior to calling this method.</param>
|
|
/// <exception cref="T:System.Threading.LockRecursionException">
|
|
/// Thread ownership tracking is enabled, and the current thread has already acquired this lock.
|
|
/// </exception>
|
|
/// <exception cref="T:System.ArgumentException">
|
|
/// The <paramref name="lockTaken"/> argument must be initialized to false prior to calling TryEnter.
|
|
/// </exception>
|
|
/// <exception cref="T:System.ArgumentOutOfRangeException"><paramref name="millisecondsTimeout"/> is
|
|
/// a negative number other than -1, which represents an infinite time-out.</exception>
|
|
public void TryEnter(int millisecondsTimeout, ref bool lockTaken)
|
|
{
|
|
#if !FEATURE_CORECLR
|
|
Thread.BeginCriticalRegion();
|
|
#endif
|
|
|
|
int observedOwner = m_owner;
|
|
if (millisecondsTimeout < -1 || //invalid parameter
|
|
lockTaken || //invalid parameter
|
|
(observedOwner & ID_DISABLED_AND_ANONYMOUS_OWNED) != LOCK_ID_DISABLE_MASK || //thread tracking is enabled or the lock is already acquired
|
|
Interlocked.CompareExchange(ref m_owner, observedOwner | LOCK_ANONYMOUS_OWNED, observedOwner, ref lockTaken) != observedOwner) // acquiring the lock failed
|
|
ContinueTryEnter(millisecondsTimeout, ref lockTaken); // The call the slow pth
|
|
}
|
|
|
|
/// <summary>
|
|
/// Try acquire the lock with long path, this is usually called after the first path in Enter and
|
|
/// TryEnter failed The reason for short path is to make it inline in the run time which improves the
|
|
/// performance. This method assumed that the parameter are validated in Enter ir TryENter method
|
|
/// </summary>
|
|
/// <param name="millisecondsTimeout">The timeout milliseconds</param>
|
|
/// <param name="lockTaken">The lockTaken param</param>
|
|
private void ContinueTryEnter(int millisecondsTimeout, ref bool lockTaken)
|
|
{
|
|
//Leave the critical region which is entered by the fast path
|
|
#if !FEATURE_CORECLR
|
|
Thread.EndCriticalRegion();
|
|
#endif
|
|
// The fast path doesn't throw any exception, so we have to validate the parameters here
|
|
if (lockTaken)
|
|
{
|
|
lockTaken = false;
|
|
throw new System.ArgumentException(Environment.GetResourceString("SpinLock_TryReliableEnter_ArgumentException"));
|
|
}
|
|
|
|
if (millisecondsTimeout < -1)
|
|
{
|
|
throw new ArgumentOutOfRangeException(
|
|
"millisecondsTimeout", millisecondsTimeout, Environment.GetResourceString("SpinLock_TryEnter_ArgumentOutOfRange"));
|
|
}
|
|
|
|
|
|
uint startTime = 0;
|
|
if (millisecondsTimeout != Timeout.Infinite && millisecondsTimeout != 0)
|
|
{
|
|
startTime = TimeoutHelper.GetTime();
|
|
}
|
|
|
|
#if !FEATURE_PAL && !FEATURE_CORECLR // PAL doesn't support eventing, and we don't compile CDS providers for Coreclr
|
|
if (CdsSyncEtwBCLProvider.Log.IsEnabled())
|
|
{
|
|
CdsSyncEtwBCLProvider.Log.SpinLock_FastPathFailed(m_owner);
|
|
}
|
|
#endif
|
|
|
|
if (IsThreadOwnerTrackingEnabled)
|
|
{
|
|
// Slow path for enabled thread tracking mode
|
|
ContinueTryEnterWithThreadTracking(millisecondsTimeout, startTime, ref lockTaken);
|
|
return;
|
|
}
|
|
|
|
// then thread tracking is disabled
|
|
// In this case there are three ways to acquire the lock
|
|
// 1- the first way the thread either tries to get the lock if it's free or updates the waiters, if the turn >= the processors count then go to 3 else go to 2
|
|
// 2- In this step the waiter threads spins and tries to acquire the lock, the number of spin iterations and spin count is dependent on the thread turn
|
|
// the late the thread arrives the more it spins and less frequent it check the lock avilability
|
|
// Also the spins count is increases each iteration
|
|
// If the spins iterations finished and failed to acquire the lock, go to step 3
|
|
// 3- This is the yielding step, there are two ways of yielding Thread.Yield and Sleep(1)
|
|
// If the timeout is expired in after step 1, we need to decrement the waiters count before returning
|
|
|
|
int observedOwner;
|
|
int turn = int.MaxValue;
|
|
//***Step 1, take the lock or update the waiters
|
|
|
|
// try to acquire the lock directly if possible or update the waiters count
|
|
observedOwner = m_owner;
|
|
if ((observedOwner & LOCK_ANONYMOUS_OWNED) == LOCK_UNOWNED)
|
|
{
|
|
#if !FEATURE_CORECLR
|
|
Thread.BeginCriticalRegion();
|
|
#endif
|
|
|
|
#if PFX_LEGACY_3_5
|
|
if (Interlocked.CompareExchange(ref m_owner, observedOwner | 1, observedOwner) == observedOwner)
|
|
{
|
|
lockTaken = true;
|
|
return;
|
|
}
|
|
#else
|
|
if (Interlocked.CompareExchange(ref m_owner, observedOwner | 1, observedOwner, ref lockTaken) == observedOwner)
|
|
{
|
|
return;
|
|
}
|
|
#endif
|
|
|
|
#if !FEATURE_CORECLR
|
|
Thread.EndCriticalRegion();
|
|
#endif
|
|
}
|
|
else //failed to acquire the lock,then try to update the waiters. If the waiters count reached the maximum, jsut break the loop to avoid overflow
|
|
{
|
|
if ((observedOwner & WAITERS_MASK) != MAXIMUM_WAITERS)
|
|
turn = (Interlocked.Add(ref m_owner, 2) & WAITERS_MASK) >> 1 ;
|
|
}
|
|
|
|
|
|
|
|
// Check the timeout.
|
|
if (millisecondsTimeout == 0 ||
|
|
(millisecondsTimeout != Timeout.Infinite &&
|
|
TimeoutHelper.UpdateTimeOut(startTime, millisecondsTimeout) <= 0))
|
|
{
|
|
DecrementWaiters();
|
|
return;
|
|
}
|
|
|
|
//***Step 2. Spinning
|
|
//lock acquired failed and waiters updated
|
|
int processorCount = PlatformHelper.ProcessorCount;
|
|
if (turn < processorCount)
|
|
{
|
|
int processFactor = 1;
|
|
for (int i = 1; i <= turn * SPINNING_FACTOR; i++)
|
|
{
|
|
Thread.SpinWait((turn + i) * SPINNING_FACTOR * processFactor);
|
|
if (processFactor < processorCount)
|
|
processFactor++;
|
|
observedOwner = m_owner;
|
|
if ((observedOwner & LOCK_ANONYMOUS_OWNED) == LOCK_UNOWNED)
|
|
{
|
|
#if !FEATURE_CORECLR
|
|
Thread.BeginCriticalRegion();
|
|
#endif
|
|
|
|
int newOwner = (observedOwner & WAITERS_MASK) == 0 ? // Gets the number of waiters, if zero
|
|
observedOwner | 1 // don't decrement it. just set the lock bit, it is zzero because a previous call of Exit(false) ehich corrupted the waiters
|
|
: (observedOwner - 2) | 1; // otherwise decrement the waiters and set the lock bit
|
|
Contract.Assert((newOwner & WAITERS_MASK) >= 0);
|
|
#if PFX_LEGACY_3_5
|
|
if (Interlocked.CompareExchange(ref m_owner, newOwner, observedOwner) == observedOwner)
|
|
{
|
|
lockTaken = true;
|
|
return;
|
|
}
|
|
#else
|
|
if (Interlocked.CompareExchange(ref m_owner, newOwner, observedOwner, ref lockTaken) == observedOwner)
|
|
{
|
|
return;
|
|
}
|
|
#endif
|
|
|
|
#if !FEATURE_CORECLR
|
|
Thread.EndCriticalRegion();
|
|
#endif
|
|
}
|
|
}
|
|
}
|
|
|
|
// Check the timeout.
|
|
if (millisecondsTimeout != Timeout.Infinite && TimeoutHelper.UpdateTimeOut(startTime, millisecondsTimeout) <= 0)
|
|
{
|
|
DecrementWaiters();
|
|
return;
|
|
}
|
|
|
|
//*** Step 3, Yielding
|
|
//Sleep(1) every 50 yields
|
|
int yieldsoFar = 0;
|
|
while (true)
|
|
{
|
|
observedOwner = m_owner;
|
|
if ((observedOwner & LOCK_ANONYMOUS_OWNED) == LOCK_UNOWNED)
|
|
{
|
|
#if !FEATURE_CORECLR
|
|
Thread.BeginCriticalRegion();
|
|
#endif
|
|
int newOwner = (observedOwner & WAITERS_MASK) == 0 ? // Gets the number of waiters, if zero
|
|
observedOwner | 1 // don't decrement it. just set the lock bit, it is zzero because a previous call of Exit(false) ehich corrupted the waiters
|
|
: (observedOwner - 2) | 1; // otherwise decrement the waiters and set the lock bit
|
|
Contract.Assert((newOwner & WAITERS_MASK) >= 0);
|
|
#if PFX_LEGACY_3_5
|
|
if (Interlocked.CompareExchange(ref m_owner, newOwner, observedOwner) == observedOwner)
|
|
{
|
|
lockTaken = true;
|
|
return;
|
|
}
|
|
#else
|
|
if (Interlocked.CompareExchange(ref m_owner, newOwner, observedOwner, ref lockTaken) == observedOwner)
|
|
{
|
|
return;
|
|
}
|
|
#endif
|
|
|
|
#if !FEATURE_CORECLR
|
|
Thread.EndCriticalRegion();
|
|
#endif
|
|
}
|
|
|
|
if (yieldsoFar % SLEEP_ONE_FREQUENCY == 0)
|
|
{
|
|
Thread.Sleep(1);
|
|
}
|
|
else if (yieldsoFar % SLEEP_ZERO_FREQUENCY == 0)
|
|
{
|
|
Thread.Sleep(0);
|
|
}
|
|
else
|
|
{
|
|
#if PFX_LEGACY_3_5
|
|
Platform.Yield();
|
|
#else
|
|
Thread.Yield();
|
|
#endif
|
|
}
|
|
|
|
if (yieldsoFar % TIMEOUT_CHECK_FREQUENCY == 0)
|
|
{
|
|
//Check the timeout.
|
|
if (millisecondsTimeout != Timeout.Infinite && TimeoutHelper.UpdateTimeOut(startTime, millisecondsTimeout) <= 0)
|
|
{
|
|
DecrementWaiters();
|
|
return;
|
|
}
|
|
}
|
|
|
|
yieldsoFar++;
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// decrements the waiters, in case of the timeout is expired
|
|
/// </summary>
|
|
private void DecrementWaiters()
|
|
{
|
|
SpinWait spinner = new SpinWait();
|
|
while (true)
|
|
{
|
|
int observedOwner = m_owner;
|
|
if ((observedOwner & WAITERS_MASK) == 0) return; // don't decrement the waiters if it's corrupted by previous call of Exit(false)
|
|
if (Interlocked.CompareExchange(ref m_owner, observedOwner - 2, observedOwner) == observedOwner)
|
|
{
|
|
Contract.Assert(!IsThreadOwnerTrackingEnabled); // Make sure the waiters never be negative which will cause the thread tracking bit to be flipped
|
|
break;
|
|
}
|
|
spinner.SpinOnce();
|
|
}
|
|
|
|
}
|
|
|
|
/// <summary>
|
|
/// ContinueTryEnter for the thread tracking mode enabled
|
|
/// </summary>
|
|
private void ContinueTryEnterWithThreadTracking(int millisecondsTimeout, uint startTime, ref bool lockTaken)
|
|
{
|
|
Contract.Assert(IsThreadOwnerTrackingEnabled);
|
|
|
|
int lockUnowned = 0;
|
|
// We are using thread IDs to mark ownership. Snap the thread ID and check for recursion.
|
|
// We also must or the ID enablement bit, to ensure we propagate when we CAS it in.
|
|
int m_newOwner = Thread.CurrentThread.ManagedThreadId;
|
|
if (m_owner == m_newOwner)
|
|
{
|
|
// We don't allow lock recursion.
|
|
throw new LockRecursionException(Environment.GetResourceString("SpinLock_TryEnter_LockRecursionException"));
|
|
}
|
|
|
|
|
|
SpinWait spinner = new SpinWait();
|
|
|
|
// Loop until the lock has been successfully acquired or, if specified, the timeout expires.
|
|
do
|
|
{
|
|
|
|
// We failed to get the lock, either from the fast route or the last iteration
|
|
// and the timeout hasn't expired; spin once and try again.
|
|
spinner.SpinOnce();
|
|
|
|
// Test before trying to CAS, to avoid acquiring the line exclusively unnecessarily.
|
|
|
|
if (m_owner == lockUnowned)
|
|
{
|
|
#if !FEATURE_CORECLR
|
|
Thread.BeginCriticalRegion();
|
|
#endif
|
|
#if PFX_LEGACY_3_5
|
|
if (Interlocked.CompareExchange(ref m_owner, m_newOwner, lockUnowned) == lockUnowned)
|
|
{
|
|
lockTaken = true;
|
|
return;
|
|
}
|
|
#else
|
|
if (Interlocked.CompareExchange(ref m_owner, m_newOwner, lockUnowned, ref lockTaken) == lockUnowned)
|
|
{
|
|
return;
|
|
}
|
|
#endif
|
|
|
|
#if !FEATURE_CORECLR
|
|
// The thread failed to get the lock, so we don't need to remain in a critical region.
|
|
Thread.EndCriticalRegion();
|
|
#endif
|
|
}
|
|
// Check the timeout. We only RDTSC if the next spin will yield, to amortize the cost.
|
|
if (millisecondsTimeout == 0 ||
|
|
(millisecondsTimeout != Timeout.Infinite && spinner.NextSpinWillYield &&
|
|
TimeoutHelper.UpdateTimeOut(startTime, millisecondsTimeout) <= 0))
|
|
{
|
|
return;
|
|
}
|
|
} while (true);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Releases the lock.
|
|
/// </summary>
|
|
/// <remarks>
|
|
/// The default overload of <see cref="Exit()"/> provides the same behavior as if calling <see
|
|
/// cref="Exit(Boolean)"/> using true as the argument, but Exit() could be slightly faster than Exit(true).
|
|
/// </remarks>
|
|
/// <exception cref="SynchronizationLockException">
|
|
/// Thread ownership tracking is enabled, and the current thread is not the owner of this lock.
|
|
/// </exception>
|
|
[ReliabilityContract(Consistency.WillNotCorruptState, Cer.Success)]
|
|
public void Exit()
|
|
{
|
|
//This is the fast path for the thread tracking is disabled, otherwise go to the slow path
|
|
if ((m_owner & LOCK_ID_DISABLE_MASK) == 0)
|
|
ExitSlowPath(true);
|
|
else
|
|
Interlocked.Decrement(ref m_owner);
|
|
|
|
#if !FEATURE_CORECLR
|
|
Thread.EndCriticalRegion();
|
|
#endif
|
|
|
|
}
|
|
|
|
/// <summary>
|
|
/// Releases the lock.
|
|
/// </summary>
|
|
/// <param name="useMemoryBarrier">
|
|
/// A Boolean value that indicates whether a memory fence should be issued in order to immediately
|
|
/// publish the exit operation to other threads.
|
|
/// </param>
|
|
/// <remarks>
|
|
/// Calling <see cref="Exit(Boolean)"/> with the <paramref name="useMemoryBarrier"/> argument set to
|
|
/// true will improve the fairness of the lock at the expense of some performance. The default <see
|
|
/// cref="Enter"/>
|
|
/// overload behaves as if specifying true for <paramref name="useMemoryBarrier"/>.
|
|
/// </remarks>
|
|
/// <exception cref="SynchronizationLockException">
|
|
/// Thread ownership tracking is enabled, and the current thread is not the owner of this lock.
|
|
/// </exception>
|
|
[ReliabilityContract(Consistency.WillNotCorruptState, Cer.Success)]
|
|
public void Exit(bool useMemoryBarrier)
|
|
{
|
|
// This is the fast path for the thread tracking is diabled and not to use memory barrier, otherwise go to the slow path
|
|
// The reason not to add else statement if the usememorybarrier is that it will add more barnching in the code and will prevent
|
|
// method inlining, so this is optimized for useMemoryBarrier=false and Exit() overload optimized for useMemoryBarrier=true
|
|
if ((m_owner & LOCK_ID_DISABLE_MASK) != 0 && !useMemoryBarrier)
|
|
{
|
|
int tmpOwner = m_owner;
|
|
m_owner = tmpOwner & (~LOCK_ANONYMOUS_OWNED);
|
|
}
|
|
else
|
|
ExitSlowPath(useMemoryBarrier);
|
|
|
|
#if !FEATURE_CORECLR
|
|
Thread.EndCriticalRegion();
|
|
#endif
|
|
}
|
|
|
|
/// <summary>
|
|
/// The slow path for exit method if the fast path failed
|
|
/// </summary>
|
|
/// <param name="useMemoryBarrier">
|
|
/// A Boolean value that indicates whether a memory fence should be issued in order to immediately
|
|
/// publish the exit operation to other threads
|
|
/// </param>
|
|
private void ExitSlowPath(bool useMemoryBarrier)
|
|
{
|
|
bool threadTrackingEnabled = (m_owner & LOCK_ID_DISABLE_MASK) == 0;
|
|
if (threadTrackingEnabled && !IsHeldByCurrentThread)
|
|
{
|
|
throw new System.Threading.SynchronizationLockException(
|
|
Environment.GetResourceString("SpinLock_Exit_SynchronizationLockException"));
|
|
}
|
|
|
|
if (useMemoryBarrier)
|
|
{
|
|
if (threadTrackingEnabled)
|
|
Interlocked.Exchange(ref m_owner, LOCK_UNOWNED);
|
|
else
|
|
Interlocked.Decrement(ref m_owner);
|
|
|
|
}
|
|
else
|
|
{
|
|
if (threadTrackingEnabled)
|
|
m_owner = LOCK_UNOWNED;
|
|
else
|
|
{
|
|
int tmpOwner = m_owner;
|
|
m_owner = tmpOwner & (~LOCK_ANONYMOUS_OWNED);
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
/// <summary>
|
|
/// Gets whether the lock is currently held by any thread.
|
|
/// </summary>
|
|
public bool IsHeld
|
|
{
|
|
[ReliabilityContract(Consistency.WillNotCorruptState, Cer.Success)]
|
|
get
|
|
{
|
|
if (IsThreadOwnerTrackingEnabled)
|
|
return m_owner != LOCK_UNOWNED;
|
|
|
|
return (m_owner & LOCK_ANONYMOUS_OWNED) != LOCK_UNOWNED;
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Gets whether the lock is currently held by any thread.
|
|
/// </summary>
|
|
/// <summary>
|
|
/// Gets whether the lock is held by the current thread.
|
|
/// </summary>
|
|
/// <remarks>
|
|
/// If the lock was initialized to track owner threads, this will return whether the lock is acquired
|
|
/// by the current thread. It is invalid to use this property when the lock was initialized to not
|
|
/// track thread ownership.
|
|
/// </remarks>
|
|
/// <exception cref="T:System.InvalidOperationException">
|
|
/// Thread ownership tracking is disabled.
|
|
/// </exception>
|
|
public bool IsHeldByCurrentThread
|
|
{
|
|
[ReliabilityContract(Consistency.WillNotCorruptState, Cer.Success)]
|
|
get
|
|
{
|
|
if (!IsThreadOwnerTrackingEnabled)
|
|
{
|
|
throw new InvalidOperationException(Environment.GetResourceString("SpinLock_IsHeldByCurrentThread"));
|
|
}
|
|
return ((m_owner & (~LOCK_ID_DISABLE_MASK)) == Thread.CurrentThread.ManagedThreadId);
|
|
}
|
|
}
|
|
|
|
/// <summary>Gets whether thread ownership tracking is enabled for this instance.</summary>
|
|
public bool IsThreadOwnerTrackingEnabled
|
|
{
|
|
[ReliabilityContract(Consistency.WillNotCorruptState, Cer.Success)]
|
|
get { return (m_owner & LOCK_ID_DISABLE_MASK) == 0; }
|
|
}
|
|
|
|
#region Debugger proxy class
|
|
/// <summary>
|
|
/// Internal class used by debug type proxy attribute to display the owner thread ID
|
|
/// </summary>
|
|
internal class SystemThreading_SpinLockDebugView
|
|
{
|
|
// SpinLock object
|
|
private SpinLock m_spinLock;
|
|
|
|
/// <summary>
|
|
/// SystemThreading_SpinLockDebugView constructor
|
|
/// </summary>
|
|
/// <param name="spinLock">The SpinLock to be proxied.</param>
|
|
public SystemThreading_SpinLockDebugView(SpinLock spinLock)
|
|
{
|
|
// Note that this makes a copy of the SpinLock (struct). It doesn't hold a reference to it.
|
|
m_spinLock = spinLock;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Checks if the lock is held by the current thread or not
|
|
/// </summary>
|
|
public bool? IsHeldByCurrentThread
|
|
{
|
|
get
|
|
{
|
|
try
|
|
{
|
|
return m_spinLock.IsHeldByCurrentThread;
|
|
}
|
|
catch (InvalidOperationException)
|
|
{
|
|
return null;
|
|
}
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Gets the current owner thread, zero if it is released
|
|
/// </summary>
|
|
public int? OwnerThreadID
|
|
{
|
|
get
|
|
{
|
|
if (m_spinLock.IsThreadOwnerTrackingEnabled)
|
|
{
|
|
return m_spinLock.m_owner;
|
|
}
|
|
else
|
|
{
|
|
return null;
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
/// <summary>
|
|
/// Gets whether the lock is currently held by any thread or not.
|
|
/// </summary>
|
|
public bool IsHeld
|
|
{
|
|
get { return m_spinLock.IsHeld; }
|
|
}
|
|
}
|
|
#endregion
|
|
|
|
}
|
|
}
|
|
#pragma warning restore 0420
|