Fix StyleCop warning SA1121 (use built-in types)
[mono-project.git] / netcore / System.Private.CoreLib / shared / System / Threading / SpinLock.cs
blobf64bc01e8cd27fc8a081e303de088892c677811a
1 // Licensed to the .NET Foundation under one or more agreements.
2 // The .NET Foundation licenses this file to you under the MIT license.
3 // See the LICENSE file in the project root for more information.
4 #pragma warning disable 0420
6 // =+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+
7 //
8 // A spin lock is a mutual exclusion lock primitive where a thread trying to acquire the lock waits in a loop ("spins")
9 // repeatedly checking until the lock becomes available. As the thread remains active performing a non-useful task,
10 // the use of such a lock is a kind of busy waiting and consumes CPU resources without performing real work.
12 // =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-
14 using System.Diagnostics;
15 using System.Runtime.CompilerServices;
17 namespace System.Threading
19 /// <summary>
20 /// Provides a mutual exclusion lock primitive where a thread trying to acquire the lock waits in a loop
21 /// repeatedly checking until the lock becomes available.
22 /// </summary>
23 /// <remarks>
24 /// <para>
25 /// Spin locks can be used for leaf-level locks where the object allocation implied by using a <see
26 /// cref="System.Threading.Monitor"/>, in size or due to garbage collection pressure, is overly
27 /// expensive. Avoiding blocking is another reason that a spin lock can be useful, however if you expect
28 /// any significant amount of blocking, you are probably best not using spin locks due to excessive
29 /// spinning. Spinning can be beneficial when locks are fine grained and large in number (for example, a
30 /// lock per node in a linked list) as well as when lock hold times are always extremely short. In
31 /// general, while holding a spin lock, one should avoid blocking, calling anything that itself may
32 /// block, holding more than one spin lock at once, making dynamically dispatched calls (interface and
33 /// virtuals), making statically dispatched calls into any code one doesn't own, or allocating memory.
34 /// </para>
35 /// <para>
36 /// <see cref="SpinLock"/> should only be used when it's been determined that doing so will improve an
37 /// application's performance. It's also important to note that <see cref="SpinLock"/> is a value type,
38 /// for performance reasons. As such, one must be very careful not to accidentally copy a SpinLock
39 /// instance, as the two instances (the original and the copy) would then be completely independent of
40 /// one another, which would likely lead to erroneous behavior of the application. If a SpinLock instance
41 /// must be passed around, it should be passed by reference rather than by value.
42 /// </para>
43 /// <para>
44 /// Do not store <see cref="SpinLock"/> instances in readonly fields.
45 /// </para>
46 /// <para>
47 /// All members of <see cref="SpinLock"/> are thread-safe and may be used from multiple threads
48 /// concurrently.
49 /// </para>
50 /// </remarks>
51 [DebuggerTypeProxy(typeof(SystemThreading_SpinLockDebugView))]
52 [DebuggerDisplay("IsHeld = {IsHeld}")]
53 public struct SpinLock
55 // The current ownership state is a single signed int. There are two modes:
57 // 1) Ownership tracking enabled: the high bit is 0, and the remaining bits
58 // store the managed thread ID of the current owner. When the 31 low bits
59 // are 0, the lock is available.
60 // 2) Performance mode: when the high bit is 1, lock availability is indicated by the low bit.
61 // When the low bit is 1 -- the lock is held; 0 -- the lock is available.
63 // There are several masks and constants below for convenience.
65 private volatile int _owner;
67 // After how many yields, call Sleep(1)
68 private const int SLEEP_ONE_FREQUENCY = 40;
70 // After how many yields, check the timeout
71 private const int TIMEOUT_CHECK_FREQUENCY = 10;
73 // Thr thread tracking disabled mask
74 private const int LOCK_ID_DISABLE_MASK = unchecked((int)0x80000000); // 1000 0000 0000 0000 0000 0000 0000 0000
76 //the lock is held by some thread, but we don't know which
77 private const int LOCK_ANONYMOUS_OWNED = 0x1; // 0000 0000 0000 0000 0000 0000 0000 0001
79 // Waiters mask if the thread tracking is disabled
80 private const int WAITERS_MASK = ~(LOCK_ID_DISABLE_MASK | 1); // 0111 1111 1111 1111 1111 1111 1111 1110
82 // 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
83 private const int ID_DISABLED_AND_ANONYMOUS_OWNED = unchecked((int)0x80000001); // 1000 0000 0000 0000 0000 0000 0000 0001
85 // If the thread is unowned if:
86 // m_owner zero and the thread tracking is enabled
87 // m_owner & LOCK_ANONYMOUS_OWNED = zero and the thread tracking is disabled
88 private const int LOCK_UNOWNED = 0;
90 // The maximum number of waiters (only used if the thread tracking is disabled)
91 // The actual maximum waiters count is this number divided by two because each waiter increments the waiters count by 2
92 // The waiters count is calculated by m_owner & WAITERS_MASK 01111....110
93 private const int MAXIMUM_WAITERS = WAITERS_MASK;
95 [MethodImpl(MethodImplOptions.AggressiveInlining)]
96 private static int CompareExchange(ref int location, int value, int comparand, ref bool success)
98 int result = Interlocked.CompareExchange(ref location, value, comparand);
99 success = (result == comparand);
100 return result;
103 /// <summary>
104 /// Initializes a new instance of the <see cref="System.Threading.SpinLock"/>
105 /// structure with the option to track thread IDs to improve debugging.
106 /// </summary>
107 /// <remarks>
108 /// The default constructor for <see cref="SpinLock"/> tracks thread ownership.
109 /// </remarks>
110 /// <param name="enableThreadOwnerTracking">Whether to capture and use thread IDs for debugging
111 /// purposes.</param>
112 public SpinLock(bool enableThreadOwnerTracking)
114 _owner = LOCK_UNOWNED;
115 if (!enableThreadOwnerTracking)
117 _owner |= LOCK_ID_DISABLE_MASK;
118 Debug.Assert(!IsThreadOwnerTrackingEnabled, "property should be false by now");
122 /// <summary>
123 /// Initializes a new instance of the <see cref="System.Threading.SpinLock"/>
124 /// structure with the option to track thread IDs to improve debugging.
125 /// </summary>
126 /// <remarks>
127 /// The default constructor for <see cref="SpinLock"/> tracks thread ownership.
128 /// </remarks>
129 /// <summary>
130 /// Acquires the lock in a reliable manner, such that even if an exception occurs within the method
131 /// call, <paramref name="lockTaken"/> can be examined reliably to determine whether the lock was
132 /// acquired.
133 /// </summary>
134 /// <remarks>
135 /// <see cref="SpinLock"/> is a non-reentrant lock, meaning that if a thread holds the lock, it is
136 /// not allowed to enter the lock again. If thread ownership tracking is enabled (whether it's
137 /// enabled is available through <see cref="IsThreadOwnerTrackingEnabled"/>), an exception will be
138 /// thrown when a thread tries to re-enter a lock it already holds. However, if thread ownership
139 /// tracking is disabled, attempting to enter a lock already held will result in deadlock.
140 /// </remarks>
141 /// <param name="lockTaken">True if the lock is acquired; otherwise, false. <paramref
142 /// name="lockTaken"/> must be initialized to false prior to calling this method.</param>
143 /// <exception cref="System.Threading.LockRecursionException">
144 /// Thread ownership tracking is enabled, and the current thread has already acquired this lock.
145 /// </exception>
146 /// <exception cref="System.ArgumentException">
147 /// The <paramref name="lockTaken"/> argument must be initialized to false prior to calling Enter.
148 /// </exception>
149 public void Enter(ref bool lockTaken)
151 // Try to keep the code and branching in this method as small as possible in order to inline the method
152 int observedOwner = _owner;
153 if (lockTaken || // invalid parameter
154 (observedOwner & ID_DISABLED_AND_ANONYMOUS_OWNED) != LOCK_ID_DISABLE_MASK || // thread tracking is enabled or the lock is already acquired
155 CompareExchange(ref _owner, observedOwner | LOCK_ANONYMOUS_OWNED, observedOwner, ref lockTaken) != observedOwner) //acquiring the lock failed
156 ContinueTryEnter(Timeout.Infinite, ref lockTaken); // Then try the slow path if any of the above conditions is met
159 /// <summary>
160 /// Attempts to acquire the lock in a reliable manner, such that even if an exception occurs within
161 /// the method call, <paramref name="lockTaken"/> can be examined reliably to determine whether the
162 /// lock was acquired.
163 /// </summary>
164 /// <remarks>
165 /// Unlike <see cref="Enter"/>, TryEnter will not block waiting for the lock to be available. If the
166 /// lock is not available when TryEnter is called, it will return immediately without any further
167 /// spinning.
168 /// </remarks>
169 /// <param name="lockTaken">True if the lock is acquired; otherwise, false. <paramref
170 /// name="lockTaken"/> must be initialized to false prior to calling this method.</param>
171 /// <exception cref="System.Threading.LockRecursionException">
172 /// Thread ownership tracking is enabled, and the current thread has already acquired this lock.
173 /// </exception>
174 /// <exception cref="System.ArgumentException">
175 /// The <paramref name="lockTaken"/> argument must be initialized to false prior to calling TryEnter.
176 /// </exception>
177 public void TryEnter(ref bool lockTaken)
179 int observedOwner = _owner;
180 if (((observedOwner & LOCK_ID_DISABLE_MASK) == 0) | lockTaken)
182 // Thread tracking enabled or invalid arg. Take slow path.
183 ContinueTryEnter(0, ref lockTaken);
185 else if ((observedOwner & LOCK_ANONYMOUS_OWNED) != 0)
187 // Lock already held by someone
188 lockTaken = false;
190 else
192 // Lock wasn't held; try to acquire it.
193 CompareExchange(ref _owner, observedOwner | LOCK_ANONYMOUS_OWNED, observedOwner, ref lockTaken);
197 /// <summary>
198 /// Attempts to acquire the lock in a reliable manner, such that even if an exception occurs within
199 /// the method call, <paramref name="lockTaken"/> can be examined reliably to determine whether the
200 /// lock was acquired.
201 /// </summary>
202 /// <remarks>
203 /// Unlike <see cref="Enter"/>, TryEnter will not block indefinitely waiting for the lock to be
204 /// available. It will block until either the lock is available or until the <paramref
205 /// name="timeout"/>
206 /// has expired.
207 /// </remarks>
208 /// <param name="timeout">A <see cref="System.TimeSpan"/> that represents the number of milliseconds
209 /// to wait, or a <see cref="System.TimeSpan"/> that represents -1 milliseconds to wait indefinitely.
210 /// </param>
211 /// <param name="lockTaken">True if the lock is acquired; otherwise, false. <paramref
212 /// name="lockTaken"/> must be initialized to false prior to calling this method.</param>
213 /// <exception cref="System.Threading.LockRecursionException">
214 /// Thread ownership tracking is enabled, and the current thread has already acquired this lock.
215 /// </exception>
216 /// <exception cref="System.ArgumentException">
217 /// The <paramref name="lockTaken"/> argument must be initialized to false prior to calling TryEnter.
218 /// </exception>
219 /// <exception cref="System.ArgumentOutOfRangeException"><paramref name="timeout"/> is a negative
220 /// number other than -1 milliseconds, which represents an infinite time-out -or- timeout is greater
221 /// than <see cref="int.MaxValue"/> milliseconds.
222 /// </exception>
223 public void TryEnter(TimeSpan timeout, ref bool lockTaken)
225 // Validate the timeout
226 long totalMilliseconds = (long)timeout.TotalMilliseconds;
227 if (totalMilliseconds < -1 || totalMilliseconds > int.MaxValue)
229 throw new System.ArgumentOutOfRangeException(
230 nameof(timeout), timeout, SR.SpinLock_TryEnter_ArgumentOutOfRange);
233 // Call reliable enter with the int-based timeout milliseconds
234 TryEnter((int)timeout.TotalMilliseconds, ref lockTaken);
237 /// <summary>
238 /// Attempts to acquire the lock in a reliable manner, such that even if an exception occurs within
239 /// the method call, <paramref name="lockTaken"/> can be examined reliably to determine whether the
240 /// lock was acquired.
241 /// </summary>
242 /// <remarks>
243 /// Unlike <see cref="Enter"/>, TryEnter will not block indefinitely waiting for the lock to be
244 /// available. It will block until either the lock is available or until the <paramref
245 /// name="millisecondsTimeout"/> has expired.
246 /// </remarks>
247 /// <param name="millisecondsTimeout">The number of milliseconds to wait, or <see
248 /// cref="System.Threading.Timeout.Infinite"/> (-1) to wait indefinitely.</param>
249 /// <param name="lockTaken">True if the lock is acquired; otherwise, false. <paramref
250 /// name="lockTaken"/> must be initialized to false prior to calling this method.</param>
251 /// <exception cref="System.Threading.LockRecursionException">
252 /// Thread ownership tracking is enabled, and the current thread has already acquired this lock.
253 /// </exception>
254 /// <exception cref="System.ArgumentException">
255 /// The <paramref name="lockTaken"/> argument must be initialized to false prior to calling TryEnter.
256 /// </exception>
257 /// <exception cref="System.ArgumentOutOfRangeException"><paramref name="millisecondsTimeout"/> is
258 /// a negative number other than -1, which represents an infinite time-out.</exception>
259 public void TryEnter(int millisecondsTimeout, ref bool lockTaken)
261 int observedOwner = _owner;
262 if (millisecondsTimeout < -1 || //invalid parameter
263 lockTaken || //invalid parameter
264 (observedOwner & ID_DISABLED_AND_ANONYMOUS_OWNED) != LOCK_ID_DISABLE_MASK || //thread tracking is enabled or the lock is already acquired
265 CompareExchange(ref _owner, observedOwner | LOCK_ANONYMOUS_OWNED, observedOwner, ref lockTaken) != observedOwner) // acquiring the lock failed
266 ContinueTryEnter(millisecondsTimeout, ref lockTaken); // The call the slow pth
269 /// <summary>
270 /// Try acquire the lock with long path, this is usually called after the first path in Enter and
271 /// TryEnter failed The reason for short path is to make it inline in the run time which improves the
272 /// performance. This method assumed that the parameter are validated in Enter or TryEnter method.
273 /// </summary>
274 /// <param name="millisecondsTimeout">The timeout milliseconds</param>
275 /// <param name="lockTaken">The lockTaken param</param>
276 private void ContinueTryEnter(int millisecondsTimeout, ref bool lockTaken)
278 // The fast path doesn't throw any exception, so we have to validate the parameters here
279 if (lockTaken)
281 lockTaken = false;
282 throw new ArgumentException(SR.SpinLock_TryReliableEnter_ArgumentException);
285 if (millisecondsTimeout < -1)
287 throw new ArgumentOutOfRangeException(
288 nameof(millisecondsTimeout), millisecondsTimeout, SR.SpinLock_TryEnter_ArgumentOutOfRange);
291 uint startTime = 0;
292 if (millisecondsTimeout != Timeout.Infinite && millisecondsTimeout != 0)
294 startTime = TimeoutHelper.GetTime();
297 if (IsThreadOwnerTrackingEnabled)
299 // Slow path for enabled thread tracking mode
300 ContinueTryEnterWithThreadTracking(millisecondsTimeout, startTime, ref lockTaken);
301 return;
304 // then thread tracking is disabled
305 // In this case there are three ways to acquire the lock
306 // 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
307 // 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
308 // the late the thread arrives the more it spins and less frequent it check the lock availability
309 // Also the spins count is increases each iteration
310 // If the spins iterations finished and failed to acquire the lock, go to step 3
311 // 3- This is the yielding step, there are two ways of yielding Thread.Yield and Sleep(1)
312 // If the timeout is expired in after step 1, we need to decrement the waiters count before returning
314 int observedOwner;
315 int turn = int.MaxValue;
316 //***Step 1, take the lock or update the waiters
318 // try to acquire the lock directly if possible or update the waiters count
319 observedOwner = _owner;
320 if ((observedOwner & LOCK_ANONYMOUS_OWNED) == LOCK_UNOWNED)
322 if (CompareExchange(ref _owner, observedOwner | 1, observedOwner, ref lockTaken) == observedOwner)
324 // Acquired lock
325 return;
328 if (millisecondsTimeout == 0)
330 // Did not acquire lock in CompareExchange and timeout is 0 so fail fast
331 return;
334 else if (millisecondsTimeout == 0)
336 // Did not acquire lock as owned and timeout is 0 so fail fast
337 return;
339 else //failed to acquire the lock, then try to update the waiters. If the waiters count reached the maximum, just break the loop to avoid overflow
341 if ((observedOwner & WAITERS_MASK) != MAXIMUM_WAITERS)
343 // This can still overflow, but maybe there will never be that many waiters
344 turn = (Interlocked.Add(ref _owner, 2) & WAITERS_MASK) >> 1;
348 // lock acquired failed and waiters updated
350 //*** Step 2, Spinning and Yielding
351 var spinner = new SpinWait();
352 if (turn > PlatformHelper.ProcessorCount)
354 spinner.Count = SpinWait.YieldThreshold;
356 while (true)
358 spinner.SpinOnce(SLEEP_ONE_FREQUENCY);
360 observedOwner = _owner;
361 if ((observedOwner & LOCK_ANONYMOUS_OWNED) == LOCK_UNOWNED)
363 int newOwner = (observedOwner & WAITERS_MASK) == 0 ? // Gets the number of waiters, if zero
364 observedOwner | 1 // don't decrement it. just set the lock bit, it is zero because a previous call of Exit(false) which corrupted the waiters
365 : (observedOwner - 2) | 1; // otherwise decrement the waiters and set the lock bit
366 Debug.Assert((newOwner & WAITERS_MASK) >= 0);
368 if (CompareExchange(ref _owner, newOwner, observedOwner, ref lockTaken) == observedOwner)
370 return;
374 if (spinner.Count % TIMEOUT_CHECK_FREQUENCY == 0)
376 // Check the timeout.
377 if (millisecondsTimeout != Timeout.Infinite && TimeoutHelper.UpdateTimeOut(startTime, millisecondsTimeout) <= 0)
379 DecrementWaiters();
380 return;
386 /// <summary>
387 /// decrements the waiters, in case of the timeout is expired
388 /// </summary>
389 private void DecrementWaiters()
391 SpinWait spinner = new SpinWait();
392 while (true)
394 int observedOwner = _owner;
395 if ((observedOwner & WAITERS_MASK) == 0) return; // don't decrement the waiters if it's corrupted by previous call of Exit(false)
396 if (Interlocked.CompareExchange(ref _owner, observedOwner - 2, observedOwner) == observedOwner)
398 Debug.Assert(!IsThreadOwnerTrackingEnabled); // Make sure the waiters never be negative which will cause the thread tracking bit to be flipped
399 break;
401 spinner.SpinOnce();
405 /// <summary>
406 /// ContinueTryEnter for the thread tracking mode enabled
407 /// </summary>
408 private void ContinueTryEnterWithThreadTracking(int millisecondsTimeout, uint startTime, ref bool lockTaken)
410 Debug.Assert(IsThreadOwnerTrackingEnabled);
412 int lockUnowned = 0;
413 // We are using thread IDs to mark ownership. Snap the thread ID and check for recursion.
414 // We also must or the ID enablement bit, to ensure we propagate when we CAS it in.
415 int newOwner = Environment.CurrentManagedThreadId;
416 if (_owner == newOwner)
418 // We don't allow lock recursion.
419 throw new LockRecursionException(SR.SpinLock_TryEnter_LockRecursionException);
423 SpinWait spinner = new SpinWait();
425 // Loop until the lock has been successfully acquired or, if specified, the timeout expires.
428 // We failed to get the lock, either from the fast route or the last iteration
429 // and the timeout hasn't expired; spin once and try again.
430 spinner.SpinOnce();
432 // Test before trying to CAS, to avoid acquiring the line exclusively unnecessarily.
434 if (_owner == lockUnowned)
436 if (CompareExchange(ref _owner, newOwner, lockUnowned, ref lockTaken) == lockUnowned)
438 return;
441 // Check the timeout. We only RDTSC if the next spin will yield, to amortize the cost.
442 if (millisecondsTimeout == 0 ||
443 (millisecondsTimeout != Timeout.Infinite && spinner.NextSpinWillYield &&
444 TimeoutHelper.UpdateTimeOut(startTime, millisecondsTimeout) <= 0))
446 return;
448 } while (true);
451 /// <summary>
452 /// Releases the lock.
453 /// </summary>
454 /// <remarks>
455 /// The default overload of <see cref="Exit()"/> provides the same behavior as if calling <see
456 /// cref="Exit(bool)"/> using true as the argument, but Exit() could be slightly faster than Exit(true).
457 /// </remarks>
458 /// <exception cref="SynchronizationLockException">
459 /// Thread ownership tracking is enabled, and the current thread is not the owner of this lock.
460 /// </exception>
461 public void Exit()
463 //This is the fast path for the thread tracking is disabled, otherwise go to the slow path
464 if ((_owner & LOCK_ID_DISABLE_MASK) == 0)
465 ExitSlowPath(true);
466 else
467 Interlocked.Decrement(ref _owner);
470 /// <summary>
471 /// Releases the lock.
472 /// </summary>
473 /// <param name="useMemoryBarrier">
474 /// A Boolean value that indicates whether a memory fence should be issued in order to immediately
475 /// publish the exit operation to other threads.
476 /// </param>
477 /// <remarks>
478 /// Calling <see cref="Exit(bool)"/> with the <paramref name="useMemoryBarrier"/> argument set to
479 /// true will improve the fairness of the lock at the expense of some performance. The default <see
480 /// cref="Enter"/>
481 /// overload behaves as if specifying true for <paramref name="useMemoryBarrier"/>.
482 /// </remarks>
483 /// <exception cref="SynchronizationLockException">
484 /// Thread ownership tracking is enabled, and the current thread is not the owner of this lock.
485 /// </exception>
486 public void Exit(bool useMemoryBarrier)
488 // This is the fast path for the thread tracking is disabled and not to use memory barrier, otherwise go to the slow path
489 // The reason not to add else statement if the usememorybarrier is that it will add more branching in the code and will prevent
490 // method inlining, so this is optimized for useMemoryBarrier=false and Exit() overload optimized for useMemoryBarrier=true.
491 int tmpOwner = _owner;
492 if ((tmpOwner & LOCK_ID_DISABLE_MASK) != 0 & !useMemoryBarrier)
494 _owner = tmpOwner & (~LOCK_ANONYMOUS_OWNED);
496 else
498 ExitSlowPath(useMemoryBarrier);
502 /// <summary>
503 /// The slow path for exit method if the fast path failed
504 /// </summary>
505 /// <param name="useMemoryBarrier">
506 /// A Boolean value that indicates whether a memory fence should be issued in order to immediately
507 /// publish the exit operation to other threads
508 /// </param>
509 private void ExitSlowPath(bool useMemoryBarrier)
511 bool threadTrackingEnabled = (_owner & LOCK_ID_DISABLE_MASK) == 0;
512 if (threadTrackingEnabled && !IsHeldByCurrentThread)
514 throw new SynchronizationLockException(SR.SpinLock_Exit_SynchronizationLockException);
517 if (useMemoryBarrier)
519 if (threadTrackingEnabled)
521 Interlocked.Exchange(ref _owner, LOCK_UNOWNED);
523 else
525 Interlocked.Decrement(ref _owner);
528 else
530 if (threadTrackingEnabled)
532 _owner = LOCK_UNOWNED;
534 else
536 int tmpOwner = _owner;
537 _owner = tmpOwner & (~LOCK_ANONYMOUS_OWNED);
542 /// <summary>
543 /// Gets whether the lock is currently held by any thread.
544 /// </summary>
545 public bool IsHeld
549 if (IsThreadOwnerTrackingEnabled)
550 return _owner != LOCK_UNOWNED;
552 return (_owner & LOCK_ANONYMOUS_OWNED) != LOCK_UNOWNED;
556 /// <summary>
557 /// Gets whether the lock is currently held by any thread.
558 /// </summary>
559 /// <summary>
560 /// Gets whether the lock is held by the current thread.
561 /// </summary>
562 /// <remarks>
563 /// If the lock was initialized to track owner threads, this will return whether the lock is acquired
564 /// by the current thread. It is invalid to use this property when the lock was initialized to not
565 /// track thread ownership.
566 /// </remarks>
567 /// <exception cref="System.InvalidOperationException">
568 /// Thread ownership tracking is disabled.
569 /// </exception>
570 public bool IsHeldByCurrentThread
574 if (!IsThreadOwnerTrackingEnabled)
576 throw new InvalidOperationException(SR.SpinLock_IsHeldByCurrentThread);
578 return ((_owner & (~LOCK_ID_DISABLE_MASK)) == Environment.CurrentManagedThreadId);
582 /// <summary>Gets whether thread ownership tracking is enabled for this instance.</summary>
583 public bool IsThreadOwnerTrackingEnabled => (_owner & LOCK_ID_DISABLE_MASK) == 0;
585 #region Debugger proxy class
586 /// <summary>
587 /// Internal class used by debug type proxy attribute to display the owner thread ID
588 /// </summary>
589 internal class SystemThreading_SpinLockDebugView
591 // SpinLock object
592 private SpinLock _spinLock;
594 /// <summary>
595 /// SystemThreading_SpinLockDebugView constructor
596 /// </summary>
597 /// <param name="spinLock">The SpinLock to be proxied.</param>
598 public SystemThreading_SpinLockDebugView(SpinLock spinLock)
600 // Note that this makes a copy of the SpinLock (struct). It doesn't hold a reference to it.
601 _spinLock = spinLock;
604 /// <summary>
605 /// Checks if the lock is held by the current thread or not
606 /// </summary>
607 public bool? IsHeldByCurrentThread
613 return _spinLock.IsHeldByCurrentThread;
615 catch (InvalidOperationException)
617 return null;
622 /// <summary>
623 /// Gets the current owner thread, zero if it is released
624 /// </summary>
625 public int? OwnerThreadID
629 if (_spinLock.IsThreadOwnerTrackingEnabled)
631 return _spinLock._owner;
633 else
635 return null;
641 /// <summary>
642 /// Gets whether the lock is currently held by any thread or not.
643 /// </summary>
644 public bool IsHeld => _spinLock.IsHeld;
646 #endregion
650 #pragma warning restore 0420