SpinLock.cs
Go to the documentation of this file.
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
27 
28 using System;
30 using System.Runtime.ConstrainedExecution;
31 using System.Runtime.InteropServices;
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 
59 
60  static readonly Watch sw = Watch.StartNew ();
61 
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 
94  public SpinLock (bool enableThreadOwnerTracking)
95  {
96  this.isThreadOwnerTrackingEnabled = enableThreadOwnerTracking;
97  this.threadWhoTookLock = 0;
98  this.ticket = new TicketType ();
99  // this.stallTickets = null;
100 
101  // Spicy Pixel: Interlocked.CompareExchange with reference types
102  // tries to JIT on old Mono AOT. The only safe place then to
103  // initialize stallTickets is in the constructor. It doesn't
104  // make sense to use a lock because the lock object would have
105  // to be initialized anyway (and that's the point of this class).
106  //
107  // The solution chosen is to always initialize stallTickets here.
108  // This means:
109  // 1. Slight performance hit because now we do a heap alloc when
110  // it is not always necessary.
111  // 2. AOT has to use this constructor. The default one (which we
112  // can't define on a struct) does not initialize the collection
113  // and we can't do it in Enter without a working CompareExchange.
114  this.stallTickets = new ConcurrentOrderedList<int> ();
115  }
116 
117  [MonoTODO ("Not safe against async exceptions")]
118  public void Enter (ref bool lockTaken)
119  {
120  if (lockTaken)
121  throw new ArgumentException ("lockTaken", "lockTaken must be initialized to false");
122  if (isThreadOwnerTrackingEnabled && IsHeldByCurrentThread)
123  throw new LockRecursionException ();
124 
125  int slot = -1;
126 
127  RuntimeHelpers.PrepareConstrainedRegions ();
128  try {
129  slot = Interlocked.Increment (ref ticket.Users) - 1;
130 
131  SpinWait wait = new SpinWait ();
132  while (slot != ticket.Value) {
133  wait.SpinOnce ();
134 
135  while (stallTickets != null && stallTickets.TryRemove (ticket.Value))
136  ++ticket.Value;
137  }
138  } finally {
139  if (slot == ticket.Value) {
140  lockTaken = true;
141  threadWhoTookLock = Thread.CurrentThread.ManagedThreadId;
142  } else if (slot != -1) {
143  // We have been interrupted, initialize stallTickets
144  if (stallTickets == null)
145  Interlocked.CompareExchange (ref stallTickets, new ConcurrentOrderedList<int> (), null);
146  stallTickets.TryAdd (slot);
147  }
148  }
149  }
150 
151  public void TryEnter (ref bool lockTaken)
152  {
153  TryEnter (0, ref lockTaken);
154  }
155 
156  public void TryEnter (TimeSpan timeout, ref bool lockTaken)
157  {
158  TryEnter ((int)timeout.TotalMilliseconds, ref lockTaken);
159  }
160 
161  public void TryEnter (int millisecondsTimeout, ref bool lockTaken)
162  {
163  if (millisecondsTimeout < -1)
164  throw new ArgumentOutOfRangeException ("milliSeconds", "millisecondsTimeout is a negative number other than -1");
165  if (lockTaken)
166  throw new ArgumentException ("lockTaken", "lockTaken must be initialized to false");
167  if (isThreadOwnerTrackingEnabled && IsHeldByCurrentThread)
168  throw new LockRecursionException ();
169 
170  long start = millisecondsTimeout == -1 ? 0 : sw.ElapsedMilliseconds;
171  bool stop = false;
172 
173  do {
174  while (stallTickets != null && stallTickets.TryRemove (ticket.Value))
175  ++ticket.Value;
176 
177  long u = ticket.Users;
178  long totalValue = (u << 32) | u;
179  long newTotalValue
180  = BitConverter.IsLittleEndian ? (u << 32) | (u + 1) : ((u + 1) << 32) | u;
181 
182  RuntimeHelpers.PrepareConstrainedRegions ();
183  try {}
184  finally {
185  lockTaken = Interlocked.CompareExchange (ref ticket.TotalValue, newTotalValue, totalValue) == totalValue;
186 
187  if (lockTaken) {
188  threadWhoTookLock = Thread.CurrentThread.ManagedThreadId;
189  stop = true;
190  }
191  }
192  } while (!stop && (millisecondsTimeout == -1 || (sw.ElapsedMilliseconds - start) < millisecondsTimeout));
193  }
194 
195  [ReliabilityContract (Consistency.WillNotCorruptState, Cer.Success)]
196  public void Exit ()
197  {
198  Exit (false);
199  }
200 
201  [ReliabilityContract (Consistency.WillNotCorruptState, Cer.Success)]
202  public void Exit (bool useMemoryBarrier)
203  {
204  RuntimeHelpers.PrepareConstrainedRegions ();
205  try {}
206  finally {
207  if (isThreadOwnerTrackingEnabled && !IsHeldByCurrentThread)
208  throw new SynchronizationLockException ("Current thread is not the owner of this lock");
209 
210  threadWhoTookLock = int.MinValue;
211  do {
212  if (useMemoryBarrier)
213  Interlocked.Increment (ref ticket.Value);
214  else
215  ticket.Value++;
216  } while (stallTickets != null && stallTickets.TryRemove (ticket.Value));
217  }
218  }
219  }
220 }
221 #endif
void TryEnter(TimeSpan timeout, ref bool lockTaken)
Definition: SpinLock.cs:156
readonly bool isThreadOwnerTrackingEnabled
Definition: SpinLock.cs:58
ConcurrentOrderedList< int > stallTickets
Definition: SpinLock.cs:62
SpinLock(bool enableThreadOwnerTracking)
Initializes a new instance of the System.Threading.SpinLock struct.
Definition: SpinLock.cs:94
void Enter(ref bool lockTaken)
Definition: SpinLock.cs:118
void Exit(bool useMemoryBarrier)
Definition: SpinLock.cs:202
void TryEnter(int millisecondsTimeout, ref bool lockTaken)
Definition: SpinLock.cs:161
void TryEnter(ref bool lockTaken)
Definition: SpinLock.cs:151