// 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)]
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;
int threadWhoTookLock;
readonly bool isThreadOwnerTrackingEnabled;
- static Watch sw = Watch.StartNew ();
+ static readonly Watch sw = Watch.StartNew ();
+
+ ConcurrentOrderedList<int> stallTickets;
public bool IsThreadOwnerTrackingEnabled {
get {
}
}
- 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")]
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)
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
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 {}
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