Merge pull request #409 from Alkarex/patch-1
[mono.git] / mcs / class / corlib / System.Threading / SpinLock.cs
1 // SpinLock.cs
2 //
3 // Copyright (c) 2008 Jérémie "Garuma" Laval
4 // Copyright 2011 Xamarin Inc (http://www.xamarin.com).
5 //
6 // Permission is hereby granted, free of charge, to any person obtaining a copy
7 // of this software and associated documentation files (the "Software"), to deal
8 // in the Software without restriction, including without limitation the rights
9 // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
10 // copies of the Software, and to permit persons to whom the Software is
11 // furnished to do so, subject to the following conditions:
12 //
13 // The above copyright notice and this permission notice shall be included in
14 // all copies or substantial portions of the Software.
15 //
16 // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
17 // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
18 // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
19 // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
20 // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
21 // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
22 // THE SOFTWARE.
23 //
24 //
25
26 #if NET_4_0 || MOBILE
27
28 using System;
29 using System.Collections.Concurrent;
30 using System.Runtime.ConstrainedExecution;
31 using System.Runtime.InteropServices;
32 using System.Runtime.CompilerServices;
33
34 namespace System.Threading
35 {
36         [StructLayout(LayoutKind.Explicit)]
37         internal struct TicketType {
38                 [FieldOffset(0)]
39                 public long TotalValue;
40                 [FieldOffset(0)]
41                 public int Value;
42                 [FieldOffset(4)]
43                 public int Users;
44         }
45
46         /* Implement the ticket SpinLock algorithm described on http://locklessinc.com/articles/locks/
47          * This lock is usable on both endianness.
48          * All the try/finally patterns in this class and various extra gimmicks compared to the original
49          * algorithm are here to avoid problems caused by asynchronous exceptions.
50          */
51         [System.Diagnostics.DebuggerDisplay ("IsHeld = {IsHeld}")]
52         [System.Diagnostics.DebuggerTypeProxy ("System.Threading.SpinLock+SystemThreading_SpinLockDebugView")]
53         public struct SpinLock
54         {
55                 TicketType ticket;
56
57                 int threadWhoTookLock;
58                 readonly bool isThreadOwnerTrackingEnabled;
59
60                 static readonly Watch sw = Watch.StartNew ();
61
62                 ConcurrentOrderedList<int> stallTickets;
63
64                 public bool IsThreadOwnerTrackingEnabled {
65                         get {
66                                 return isThreadOwnerTrackingEnabled;
67                         }
68                 }
69
70                 public bool IsHeld {
71                         get {
72                                 // No need for barrier here
73                                 long totalValue = ticket.TotalValue;
74                                 return (totalValue >> 32) != (totalValue & 0xFFFFFFFF);
75                         }
76                 }
77
78                 public bool IsHeldByCurrentThread {
79                         get {
80                                 if (isThreadOwnerTrackingEnabled)
81                                         return IsHeld && Thread.CurrentThread.ManagedThreadId == threadWhoTookLock;
82                                 else
83                                         return IsHeld;
84                         }
85                 }
86
87                 public SpinLock (bool enableThreadOwnerTracking)
88                 {
89                         this.isThreadOwnerTrackingEnabled = enableThreadOwnerTracking;
90                         this.threadWhoTookLock = 0;
91                         this.ticket = new TicketType ();
92                         this.stallTickets = null;
93                 }
94
95                 [MonoTODO ("Not safe against async exceptions")]
96                 public void Enter (ref bool lockTaken)
97                 {
98                         if (lockTaken)
99                                 throw new ArgumentException ("lockTaken", "lockTaken must be initialized to false");
100                         if (isThreadOwnerTrackingEnabled && IsHeldByCurrentThread)
101                                 throw new LockRecursionException ();
102
103                         int slot = -1;
104
105                         RuntimeHelpers.PrepareConstrainedRegions ();
106                         try {
107                                 slot = Interlocked.Increment (ref ticket.Users) - 1;
108
109                                 SpinWait wait = new SpinWait ();
110                                 while (slot != ticket.Value) {
111                                         wait.SpinOnce ();
112
113                                         while (stallTickets != null && stallTickets.TryRemove (ticket.Value))
114                                                 ++ticket.Value;
115                                 }
116                         } finally {
117                                 if (slot == ticket.Value) {
118                                         lockTaken = true;
119                                         threadWhoTookLock = Thread.CurrentThread.ManagedThreadId;
120                                 } else if (slot != -1) {
121                                         // We have been interrupted, initialize stallTickets
122                                         if (stallTickets == null)
123                                                 Interlocked.CompareExchange (ref stallTickets, new ConcurrentOrderedList<int> (), null);
124                                         stallTickets.TryAdd (slot);
125                                 }
126                         }
127                 }
128
129                 public void TryEnter (ref bool lockTaken)
130                 {
131                         TryEnter (0, ref lockTaken);
132                 }
133
134                 public void TryEnter (TimeSpan timeout, ref bool lockTaken)
135                 {
136                         TryEnter ((int)timeout.TotalMilliseconds, ref lockTaken);
137                 }
138
139                 public void TryEnter (int millisecondsTimeout, ref bool lockTaken)
140                 {
141                         if (millisecondsTimeout < -1)
142                                 throw new ArgumentOutOfRangeException ("milliSeconds", "millisecondsTimeout is a negative number other than -1");
143                         if (lockTaken)
144                                 throw new ArgumentException ("lockTaken", "lockTaken must be initialized to false");
145                         if (isThreadOwnerTrackingEnabled && IsHeldByCurrentThread)
146                                 throw new LockRecursionException ();
147
148                         long start = millisecondsTimeout == -1 ? 0 : sw.ElapsedMilliseconds;
149                         bool stop = false;
150
151                         do {
152                                 while (stallTickets != null && stallTickets.TryRemove (ticket.Value))
153                                         ++ticket.Value;
154
155                                 long u = ticket.Users;
156                                 long totalValue = (u << 32) | u;
157                                 long newTotalValue
158                                         = BitConverter.IsLittleEndian ? (u << 32) | (u + 1) : ((u + 1) << 32) | u;
159                                 
160                                 RuntimeHelpers.PrepareConstrainedRegions ();
161                                 try {}
162                                 finally {
163                                         lockTaken = Interlocked.CompareExchange (ref ticket.TotalValue, newTotalValue, totalValue) == totalValue;
164                                 
165                                         if (lockTaken) {
166                                                 threadWhoTookLock = Thread.CurrentThread.ManagedThreadId;
167                                                 stop = true;
168                                         }
169                                 }
170                 } while (!stop && (millisecondsTimeout == -1 || (sw.ElapsedMilliseconds - start) < millisecondsTimeout));
171                 }
172
173                 [ReliabilityContract (Consistency.WillNotCorruptState, Cer.Success)]
174                 public void Exit ()
175                 {
176                         Exit (false);
177                 }
178
179                 [ReliabilityContract (Consistency.WillNotCorruptState, Cer.Success)]
180                 public void Exit (bool useMemoryBarrier)
181                 {
182                         RuntimeHelpers.PrepareConstrainedRegions ();
183                         try {}
184                         finally {
185                                 if (isThreadOwnerTrackingEnabled && !IsHeldByCurrentThread)
186                                         throw new SynchronizationLockException ("Current thread is not the owner of this lock");
187
188                                 threadWhoTookLock = int.MinValue;
189                                 do {
190                                         if (useMemoryBarrier)
191                                                 Interlocked.Increment (ref ticket.Value);
192                                         else
193                                                 ticket.Value++;
194                                 } while (stallTickets != null && stallTickets.TryRemove (ticket.Value));
195                         }
196                 }
197         }
198 }
199 #endif