2010-03-08 Jérémie Laval <jeremie.laval@gmail.com>
authorJérémie Laval <jeremie.laval@gmail.com>
Mon, 8 Mar 2010 17:21:05 +0000 (17:21 -0000)
committerJérémie Laval <jeremie.laval@gmail.com>
Mon, 8 Mar 2010 17:21:05 +0000 (17:21 -0000)
In class/corlib/:
   * corlib_test.dll.sources: Add System.Threading/SpinLockTests.cs

In class/corlib/System.Threading/:
   * SpinLock.cs: Update to use ticket spinlock algorithm

In class/corlib/Test/System.Threading/:
   * SpinLockTests.cs: Added unit tests for SpinLock

svn path=/trunk/mcs/; revision=153273

mcs/class/corlib/ChangeLog
mcs/class/corlib/System.Threading/ChangeLog
mcs/class/corlib/System.Threading/SpinLock.cs
mcs/class/corlib/Test/System.Threading/ChangeLog
mcs/class/corlib/Test/System.Threading/SpinLockTests.cs [new file with mode: 0644]
mcs/class/corlib/corlib_test.dll.sources

index ec9cc17cfe1d460c9f61f94188e96d7f8f2dbe0c..7c8380fb9673c0e9f4a830a12eb785c5698b21ad 100644 (file)
@@ -1,3 +1,7 @@
+2010-03-08  Jérémie Laval  <jeremie.laval@gmail.com>
+
+       * corlib_test.dll.sources: Add System.Threading/SpinLockTests.cs
+
 2010-03-02  Jérémie Laval  <jeremie.laval@gmail.com>
 
        * corlib_test.dll.sources: Add System.Threading.Tasks/TaskFactoryTest.cs
index f973aa83bc508d77ca4780a6bba17032a829c55e..e841e6b6c2dfba74a18efb1214db8d1d25f1656b 100644 (file)
@@ -1,3 +1,7 @@
+2010-03-08  Jérémie Laval  <jeremie.laval@gmail.com>
+
+       * SpinLock.cs: Update to use ticket spinlock algorithm
+
 2010-02-26 Rodrigo Kumpera  <rkumpera@novell.com>
 
        * LazyThreadSafetyMode.cs: Added.
index 4818869466a3b31491d4aa4c42cb0a38dd9fd4a7..726d1717a128cff7e5d8ad3b30a4d719264a9569 100644 (file)
 
 using System;
 using System.Runtime.ConstrainedExecution;
+using System.Runtime.InteropServices;
 
 #if NET_4_0
 namespace System.Threading
 {
+       // 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
+       internal static class SpinLockHelpers
+       {
+               [StructLayout(LayoutKind.Explicit)]
+               internal struct TicketType {
+                       [FieldOffset(0)]
+                       public long TotalValue;
+                       [FieldOffset(0)]
+                       public int Value;
+                       [FieldOffset(4)]
+                       public int Users;
+               }
+
+               internal static void EnterLock (ref TicketType ticket)
+               {
+                       int slot = Interlocked.Increment (ref ticket.Users) - 1;
+
+                       SpinWait wait;
+                       while (slot != ticket.Value)
+                               wait.SpinOnce ();
+               }
+
+               internal static void ReleaseLock (ref TicketType ticket, bool flush)
+               {
+                       if (flush)
+                               Interlocked.Increment (ref ticket.Value);
+                       else
+                               ticket.Value++;
+               }
+
+               internal static bool TryEnterLock (ref TicketType ticket)
+               {
+                       long u = ticket.Users;
+                       long totalValue = (u << 32) | u;
+                       long newTotalValue
+                               = BitConverter.IsLittleEndian ? (u << 32) | (u + 1) : ((u + 1) << 32) | u;
+
+                       return Interlocked.CompareExchange (ref ticket.TotalValue, newTotalValue, totalValue) == totalValue;
+               }
+
+               internal static bool IsLockHeld (ref TicketType ticket)
+               {
+                       // No need for barrier here
+                       long totalValue = ticket.TotalValue;
+                       return (totalValue >> 32) != (totalValue & 0xFFFFFFFF);
+               }
+       }
+
        public struct SpinLock
        {
-               const int isFree = 0;
-               const int isOwned = 1;
-               int lockState;
-               readonly SpinWait sw;
+               SpinLockHelpers.TicketType tickets;
+
                int threadWhoTookLock;
                readonly bool isThreadOwnerTrackingEnabled;
-               
+
                public bool IsThreadOwnerTrackingEnabled {
                        get {
                                return isThreadOwnerTrackingEnabled;
                        }
                }
-               
+
                public bool IsHeld {
                        get {
-                               return lockState == isOwned;
+                               return SpinLockHelpers.IsLockHeld (ref tickets);
                        }
                }
-               
+
                public bool IsHeldByCurrentThread {
                        get {
                                if (isThreadOwnerTrackingEnabled)
-                                       return lockState == isOwned && Thread.CurrentThread.ManagedThreadId == threadWhoTookLock;
+                                       return IsHeld && Thread.CurrentThread.ManagedThreadId == threadWhoTookLock;
                                else
-                                       return lockState == isOwned;
+                                       return IsHeld;
                        }
                }
 
@@ -62,75 +111,36 @@ namespace System.Threading
                {
                        this.isThreadOwnerTrackingEnabled = trackId;
                        this.threadWhoTookLock = 0;
-                       this.lockState = isFree;
-                       this.sw = new SpinWait();
-               }
-               
-               void CheckAndSetThreadId ()
-               {
-                       if (threadWhoTookLock == Thread.CurrentThread.ManagedThreadId)
-                               throw new LockRecursionException("The current thread has already acquired this lock.");
-                       threadWhoTookLock = Thread.CurrentThread.ManagedThreadId;
+                       this.tickets = new SpinLockHelpers.TicketType ();
                }
-               
+
+               [MonoTODO("This method is not rigorously correct. Need CER treatment")]
                public void Enter (ref bool lockTaken)
                {
                        if (lockTaken)
                                throw new ArgumentException ("lockTaken", "lockTaken must be initialized to false");
                        if (isThreadOwnerTrackingEnabled && IsHeldByCurrentThread)
                                throw new LockRecursionException ();
+
+                       SpinLockHelpers.EnterLock (ref tickets);
+                       lockTaken = true;
                        
-                       try {
-                               Enter ();
-                               lockTaken = lockState == isOwned && Thread.CurrentThread.ManagedThreadId == threadWhoTookLock;
-                       } catch {
-                               lockTaken = false;
-                       }
-               }
-               
-               internal void Enter () 
-               {
-                       int result = Interlocked.Exchange (ref lockState, isOwned);
-                       
-                       while (result == isOwned) {
-                               sw.SpinOnce ();
-                               
-                               // Efficiently spin, until the resource looks like it might 
-                               // be free. NOTE: Just reading here (as compared to repeatedly 
-                               // calling Exchange) improves performance because writing 
-                               // forces all CPUs to update this value
-                               result = Thread.VolatileRead (ref lockState);
-                               
-                               if (result == isFree) {
-                                       result = Interlocked.Exchange (ref lockState, isOwned);
-                                       if (result == isFree)
-                                               break;
-                               }
-                       }
-                       
-                       CheckAndSetThreadId ();
-               }
-               
-               bool TryEnter ()
-               {
-                       // If resource available, set it to in-use and return
-                       if (Interlocked.Exchange (ref lockState, isOwned) == isFree) {
-                               CheckAndSetThreadId ();
-                               return true;
-                       }
-                       return false;
+                       threadWhoTookLock = Thread.CurrentThread.ManagedThreadId;
                }
-               
+
+               [MonoTODO("This method is not rigorously correct. Need CER treatment")]
                public void TryEnter (ref bool lockTaken)
                {
-                       TryEnter (-1, ref 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)
                {
                        if (milliSeconds < -1)
@@ -139,42 +149,37 @@ namespace System.Threading
                                throw new ArgumentException ("lockTaken", "lockTaken must be initialized to false");
                        if (isThreadOwnerTrackingEnabled && IsHeldByCurrentThread)
                                throw new LockRecursionException ();
-                       
+
                        Watch sw = Watch.StartNew ();
-                       
-                       while (milliSeconds == -1 || sw.ElapsedMilliseconds < milliSeconds) {
-                               lockTaken = TryEnter ();
-                       }
-                       
-                       sw.Stop ();
+
+                       do {
+                               if (lockTaken = SpinLockHelpers.TryEnterLock (ref tickets)) {
+                                       threadWhoTookLock = Thread.CurrentThread.ManagedThreadId;
+                                       break;
+                               }
+                       } while (milliSeconds == -1 || (milliSeconds > 0 && sw.ElapsedMilliseconds < milliSeconds));
                }
 
-               public void Exit () 
-               { 
+               public void Exit ()
+               {
                        Exit (false);
                }
 
-               public void Exit (bool flushReleaseWrites) 
+               public void Exit (bool flushReleaseWrites)
                {
                        if (isThreadOwnerTrackingEnabled && !IsHeldByCurrentThread)
                                throw new SynchronizationLockException ("Current thread is not the owner of this lock");
-                               
+
                        threadWhoTookLock = int.MinValue;
-                       
-                       // Mark the resource as available
-                       if (!flushReleaseWrites) {
-                               lockState = isFree;
-                       } else {
-                               Interlocked.Exchange (ref lockState, isFree);
-                       }
+                       SpinLockHelpers.ReleaseLock (ref tickets, flushReleaseWrites);
                }
        }
-       
+
        // Wraps a SpinLock in a reference when we need to pass
        // around the lock
        internal class SpinLockWrapper
        {
-               public readonly SpinLock Lock = new SpinLock (false);
+               public SpinLock Lock = new SpinLock (false);
        }
 }
 #endif
index fd6d2d120cde709e5bc3853287b8f4e6165eeff3..988631ffa60fbfddc7fffc4bf14eee24c5f99a92 100644 (file)
@@ -1,3 +1,7 @@
+2010-03-08  Jérémie Laval  <jeremie.laval@gmail.com>
+
+       * SpinLockTests.cs: Added unit tests for SpinLock
+
 2010-02-08  Zoltan Varga  <vargaz@gmail.com>
 
        * WaitHandleTest.cs: Add a test for #576039.
diff --git a/mcs/class/corlib/Test/System.Threading/SpinLockTests.cs b/mcs/class/corlib/Test/System.Threading/SpinLockTests.cs
new file mode 100644 (file)
index 0000000..48149a3
--- /dev/null
@@ -0,0 +1,120 @@
+#if NET_4_0
+// 
+// SpinLockTests.cs
+//  
+// Author:
+//       Jérémie "Garuma" Laval <jeremie.laval@gmail.com>
+// 
+// Copyright (c) 2010 Jérémie "Garuma" Laval
+// 
+// Permission is hereby granted, free of charge, to any person obtaining a copy
+// of this software and associated documentation files (the "Software"), to deal
+// in the Software without restriction, including without limitation the rights
+// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+// copies of the Software, and to permit persons to whom the Software is
+// furnished to do so, subject to the following conditions:
+// 
+// The above copyright notice and this permission notice shall be included in
+// all copies or substantial portions of the Software.
+// 
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+// THE SOFTWARE.
+
+using System;
+using System.Threading;
+
+using NUnit.Framework;
+
+namespace MonoTests.System.Threading
+{
+       [TestFixture]
+       public class SpinLockTests
+       {
+               SpinLock sl;
+               
+               [SetUp]
+               public void Setup ()
+               {
+                       sl = new SpinLock (false);
+               }
+               
+               [Test, ExpectedException (typeof (LockRecursionException))]
+               public void RecursionExceptionTest ()
+               {
+                       sl = new SpinLock (true);
+                       bool taken = false, taken2 = false;
+                       
+                       sl.Enter (ref taken);
+                       Assert.IsTrue (taken, "#1");
+                       sl.Enter (ref taken2);
+               }
+               
+               [Test]
+               public void SimpleEnterExitSchemeTest ()
+               {
+                       bool taken = false;
+                       
+                       for (int i = 0; i < 50000; i++) {
+                               sl.Enter (ref taken);
+                               Assert.IsTrue (taken, "#" + i.ToString ());
+                               sl.Exit ();
+                               taken = false;
+                       }
+               }
+               
+               [Test]
+               public void SemanticCorrectnessTest ()
+               {
+                       bool taken = false;
+                       bool taken2 = false;
+                       
+                       sl.Enter (ref taken);
+                       Assert.IsTrue (taken, "#1");
+                       sl.TryEnter (ref taken2);
+                       Assert.IsFalse (taken2, "#2");
+                       sl.Exit ();
+                       
+                       sl.TryEnter (ref taken2);
+                       Assert.IsTrue (taken2, "#3");
+               }
+               
+               [Test, ExpectedException (typeof (ArgumentException))]
+               public void FirstTakenParameterTest ()
+               {
+                       bool taken = true;
+                       
+                       sl.Enter (ref taken);
+               }
+               
+               [Test, ExpectedException (typeof (ArgumentException))]
+               public void SecondTakenParameterTest ()
+               {
+                       bool taken = true;
+                       
+                       sl.TryEnter (ref taken);
+               }
+               
+               [Test]
+               public void IsHeldByCurrentThreadTest ()
+               {
+                       bool lockTaken = false;
+                       
+                       sl.Enter (ref lockTaken);
+                       Assert.IsTrue (lockTaken, "#1");
+                       Assert.IsTrue (sl.IsHeldByCurrentThread, "#2");
+                       
+                       lockTaken = false;
+                       sl = new SpinLock (true);
+                       
+                       sl.Enter (ref lockTaken);
+                       Assert.IsTrue (lockTaken, "#3");
+                       Assert.IsTrue (sl.IsHeldByCurrentThread, "#4");
+               }
+       }
+}
+#endif
index 4c670c35a01c6640d9617a2d301245911d19f9f7..9bbfb60b7d64a093fc47595905fb0152f295499f 100644 (file)
@@ -442,3 +442,5 @@ System.Threading/SemaphoreSlimTests.cs
 System.Threading/CountdownEventTests.cs
 System/AggregateExceptionTests.cs
 System.Threading/ThreadLazyTests.cs
+System.Threading/SpinLockTests.cs
+