Merge pull request #409 from Alkarex/patch-1
[mono.git] / mcs / class / corlib / System.Threading / SpinLock.cs
index 055b7fcefbe38903de1f12f6f261cf3dea62cc75..683c98374180e2dad7e7a2160cda1144ccfeabee 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
 //
 //
 
+#if NET_4_0 || MOBILE
+
 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 +43,13 @@ 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
        {
                TicketType ticket;
@@ -51,7 +57,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 {
@@ -76,11 +84,12 @@ namespace System.Threading
                        }
                }
 
-               public SpinLock (bool trackId)
+               public SpinLock (bool enableThreadOwnerTracking)
                {
-                       this.isThreadOwnerTrackingEnabled = trackId;
+                       this.isThreadOwnerTrackingEnabled = enableThreadOwnerTracking;
                        this.threadWhoTookLock = 0;
                        this.ticket = new TicketType ();
+                       this.stallTickets = null;
                }
 
                [MonoTODO ("Not safe against async exceptions")]
@@ -91,21 +100,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)
@@ -118,19 +136,22 @@ namespace System.Threading
                        TryEnter ((int)timeout.TotalMilliseconds, ref lockTaken);
                }
 
-               public void TryEnter (int milliSeconds, ref bool lockTaken)
+               public void TryEnter (int millisecondsTimeout, ref bool lockTaken)
                {
-                       if (milliSeconds < -1)
+                       if (millisecondsTimeout < -1)
                                throw new ArgumentOutOfRangeException ("milliSeconds", "millisecondsTimeout is a negative number other than -1");
                        if (lockTaken)
                                throw new ArgumentException ("lockTaken", "lockTaken must be initialized to false");
                        if (isThreadOwnerTrackingEnabled && IsHeldByCurrentThread)
                                throw new LockRecursionException ();
 
-                       long start = milliSeconds == -1 ? 0 : sw.ElapsedMilliseconds;
+                       long start = millisecondsTimeout == -1 ? 0 : sw.ElapsedMilliseconds;
                        bool stop = false;
 
                        do {
+                               while (stallTickets != null && stallTickets.TryRemove (ticket.Value))
+                                       ++ticket.Value;
+
                                long u = ticket.Users;
                                long totalValue = (u << 32) | u;
                                long newTotalValue
@@ -146,15 +167,17 @@ namespace System.Threading
                                                stop = true;
                                        }
                                }
-               } while (!stop && (milliSeconds == -1 || (sw.ElapsedMilliseconds - start) < milliSeconds));
+               } while (!stop && (millisecondsTimeout == -1 || (sw.ElapsedMilliseconds - start) < millisecondsTimeout));
                }
 
+               [ReliabilityContract (Consistency.WillNotCorruptState, Cer.Success)]
                public void Exit ()
                {
                        Exit (false);
                }
 
-               public void Exit (bool flushReleaseWrites)
+               [ReliabilityContract (Consistency.WillNotCorruptState, Cer.Success)]
+               public void Exit (bool useMemoryBarrier)
                {
                        RuntimeHelpers.PrepareConstrainedRegions ();
                        try {}
@@ -163,20 +186,14 @@ namespace System.Threading
                                        throw new SynchronizationLockException ("Current thread is not the owner of this lock");
 
                                threadWhoTookLock = int.MinValue;
-                               // Fast path
-                               if (flushReleaseWrites)
-                                       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