[bcl] Remove NET_4_0 defines from class libs.
[mono.git] / mcs / class / corlib / System.Threading / SpinLock.cs
index 122a3f094b2debef171d29b4646e58336acb5d63..28bb1527340f407d56860e23ee2f6762254b26a6 100644 (file)
@@ -1,6 +1,7 @@
 // SpinLock.cs
 //
 // Copyright (c) 2008 Jérémie "Garuma" Laval
+// Copyright 2011 Xamarin Inc (http://www.xamarin.com).
 //
 // Permission is hereby granted, free of charge, to any person obtaining a copy
 // of this software and associated documentation files (the "Software"), to deal
 //
 //
 
+
 using System;
+using System.Collections.Concurrent;
 using System.Runtime.ConstrainedExecution;
 using System.Runtime.InteropServices;
 using System.Runtime.CompilerServices;
 
-#if NET_4_0 || BOOTSTRAP_NET_4_0
-
 namespace System.Threading
 {
        [StructLayout(LayoutKind.Explicit)]
@@ -41,9 +42,11 @@ namespace System.Threading
                public int Users;
        }
 
-       // Implement the ticket SpinLock algorithm described on http://locklessinc.com/articles/locks/
-       // This lock is usable on both endianness
-       // TODO: some 32 bits platform apparently doesn't support CAS with 64 bits value
+       /* Implement the ticket SpinLock algorithm described on http://locklessinc.com/articles/locks/
+        * This lock is usable on both endianness.
+        * All the try/finally patterns in this class and various extra gimmicks compared to the original
+        * algorithm are here to avoid problems caused by asynchronous exceptions.
+        */
        [System.Diagnostics.DebuggerDisplay ("IsHeld = {IsHeld}")]
        [System.Diagnostics.DebuggerTypeProxy ("System.Threading.SpinLock+SystemThreading_SpinLockDebugView")]
        public struct SpinLock
@@ -53,7 +56,9 @@ namespace System.Threading
                int threadWhoTookLock;
                readonly bool isThreadOwnerTrackingEnabled;
 
-               static Watch sw = Watch.StartNew ();
+               static readonly Watch sw = Watch.StartNew ();
+
+               ConcurrentOrderedList<int> stallTickets;
 
                public bool IsThreadOwnerTrackingEnabled {
                        get {
@@ -83,6 +88,7 @@ namespace System.Threading
                        this.isThreadOwnerTrackingEnabled = enableThreadOwnerTracking;
                        this.threadWhoTookLock = 0;
                        this.ticket = new TicketType ();
+                       this.stallTickets = null;
                }
 
                [MonoTODO ("Not safe against async exceptions")]
@@ -93,21 +99,30 @@ namespace System.Threading
                        if (isThreadOwnerTrackingEnabled && IsHeldByCurrentThread)
                                throw new LockRecursionException ();
 
-                       /* The current ticket algorithm, even though it's a thing of beauty, doesn't make it easy to
-                        * hand back ticket that have been taken in the case of an asynchronous exception and naively
-                        * fixing it bloat a code that should be kept simple. A straightforward possibility is to wrap
-                        * the whole thing in a finally block but due to the while loop a number of bad things can
-                        * happen, thus for the moment the code is left as is in the spirit of "better breaking fast,
-                        * than later in a weird way".
-                        */
-                       int slot = Interlocked.Increment (ref ticket.Users) - 1;
-
-                       SpinWait wait = new SpinWait ();
-                       while (slot != ticket.Value)
-                               wait.SpinOnce ();
-
-                       lockTaken = true;
-                       threadWhoTookLock = Thread.CurrentThread.ManagedThreadId;
+                       int slot = -1;
+
+                       RuntimeHelpers.PrepareConstrainedRegions ();
+                       try {
+                               slot = Interlocked.Increment (ref ticket.Users) - 1;
+
+                               SpinWait wait = new SpinWait ();
+                               while (slot != ticket.Value) {
+                                       wait.SpinOnce ();
+
+                                       while (stallTickets != null && stallTickets.TryRemove (ticket.Value))
+                                               ++ticket.Value;
+                               }
+                       } finally {
+                               if (slot == ticket.Value) {
+                                       lockTaken = true;
+                                       threadWhoTookLock = Thread.CurrentThread.ManagedThreadId;
+                               } else if (slot != -1) {
+                                       // We have been interrupted, initialize stallTickets
+                                       if (stallTickets == null)
+                                               Interlocked.CompareExchange (ref stallTickets, new ConcurrentOrderedList<int> (), null);
+                                       stallTickets.TryAdd (slot);
+                               }
+                       }
                }
 
                public void TryEnter (ref bool lockTaken)
@@ -133,6 +148,9 @@ namespace System.Threading
                        bool stop = false;
 
                        do {
+                               while (stallTickets != null && stallTickets.TryRemove (ticket.Value))
+                                       ++ticket.Value;
+
                                long u = ticket.Users;
                                long totalValue = (u << 32) | u;
                                long newTotalValue
@@ -151,6 +169,7 @@ namespace System.Threading
                } while (!stop && (millisecondsTimeout == -1 || (sw.ElapsedMilliseconds - start) < millisecondsTimeout));
                }
 
+               [ReliabilityContract (Consistency.WillNotCorruptState, Cer.Success)]
                public void Exit ()
                {
                        Exit (false);
@@ -166,20 +185,13 @@ namespace System.Threading
                                        throw new SynchronizationLockException ("Current thread is not the owner of this lock");
 
                                threadWhoTookLock = int.MinValue;
-                               // Fast path
-                               if (useMemoryBarrier)
-                                       Interlocked.Increment (ref ticket.Value);
-                               else
-                                       ticket.Value++;
+                               do {
+                                       if (useMemoryBarrier)
+                                               Interlocked.Increment (ref ticket.Value);
+                                       else
+                                               ticket.Value++;
+                               } while (stallTickets != null && stallTickets.TryRemove (ticket.Value));
                        }
                }
        }
-
-       // Wraps a SpinLock in a reference when we need to pass
-       // around the lock
-       internal class SpinLockWrapper
-       {
-               public SpinLock Lock = new SpinLock (false);
-       }
 }
-#endif