Merge pull request #409 from Alkarex/patch-1
[mono.git] / mcs / class / corlib / System.Threading / SpinLock.cs
index e3fb372dd26551be9327ea5a6349eacb7a0e5eab..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)]
@@ -39,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;
@@ -49,6 +57,10 @@ namespace System.Threading
                int threadWhoTookLock;
                readonly bool isThreadOwnerTrackingEnabled;
 
+               static readonly Watch sw = Watch.StartNew ();
+
+               ConcurrentOrderedList<int> stallTickets;
+
                public bool IsThreadOwnerTrackingEnabled {
                        get {
                                return isThreadOwnerTrackingEnabled;
@@ -72,14 +84,15 @@ 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("This method is not rigorously correct. Need CER treatment")]
+               [MonoTODO ("Not safe against async exceptions")]
                public void Enter (ref bool lockTaken)
                {
                        if (lockTaken)
@@ -87,79 +100,100 @@ namespace System.Threading
                        if (isThreadOwnerTrackingEnabled && IsHeldByCurrentThread)
                                throw new LockRecursionException ();
 
-                       int slot = Interlocked.Increment (ref ticket.Users) - 1;
+                       int slot = -1;
+
+                       RuntimeHelpers.PrepareConstrainedRegions ();
+                       try {
+                               slot = Interlocked.Increment (ref ticket.Users) - 1;
 
-                       SpinWait wait;
-                       while (slot != ticket.Value)
-                               wait.SpinOnce ();
-                       
-                       lockTaken = true;
-                       
-                       threadWhoTookLock = Thread.CurrentThread.ManagedThreadId;
+                               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);
+                               }
+                       }
                }
 
-               [MonoTODO("This method is not rigorously correct. Need CER treatment")]
                public void TryEnter (ref bool lockTaken)
                {
                        TryEnter (0, ref lockTaken);
                }
 
-               [MonoTODO("This method is not rigorously correct. Need CER treatment")]
                public void TryEnter (TimeSpan timeout, ref bool lockTaken)
                {
                        TryEnter ((int)timeout.TotalMilliseconds, ref lockTaken);
                }
 
-               [MonoTODO("This method is not rigorously correct. Need CER treatment")]
-               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 ();
 
-                       Watch sw = Watch.StartNew ();
+                       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
                                        = BitConverter.IsLittleEndian ? (u << 32) | (u + 1) : ((u + 1) << 32) | u;
                                
-                               lockTaken = Interlocked.CompareExchange (ref ticket.TotalValue, newTotalValue, totalValue) == totalValue;
+                               RuntimeHelpers.PrepareConstrainedRegions ();
+                               try {}
+                               finally {
+                                       lockTaken = Interlocked.CompareExchange (ref ticket.TotalValue, newTotalValue, totalValue) == totalValue;
                                
-                               if (lockTaken) {
-                                       threadWhoTookLock = Thread.CurrentThread.ManagedThreadId;
-                                       break;
+                                       if (lockTaken) {
+                                               threadWhoTookLock = Thread.CurrentThread.ManagedThreadId;
+                                               stop = true;
+                                       }
                                }
-                       } while (milliSeconds == -1 || (milliSeconds > 0 && sw.ElapsedMilliseconds < 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)
                {
-                       if (isThreadOwnerTrackingEnabled && !IsHeldByCurrentThread)
-                               throw new SynchronizationLockException ("Current thread is not the owner of this lock");
-
-                       threadWhoTookLock = int.MinValue;
-                       if (flushReleaseWrites)
-                               Interlocked.Increment (ref ticket.Value);
-                       else
-                               ticket.Value++;
+                       RuntimeHelpers.PrepareConstrainedRegions ();
+                       try {}
+                       finally {
+                               if (isThreadOwnerTrackingEnabled && !IsHeldByCurrentThread)
+                                       throw new SynchronizationLockException ("Current thread is not the owner of this lock");
+
+                               threadWhoTookLock = int.MinValue;
+                               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