Update Reference Sources to .NET Framework 4.6.1
[mono.git] / mcs / class / referencesource / mscorlib / system / threading / SpinLock.cs
1 #pragma warning disable 0420
2 // ==++==
3 //
4 //   Copyright (c) Microsoft Corporation.  All rights reserved.
5 // 
6 // ==--==
7 // =+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+
8 //
9 // SpinLock.cs
10 // A spin lock is a mutual exclusion lock primitive where a thread trying to acquire the lock waits in a loop ("spins")
11 // repeatedly checking until the lock becomes available. As the thread remains active performing a non-useful task,
12 // the use of such a lock is a kind of busy waiting and consumes CPU resources without performing real work. 
13 //
14 // <OWNER>[....]</OWNER>
15 //
16 // =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-
17 using System;
18 using System.Diagnostics;
19 using System.Security.Permissions;
20 using System.Runtime.InteropServices;
21 using System.Runtime.CompilerServices;
22 using System.Runtime.ConstrainedExecution;
23 using System.Diagnostics.Contracts;
24
25 namespace System.Threading
26 {
27
28     /// <summary>
29     /// Provides a mutual exclusion lock primitive where a thread trying to acquire the lock waits in a loop
30     /// repeatedly checking until the lock becomes available.
31     /// </summary>
32     /// <remarks>
33     /// <para>
34     /// Spin locks can be used for leaf-level locks where the object allocation implied by using a <see
35     /// cref="System.Threading.Monitor"/>, in size or due to garbage collection pressure, is overly
36     /// expensive. Avoiding blocking is another reason that a spin lock can be useful, however if you expect
37     /// any significant amount of blocking, you are probably best not using spin locks due to excessive
38     /// spinning. Spinning can be beneficial when locks are fine grained and large in number (for example, a
39     /// lock per node in a linked list) as well as when lock hold times are always extremely short. In
40     /// general, while holding a spin lock, one should avoid blocking, calling anything that itself may
41     /// block, holding more than one spin lock at once, making dynamically dispatched calls (interface and
42     /// virtuals), making statically dispatched calls into any code one doesn't own, or allocating memory.
43     /// </para>
44     /// <para>
45     /// <see cref="SpinLock"/> should only be used when it's been determined that doing so will improve an
46     /// application's performance. It's also important to note that <see cref="SpinLock"/> is a value type,
47     /// for performance reasons. As such, one must be very careful not to accidentally copy a SpinLock
48     /// instance, as the two instances (the original and the copy) would then be completely independent of
49     /// one another, which would likely lead to erroneous behavior of the application. If a SpinLock instance
50     /// must be passed around, it should be passed by reference rather than by value.
51     /// </para>
52     /// <para>
53     /// Do not store <see cref="SpinLock"/> instances in readonly fields.
54     /// </para>
55     /// <para>
56     /// All members of <see cref="SpinLock"/> are thread-safe and may be used from multiple threads
57     /// concurrently.
58     /// </para>
59     /// </remarks>
60     [ComVisible(false)]
61     [HostProtection(Synchronization = true, ExternalThreading = true)]
62     [DebuggerTypeProxy(typeof(SystemThreading_SpinLockDebugView))]
63     [DebuggerDisplay("IsHeld = {IsHeld}")]
64     public struct SpinLock
65     {
66         // The current ownership state is a single signed int. There are two modes:
67         //
68         //    1) Ownership tracking enabled: the high bit is 0, and the remaining bits
69         //       store the managed thread ID of the current owner.  When the 31 low bits
70         //       are 0, the lock is available.
71         //    2) Performance mode: when the high bit is 1, lock availability is indicated by the low bit.  
72         //       When the low bit is 1 -- the lock is held; 0 -- the lock is available.
73         //
74         // There are several masks and constants below for convenience.
75
76         private volatile int m_owner;
77
78         // The multiplier factor for the each spinning iteration
79         // This number has been chosen after trying different numbers on different CPUs (4, 8 and 16 ) and this provided the best results
80         private const int SPINNING_FACTOR = 100;
81
82         // After how many yields, call Sleep(1)
83         private const int SLEEP_ONE_FREQUENCY = 40;
84
85         // After how many yields, call Sleep(0)
86         private const int SLEEP_ZERO_FREQUENCY = 10;
87
88         // After how many yields, check the timeout
89         private const int TIMEOUT_CHECK_FREQUENCY = 10;
90
91         // Thr thread tracking disabled mask
92         private const int LOCK_ID_DISABLE_MASK = unchecked((int)0x80000000);        //1000 0000 0000 0000 0000 0000 0000 0000
93
94         //the lock is held by some thread, but we don't know which
95         private const int LOCK_ANONYMOUS_OWNED = 0x1;                               //0000 0000 0000 0000 0000 0000 0000 0001
96
97         // Waiters mask if the thread tracking is disabled
98         private const int WAITERS_MASK = ~(LOCK_ID_DISABLE_MASK | 1);               //0111 1111 1111 1111 1111 1111 1111 1110
99
100         // 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
101         private const int ID_DISABLED_AND_ANONYMOUS_OWNED = unchecked((int)0x80000001); //1000 0000 0000 0000 0000 0000 0000 0001
102
103         // If the thread is unowned if:
104         // m_owner zero and the threa tracking is enabled
105         // m_owner & LOCK_ANONYMOUS_OWNED = zero and the thread tracking is disabled
106         private const int LOCK_UNOWNED = 0;
107
108         // The maximum number of waiters (only used if the thread tracking is disabled)
109         // The actual maximum waiters count is this number divided by two because each waiter increments the waiters count by 2
110         // The waiters count is calculated by m_owner & WAITERS_MASK 01111....110
111         private static int MAXIMUM_WAITERS = WAITERS_MASK;
112
113
114         /// <summary>
115         /// Initializes a new instance of the <see cref="T:System.Threading.SpinLock"/>
116         /// structure with the option to track thread IDs to improve debugging.
117         /// </summary>
118         /// <remarks>
119         /// The default constructor for <see cref="SpinLock"/> tracks thread ownership.
120         /// </remarks>
121         /// <param name="enableThreadOwnerTracking">Whether to capture and use thread IDs for debugging
122         /// purposes.</param>
123         public SpinLock(bool enableThreadOwnerTracking)
124         {
125             m_owner = LOCK_UNOWNED;
126             if (!enableThreadOwnerTracking)
127             {
128                 m_owner |= LOCK_ID_DISABLE_MASK;
129                 Contract.Assert(!IsThreadOwnerTrackingEnabled, "property should be false by now");
130             }
131         }
132
133
134         /// <summary>
135         /// Initializes a new instance of the <see cref="T:System.Threading.SpinLock"/>
136         /// structure with the option to track thread IDs to improve debugging.
137         /// </summary>
138         /// <remarks>
139         /// The default constructor for <see cref="SpinLock"/> tracks thread ownership.
140         /// </remarks>
141         /// <summary>
142         /// Acquires the lock in a reliable manner, such that even if an exception occurs within the method
143         /// call, <paramref name="lockTaken"/> can be examined reliably to determine whether the lock was
144         /// acquired.
145         /// </summary>
146         /// <remarks>
147         /// <see cref="SpinLock"/> is a non-reentrant lock, meaning that if a thread holds the lock, it is
148         /// not allowed to enter the lock again. If thread ownership tracking is enabled (whether it's
149         /// enabled is available through <see cref="IsThreadOwnerTrackingEnabled"/>), an exception will be
150         /// thrown when a thread tries to re-enter a lock it already holds. However, if thread ownership
151         /// tracking is disabled, attempting to enter a lock already held will result in deadlock.
152         /// </remarks>
153         /// <param name="lockTaken">True if the lock is acquired; otherwise, false. <paramref
154         /// name="lockTaken"/> must be initialized to false prior to calling this method.</param>
155         /// <exception cref="T:System.Threading.LockRecursionException">
156         /// Thread ownership tracking is enabled, and the current thread has already acquired this lock.
157         /// </exception>
158         /// <exception cref="T:System.ArgumentException">
159         /// The <paramref name="lockTaken"/> argument must be initialized to false prior to calling Enter.
160         /// </exception>
161         public void Enter(ref bool lockTaken)
162         {
163 #if !FEATURE_CORECLR
164             Thread.BeginCriticalRegion();
165 #endif
166             //Try to keep the code and branching in this method as small as possible in order to inline the method
167             int observedOwner = m_owner;
168             if (lockTaken || //invalid parameter
169                 (observedOwner & ID_DISABLED_AND_ANONYMOUS_OWNED) != LOCK_ID_DISABLE_MASK || //thread tracking is enabled or the lock is already acquired
170                 Interlocked.CompareExchange(ref m_owner, observedOwner | LOCK_ANONYMOUS_OWNED, observedOwner, ref lockTaken) != observedOwner) //acquiring the lock failed
171                 ContinueTryEnter(Timeout.Infinite, ref lockTaken); // Then try the slow path if any of the above conditions is met
172
173         }
174
175         /// <summary>
176         /// Attempts to acquire the lock in a reliable manner, such that even if an exception occurs within
177         /// the method call, <paramref name="lockTaken"/> can be examined reliably to determine whether the
178         /// lock was acquired.
179         /// </summary>
180         /// <remarks>
181         /// Unlike <see cref="Enter"/>, TryEnter will not block waiting for the lock to be available. If the
182         /// lock is not available when TryEnter is called, it will return immediately without any further
183         /// spinning.
184         /// </remarks>
185         /// <param name="lockTaken">True if the lock is acquired; otherwise, false. <paramref
186         /// name="lockTaken"/> must be initialized to false prior to calling this method.</param>
187         /// <exception cref="T:System.Threading.LockRecursionException">
188         /// Thread ownership tracking is enabled, and the current thread has already acquired this lock.
189         /// </exception>
190         /// <exception cref="T:System.ArgumentException">
191         /// The <paramref name="lockTaken"/> argument must be initialized to false prior to calling TryEnter.
192         /// </exception>
193         public void TryEnter(ref bool lockTaken)
194         {
195             TryEnter(0, ref lockTaken);
196         }
197
198         /// <summary>
199         /// Attempts to acquire the lock in a reliable manner, such that even if an exception occurs within
200         /// the method call, <paramref name="lockTaken"/> can be examined reliably to determine whether the
201         /// lock was acquired.
202         /// </summary>
203         /// <remarks>
204         /// Unlike <see cref="Enter"/>, TryEnter will not block indefinitely waiting for the lock to be
205         /// available. It will block until either the lock is available or until the <paramref
206         /// name="timeout"/>
207         /// has expired.
208         /// </remarks>
209         /// <param name="timeout">A <see cref="System.TimeSpan"/> that represents the number of milliseconds
210         /// to wait, or a <see cref="System.TimeSpan"/> that represents -1 milliseconds to wait indefinitely.
211         /// </param>
212         /// <param name="lockTaken">True if the lock is acquired; otherwise, false. <paramref
213         /// name="lockTaken"/> must be initialized to false prior to calling this method.</param>
214         /// <exception cref="T:System.Threading.LockRecursionException">
215         /// Thread ownership tracking is enabled, and the current thread has already acquired this lock.
216         /// </exception>
217         /// <exception cref="T:System.ArgumentException">
218         /// The <paramref name="lockTaken"/> argument must be initialized to false prior to calling TryEnter.
219         /// </exception>
220         /// <exception cref="T:System.ArgumentOutOfRangeException"><paramref name="timeout"/> is a negative
221         /// number other than -1 milliseconds, which represents an infinite time-out -or- timeout is greater
222         /// than <see cref="System.Int32.MaxValue"/> milliseconds.
223         /// </exception>
224         public void TryEnter(TimeSpan timeout, ref bool lockTaken)
225         {
226             // Validate the timeout
227             Int64 totalMilliseconds = (Int64)timeout.TotalMilliseconds;
228             if (totalMilliseconds < -1 || totalMilliseconds > int.MaxValue)
229             {
230                 throw new System.ArgumentOutOfRangeException(
231                     "timeout", timeout, Environment.GetResourceString("SpinLock_TryEnter_ArgumentOutOfRange"));
232             }
233
234             // Call reliable enter with the int-based timeout milliseconds
235             TryEnter((int)timeout.TotalMilliseconds, ref lockTaken);
236         }
237
238         /// <summary>
239         /// Attempts to acquire the lock in a reliable manner, such that even if an exception occurs within
240         /// the method call, <paramref name="lockTaken"/> can be examined reliably to determine whether the
241         /// lock was acquired.
242         /// </summary>
243         /// <remarks>
244         /// Unlike <see cref="Enter"/>, TryEnter will not block indefinitely waiting for the lock to be
245         /// available. It will block until either the lock is available or until the <paramref
246         /// name="millisecondsTimeout"/> has expired.
247         /// </remarks>
248         /// <param name="millisecondsTimeout">The number of milliseconds to wait, or <see
249         /// cref="System.Threading.Timeout.Infinite"/> (-1) to wait indefinitely.</param>
250         /// <param name="lockTaken">True if the lock is acquired; otherwise, false. <paramref
251         /// name="lockTaken"/> must be initialized to false prior to calling this method.</param>
252         /// <exception cref="T:System.Threading.LockRecursionException">
253         /// Thread ownership tracking is enabled, and the current thread has already acquired this lock.
254         /// </exception>
255         /// <exception cref="T:System.ArgumentException">
256         /// The <paramref name="lockTaken"/> argument must be initialized to false prior to calling TryEnter.
257         /// </exception>
258         /// <exception cref="T:System.ArgumentOutOfRangeException"><paramref name="millisecondsTimeout"/> is
259         /// a negative number other than -1, which represents an infinite time-out.</exception>
260         public void TryEnter(int millisecondsTimeout, ref bool lockTaken)
261         {
262 #if !FEATURE_CORECLR
263             Thread.BeginCriticalRegion();
264 #endif
265
266             int observedOwner = m_owner;
267             if (millisecondsTimeout < -1 || //invalid parameter
268                 lockTaken || //invalid parameter
269                 (observedOwner & ID_DISABLED_AND_ANONYMOUS_OWNED) != LOCK_ID_DISABLE_MASK ||  //thread tracking is enabled or the lock is already acquired
270                 Interlocked.CompareExchange(ref m_owner, observedOwner | LOCK_ANONYMOUS_OWNED, observedOwner, ref lockTaken) != observedOwner) // acquiring the lock failed
271                 ContinueTryEnter(millisecondsTimeout, ref lockTaken); // The call the slow pth
272         }
273
274         /// <summary>
275         /// Try acquire the lock with long path, this is usually called after the first path in Enter and
276         /// TryEnter failed The reason for short path is to make it inline in the run time which improves the
277         /// performance. This method assumed that the parameter are validated in Enter ir TryENter method
278         /// </summary>
279         /// <param name="millisecondsTimeout">The timeout milliseconds</param>
280         /// <param name="lockTaken">The lockTaken param</param>
281         private void ContinueTryEnter(int millisecondsTimeout, ref bool lockTaken)
282         {
283             //Leave the critical region which is entered by the fast path
284 #if !FEATURE_CORECLR
285             Thread.EndCriticalRegion();
286 #endif
287             // The fast path doesn't throw any exception, so we have to validate the parameters here
288             if (lockTaken)
289             {
290                 lockTaken = false;
291                 throw new System.ArgumentException(Environment.GetResourceString("SpinLock_TryReliableEnter_ArgumentException"));
292             }
293
294             if (millisecondsTimeout < -1)
295             {
296                 throw new ArgumentOutOfRangeException(
297                     "millisecondsTimeout", millisecondsTimeout, Environment.GetResourceString("SpinLock_TryEnter_ArgumentOutOfRange"));
298             }
299
300
301             uint startTime = 0;
302             if (millisecondsTimeout != Timeout.Infinite && millisecondsTimeout != 0)
303             {
304                 startTime = TimeoutHelper.GetTime();
305             }
306
307 #if !FEATURE_PAL && !FEATURE_CORECLR   // PAL doesn't support  eventing, and we don't compile CDS providers for Coreclr
308             if (CdsSyncEtwBCLProvider.Log.IsEnabled())
309             {
310                 CdsSyncEtwBCLProvider.Log.SpinLock_FastPathFailed(m_owner);
311             }
312 #endif
313
314             if (IsThreadOwnerTrackingEnabled)
315             {
316                 // Slow path for enabled thread tracking mode
317                 ContinueTryEnterWithThreadTracking(millisecondsTimeout, startTime, ref lockTaken);
318                 return;
319             }
320
321             // then thread tracking is disabled
322             // In this case there are three ways to acquire the lock
323             // 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
324             // 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
325             // the late the thread arrives the more it spins and less frequent it check the lock avilability
326             // Also the spins count is increases each iteration
327             // If the spins iterations finished and failed to acquire the lock, go to step 3
328             // 3- This is the yielding step, there are two ways of yielding Thread.Yield and Sleep(1)
329             // If the timeout is expired in after step 1, we need to decrement the waiters count before returning
330
331             int observedOwner;
332             int turn = int.MaxValue;
333             //***Step 1, take the lock or update the waiters
334
335             // try to acquire the lock directly if possible or update the waiters count
336             observedOwner = m_owner;
337             if ((observedOwner & LOCK_ANONYMOUS_OWNED) == LOCK_UNOWNED)
338             {
339 #if !FEATURE_CORECLR
340                 Thread.BeginCriticalRegion();
341 #endif
342
343 #if PFX_LEGACY_3_5
344                     if (Interlocked.CompareExchange(ref m_owner, observedOwner | 1, observedOwner) == observedOwner)
345                     {
346                         lockTaken = true;
347                         return;
348                     }
349 #else
350                 if (Interlocked.CompareExchange(ref m_owner, observedOwner | 1, observedOwner, ref lockTaken) == observedOwner)
351                 {
352                     return;
353                 }
354 #endif
355
356 #if !FEATURE_CORECLR
357                 Thread.EndCriticalRegion();
358 #endif
359             }
360             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
361             {
362                 if ((observedOwner & WAITERS_MASK) != MAXIMUM_WAITERS)
363                     turn = (Interlocked.Add(ref m_owner, 2) & WAITERS_MASK) >> 1 ;
364             }
365
366
367
368             // Check the timeout.
369             if (millisecondsTimeout == 0 ||
370                 (millisecondsTimeout != Timeout.Infinite &&
371                 TimeoutHelper.UpdateTimeOut(startTime, millisecondsTimeout) <= 0))
372             {
373                 DecrementWaiters();
374                 return;
375             }
376
377             //***Step 2. Spinning
378             //lock acquired failed and waiters updated
379             int processorCount = PlatformHelper.ProcessorCount;
380             if (turn < processorCount)
381             {
382                 int processFactor = 1;
383                 for (int i = 1; i <= turn * SPINNING_FACTOR; i++)
384                 {
385                     Thread.SpinWait((turn + i) * SPINNING_FACTOR * processFactor);
386                     if (processFactor < processorCount)
387                         processFactor++;
388                     observedOwner = m_owner;
389                     if ((observedOwner & LOCK_ANONYMOUS_OWNED) == LOCK_UNOWNED)
390                     {
391 #if !FEATURE_CORECLR
392                         Thread.BeginCriticalRegion();
393 #endif
394
395                         int newOwner = (observedOwner & WAITERS_MASK) == 0 ? // Gets the number of waiters, if zero
396                             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
397                             : (observedOwner - 2) | 1; // otherwise decrement the waiters and set the lock bit
398                         Contract.Assert((newOwner & WAITERS_MASK) >= 0);
399 #if PFX_LEGACY_3_5
400                         if (Interlocked.CompareExchange(ref m_owner, newOwner, observedOwner) == observedOwner)
401                         {
402                             lockTaken = true;
403                             return;
404                         }
405 #else
406                         if (Interlocked.CompareExchange(ref m_owner, newOwner, observedOwner, ref lockTaken) == observedOwner)
407                         {
408                             return;
409                         }
410 #endif
411
412 #if !FEATURE_CORECLR
413                         Thread.EndCriticalRegion();
414 #endif
415                     }
416                 }
417             }
418
419             // Check the timeout.
420             if (millisecondsTimeout != Timeout.Infinite && TimeoutHelper.UpdateTimeOut(startTime, millisecondsTimeout) <= 0)
421             {
422                 DecrementWaiters();
423                 return;
424             }
425
426             //*** Step 3, Yielding
427             //Sleep(1) every 50 yields
428             int yieldsoFar = 0;
429             while (true)
430             {
431                 observedOwner = m_owner;
432                 if ((observedOwner & LOCK_ANONYMOUS_OWNED) == LOCK_UNOWNED)
433                 {
434 #if !FEATURE_CORECLR
435                     Thread.BeginCriticalRegion();
436 #endif
437                     int newOwner = (observedOwner & WAITERS_MASK) == 0 ? // Gets the number of waiters, if zero
438                            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
439                            : (observedOwner - 2) | 1; // otherwise decrement the waiters and set the lock bit
440                     Contract.Assert((newOwner & WAITERS_MASK) >= 0);
441 #if PFX_LEGACY_3_5
442                     if (Interlocked.CompareExchange(ref m_owner, newOwner, observedOwner) == observedOwner)
443                     {
444                         lockTaken = true;
445                         return;
446                     }
447 #else
448                     if (Interlocked.CompareExchange(ref m_owner, newOwner, observedOwner, ref lockTaken) == observedOwner)
449                     {
450                         return;
451                     }
452 #endif
453
454 #if !FEATURE_CORECLR
455                     Thread.EndCriticalRegion();
456 #endif
457                 }
458
459                 if (yieldsoFar % SLEEP_ONE_FREQUENCY == 0)
460                 {
461                     Thread.Sleep(1);
462                 }
463                 else if (yieldsoFar % SLEEP_ZERO_FREQUENCY == 0)
464                 {
465                     Thread.Sleep(0);
466                 }
467                 else
468                 {
469 #if PFX_LEGACY_3_5
470                     Platform.Yield();
471 #else
472                     Thread.Yield();
473 #endif
474                 }
475
476                 if (yieldsoFar % TIMEOUT_CHECK_FREQUENCY == 0)
477                 {
478                     //Check the timeout.
479                     if (millisecondsTimeout != Timeout.Infinite && TimeoutHelper.UpdateTimeOut(startTime, millisecondsTimeout) <= 0)
480                     {
481                         DecrementWaiters();
482                         return;
483                     }
484                 }
485
486                 yieldsoFar++;
487             }
488         }
489
490         /// <summary>
491         /// decrements the waiters, in case of the timeout is expired
492         /// </summary>
493         private void DecrementWaiters()
494         {
495             SpinWait spinner = new SpinWait();
496             while (true)
497             {
498                 int observedOwner = m_owner;
499                 if ((observedOwner & WAITERS_MASK) == 0) return; // don't decrement the waiters if it's corrupted by previous call of Exit(false)
500                 if (Interlocked.CompareExchange(ref m_owner, observedOwner - 2, observedOwner) == observedOwner)
501                 {
502                     Contract.Assert(!IsThreadOwnerTrackingEnabled); // Make sure the waiters never be negative which will cause the thread tracking bit to be flipped
503                     break;
504                 }
505                 spinner.SpinOnce();
506             }
507
508         }
509
510         /// <summary>
511         /// ContinueTryEnter for the thread tracking mode enabled
512         /// </summary>
513         private void ContinueTryEnterWithThreadTracking(int millisecondsTimeout, uint startTime, ref bool lockTaken)
514         {
515             Contract.Assert(IsThreadOwnerTrackingEnabled);
516
517             int lockUnowned = 0;
518             // We are using thread IDs to mark ownership. Snap the thread ID and check for recursion.
519             // We also must or the ID enablement bit, to ensure we propagate when we CAS it in.
520             int m_newOwner = Thread.CurrentThread.ManagedThreadId;
521             if (m_owner == m_newOwner)
522             {
523                 // We don't allow lock recursion.
524                 throw new LockRecursionException(Environment.GetResourceString("SpinLock_TryEnter_LockRecursionException"));
525             }
526
527
528             SpinWait spinner = new SpinWait();
529
530             // Loop until the lock has been successfully acquired or, if specified, the timeout expires.
531             do
532             {
533
534                 // We failed to get the lock, either from the fast route or the last iteration
535                 // and the timeout hasn't expired; spin once and try again.
536                 spinner.SpinOnce();
537
538                 // Test before trying to CAS, to avoid acquiring the line exclusively unnecessarily.
539
540                 if (m_owner == lockUnowned)
541                 {
542 #if !FEATURE_CORECLR
543                     Thread.BeginCriticalRegion();
544 #endif
545 #if PFX_LEGACY_3_5
546                     if (Interlocked.CompareExchange(ref m_owner, m_newOwner, lockUnowned) == lockUnowned)
547                     {
548                         lockTaken = true;
549                         return;
550                     }
551 #else
552                     if (Interlocked.CompareExchange(ref m_owner, m_newOwner, lockUnowned, ref lockTaken) == lockUnowned)
553                     {
554                         return;
555                     }
556 #endif
557
558 #if !FEATURE_CORECLR
559                     // The thread failed to get the lock, so we don't need to remain in a critical region.
560                     Thread.EndCriticalRegion();
561 #endif
562                 }
563                 // Check the timeout.  We only RDTSC if the next spin will yield, to amortize the cost.
564                 if (millisecondsTimeout == 0 ||
565                     (millisecondsTimeout != Timeout.Infinite && spinner.NextSpinWillYield &&
566                     TimeoutHelper.UpdateTimeOut(startTime, millisecondsTimeout) <= 0))
567                 {
568                     return;
569                 }
570             } while (true);
571         }
572
573         /// <summary>
574         /// Releases the lock.
575         /// </summary>
576         /// <remarks>
577         /// The default overload of <see cref="Exit()"/> provides the same behavior as if calling <see
578         /// cref="Exit(Boolean)"/> using true as the argument, but Exit() could be slightly faster than Exit(true).
579         /// </remarks>
580         /// <exception cref="SynchronizationLockException">
581         /// Thread ownership tracking is enabled, and the current thread is not the owner of this lock.
582         /// </exception>
583         [ReliabilityContract(Consistency.WillNotCorruptState, Cer.Success)]
584         public void Exit()
585         {
586             //This is the fast path for the thread tracking is disabled, otherwise go to the slow path
587             if ((m_owner & LOCK_ID_DISABLE_MASK) == 0)
588                 ExitSlowPath(true);
589             else
590                 Interlocked.Decrement(ref m_owner);
591
592 #if !FEATURE_CORECLR
593             Thread.EndCriticalRegion();
594 #endif
595
596         }
597
598         /// <summary>
599         /// Releases the lock.
600         /// </summary>
601         /// <param name="useMemoryBarrier">
602         /// A Boolean value that indicates whether a memory fence should be issued in order to immediately
603         /// publish the exit operation to other threads.
604         /// </param>
605         /// <remarks>
606         /// Calling <see cref="Exit(Boolean)"/> with the <paramref name="useMemoryBarrier"/> argument set to
607         /// true will improve the fairness of the lock at the expense of some performance. The default <see
608         /// cref="Enter"/>
609         /// overload behaves as if specifying true for <paramref name="useMemoryBarrier"/>.
610         /// </remarks>
611         /// <exception cref="SynchronizationLockException">
612         /// Thread ownership tracking is enabled, and the current thread is not the owner of this lock.
613         /// </exception>
614         [ReliabilityContract(Consistency.WillNotCorruptState, Cer.Success)]
615         public void Exit(bool useMemoryBarrier)
616         {
617             // This is the fast path for the thread tracking is diabled and not to use memory barrier, otherwise go to the slow path
618             // The reason not to add else statement if the usememorybarrier is that it will add more barnching in the code and will prevent
619             // method inlining, so this is optimized for useMemoryBarrier=false and Exit() overload optimized for useMemoryBarrier=true
620             if ((m_owner & LOCK_ID_DISABLE_MASK) != 0 && !useMemoryBarrier)
621             {
622                 int tmpOwner = m_owner;
623                 m_owner = tmpOwner & (~LOCK_ANONYMOUS_OWNED);
624             }
625             else
626                 ExitSlowPath(useMemoryBarrier);
627
628 #if !FEATURE_CORECLR
629             Thread.EndCriticalRegion();
630 #endif
631         }
632
633         /// <summary>
634         /// The slow path for exit method if the fast path failed
635         /// </summary>
636         /// <param name="useMemoryBarrier">
637         /// A Boolean value that indicates whether a memory fence should be issued in order to immediately
638         /// publish the exit operation to other threads
639         /// </param>
640         private void ExitSlowPath(bool useMemoryBarrier)
641         {
642             bool threadTrackingEnabled = (m_owner & LOCK_ID_DISABLE_MASK) == 0;
643             if (threadTrackingEnabled && !IsHeldByCurrentThread)
644             {
645                 throw new System.Threading.SynchronizationLockException(
646                     Environment.GetResourceString("SpinLock_Exit_SynchronizationLockException"));
647             }
648
649             if (useMemoryBarrier)
650             {
651                 if (threadTrackingEnabled)
652                     Interlocked.Exchange(ref m_owner, LOCK_UNOWNED);
653                 else
654                     Interlocked.Decrement(ref m_owner);
655
656             }
657             else
658             {
659                 if (threadTrackingEnabled)
660                     m_owner = LOCK_UNOWNED;
661                 else
662                 {
663                     int tmpOwner = m_owner;
664                     m_owner = tmpOwner & (~LOCK_ANONYMOUS_OWNED);
665                 }
666
667             }
668
669         }
670
671         /// <summary>
672         /// Gets whether the lock is currently held by any thread.
673         /// </summary>
674         public bool IsHeld
675         {
676             [ReliabilityContract(Consistency.WillNotCorruptState, Cer.Success)]
677             get
678             {
679                 if (IsThreadOwnerTrackingEnabled)
680                     return m_owner != LOCK_UNOWNED;
681
682                 return (m_owner & LOCK_ANONYMOUS_OWNED) != LOCK_UNOWNED;
683             }
684         }
685
686         /// <summary>
687         /// Gets whether the lock is currently held by any thread.
688         /// </summary>
689         /// <summary>
690         /// Gets whether the lock is held by the current thread.
691         /// </summary>
692         /// <remarks>
693         /// If the lock was initialized to track owner threads, this will return whether the lock is acquired
694         /// by the current thread. It is invalid to use this property when the lock was initialized to not
695         /// track thread ownership.
696         /// </remarks>
697         /// <exception cref="T:System.InvalidOperationException">
698         /// Thread ownership tracking is disabled.
699         /// </exception>
700         public bool IsHeldByCurrentThread
701         {
702             [ReliabilityContract(Consistency.WillNotCorruptState, Cer.Success)]
703             get
704             {
705                 if (!IsThreadOwnerTrackingEnabled)
706                 {
707                     throw new InvalidOperationException(Environment.GetResourceString("SpinLock_IsHeldByCurrentThread"));
708                 }
709                 return ((m_owner & (~LOCK_ID_DISABLE_MASK)) == Thread.CurrentThread.ManagedThreadId);
710             }
711         }
712
713         /// <summary>Gets whether thread ownership tracking is enabled for this instance.</summary>
714         public bool IsThreadOwnerTrackingEnabled
715         {
716             [ReliabilityContract(Consistency.WillNotCorruptState, Cer.Success)]
717             get { return (m_owner & LOCK_ID_DISABLE_MASK) == 0; }
718         }
719
720         #region Debugger proxy class
721         /// <summary>
722         /// Internal class used by debug type proxy attribute to display the owner thread ID 
723         /// </summary>
724         internal class SystemThreading_SpinLockDebugView
725         {
726             // SpinLock object
727             private SpinLock m_spinLock;
728
729             /// <summary>
730             /// SystemThreading_SpinLockDebugView constructor
731             /// </summary>
732             /// <param name="spinLock">The SpinLock to be proxied.</param>
733             public SystemThreading_SpinLockDebugView(SpinLock spinLock)
734             {
735                 // Note that this makes a copy of the SpinLock (struct). It doesn't hold a reference to it.
736                 m_spinLock = spinLock;
737             }
738
739             /// <summary>
740             /// Checks if the lock is held by the current thread or not
741             /// </summary>
742             public bool? IsHeldByCurrentThread
743             {
744                 get
745                 {
746                     try
747                     {
748                         return m_spinLock.IsHeldByCurrentThread;
749                     }
750                     catch (InvalidOperationException)
751                     {
752                         return null;
753                     }
754                 }
755             }
756
757             /// <summary>
758             /// Gets the current owner thread, zero if it is released
759             /// </summary>
760             public int? OwnerThreadID
761             {
762                 get
763                 {
764                     if (m_spinLock.IsThreadOwnerTrackingEnabled)
765                     {
766                         return m_spinLock.m_owner;
767                     }
768                     else
769                     {
770                         return null;
771                     }
772                 }
773             }
774
775
776             /// <summary>
777             ///  Gets whether the lock is currently held by any thread or not.
778             /// </summary>
779             public bool IsHeld
780             {
781                 get { return m_spinLock.IsHeld; }
782             }
783         }
784         #endregion
785
786     }
787 }
788 #pragma warning restore 0420