Merge pull request #1072 from esdrubal/bug19862
[mono.git] / mcs / class / corlib / System.Threading / ManualResetEventSlim.cs
index 472975ef77d9216b78bfac80c66b96aa52f31f49..a34370d460be051bbdaf54315d093cb8f573b037 100644 (file)
@@ -1,6 +1,10 @@
-// ManuelResetEventSlim.cs
+// ManualResetEventSlim.cs
+//
+// Authors:
+//    Marek Safar  <marek.safar@gmail.com>
 //
 // 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 || BOOTSTRAP_NET_4_0
-
-using System;
+#if NET_4_0
 
 namespace System.Threading
 {
+       [System.Diagnostics.DebuggerDisplayAttribute ("Set = {IsSet}")]
        public class ManualResetEventSlim : IDisposable
        {
-               const int isSet    = 1;
-               const int isNotSet = 0;
-               const int defaultSpinCount = 10;
-
-               int state;
                readonly int spinCount;
 
                ManualResetEvent handle;
+               internal AtomicBooleanValue disposed;
+               int used;
+               int state;
 
-               readonly static Watch sw = Watch.StartNew ();
-
-               public ManualResetEventSlim () : this (false, defaultSpinCount)
+               public ManualResetEventSlim ()
+                       : this (false, 10)
                {
                }
 
-               public ManualResetEventSlim (bool initState) : this (initState, defaultSpinCount)
+               public ManualResetEventSlim (bool initialState)
+                       : this (initialState, 10)
                {
                }
 
-               public ManualResetEventSlim (bool initState, int spinCount)
+               public ManualResetEventSlim (bool initialState, int spinCount)
                {
-                       if (spinCount < 0)
-                               throw new ArgumentOutOfRangeException ("spinCount is less than 0", "spinCount");
+                       if (spinCount < 0 || spinCount > 2047)
+                               throw new ArgumentOutOfRangeException ("spinCount");
 
-                       this.state = initState ? isSet : isNotSet;
+                       this.state = initialState ? 1 : 0;
                        this.spinCount = spinCount;
                }
 
                public bool IsSet {
                        get {
-                               return state == isSet;
+                               return (state & 1) == 1;
                        }
                }
 
@@ -72,18 +73,64 @@ namespace System.Threading
 
                public void Reset ()
                {
-                       Interlocked.Exchange (ref state, isNotSet);
-                       //state = isNotSet;
+                       ThrowIfDisposed ();
+
+                       var stamp = UpdateStateWithOp (false);
                        if (handle != null)
-                               handle.Reset ();
+                               CommitChangeToHandle (stamp);
                }
 
                public void Set ()
                {
-                       Interlocked.Exchange (ref state, isSet);
-                       //state = isSet;
+                       var stamp = UpdateStateWithOp (true);
                        if (handle != null)
-                               handle.Set ();
+                               CommitChangeToHandle (stamp);
+               }
+
+               long UpdateStateWithOp (bool set)
+               {
+                       int oldValue, newValue;
+                       do {
+                               oldValue = state;
+                               newValue = (int)(((oldValue >> 1) + 1) << 1) | (set ? 1 : 0);
+                       } while (Interlocked.CompareExchange (ref state, newValue, oldValue) != oldValue);
+                       return newValue;
+               }
+
+               void CommitChangeToHandle (long stamp)
+               {
+                       Interlocked.Increment (ref used);
+                       var tmpHandle = handle;
+                       if (tmpHandle != null) {
+                               // First in all case we carry the operation we were called for
+                               if ((stamp & 1) == 1)
+                                       tmpHandle.Set ();
+                               else
+                                       tmpHandle.Reset ();
+
+                               /* Then what may happen is that the two suboperations (state change and handle change)
+                                * overlapped with others. In our case it doesn't matter if the two suboperations aren't
+                                * executed together at the same time, the only thing we have to make sure of is that both
+                                * state and handle are synchronized on the last visible state change.
+                                *
+                                * For instance if S is state change and H is handle change, for 3 concurrent operations
+                                * we may have the following serialized timeline: S1 S2 H2 S3 H3 H1
+                                * Which is perfectly fine (all S were converted to H at some stage) but in that case
+                                * we have a mismatch between S and H at the end because the last operations done were
+                                * S3/H1. We thus need to repeat H3 to get to the desired final state.
+                                */
+                               int currentState;
+                               do {
+                                       currentState = state;
+                                       if (currentState != stamp && (stamp & 1) != (currentState & 1)) {
+                                               if ((currentState & 1) == 1)
+                                                       tmpHandle.Set ();
+                                               else
+                                                       tmpHandle.Reset ();
+                                       }
+                               } while (currentState != state);
+                       }
+                       Interlocked.Decrement (ref used);
                }
 
                public void Wait ()
@@ -96,72 +143,129 @@ namespace System.Threading
                        return Wait (millisecondsTimeout, CancellationToken.None);
                }
 
-               public bool Wait (TimeSpan ts)
+               public bool Wait (TimeSpan timeout)
                {
-                       return Wait ((int)ts.TotalMilliseconds, CancellationToken.None);
+                       return Wait (CheckTimeout (timeout), CancellationToken.None);
                }
 
-               public void Wait (CancellationToken token)
+               public void Wait (CancellationToken cancellationToken)
                {
-                       Wait (-1, token);
+                       Wait (Timeout.Infinite, cancellationToken);
                }
 
-               public bool Wait (int ms, CancellationToken token)
+               public bool Wait (int millisecondsTimeout, CancellationToken cancellationToken)
                {
-                       if (ms < -1)
-                               throw new ArgumentOutOfRangeException ("millisecondsTimeout",
-                                                                      "millisecondsTimeout is a negative number other than -1");
+                       if (millisecondsTimeout < -1)
+                               throw new ArgumentOutOfRangeException ("millisecondsTimeout");
 
-                       long start = ms == -1 ? 0 : sw.ElapsedMilliseconds;
-                       SpinWait wait = new SpinWait ();
+                       ThrowIfDisposed ();
 
-                       while (state == isNotSet) {
-                               token.ThrowIfCancellationRequested ();
+                       if (!IsSet) {
+                               SpinWait wait = new SpinWait ();
 
-                               if (ms > -1 && (sw.ElapsedMilliseconds - start) > ms)
-                                       return false;
+                               while (!IsSet) {
+                                       if (wait.Count < spinCount) {
+                                               wait.SpinOnce ();
+                                               continue;
+                                       }
+
+                                       break;
+                               }
 
-                               if (wait.Count < spinCount) {
-                                       wait.SpinOnce ();
+                               cancellationToken.ThrowIfCancellationRequested ();
+
+                               if (IsSet)
+                                       return true;
+
+                               WaitHandle handle = WaitHandle;
+
+                               if (cancellationToken.CanBeCanceled) {
+                                       var result = WaitHandle.WaitAny (new[] { handle, cancellationToken.WaitHandle }, millisecondsTimeout, false);
+                                       if (result == 1)
+                                               throw new OperationCanceledException (cancellationToken);
+                                       if (result == WaitHandle.WaitTimeout)
+                                               return false;
                                } else {
-                                       int waitTime = ms == -1 ? -1 : Math.Max (ms - (int)(sw.ElapsedMilliseconds - start) , 1);
-                                       WaitHandle handle = WaitHandle;
-                                       if (state == isSet)
-                                               return true;
-                                       if (WaitHandle.WaitAny (new[] { handle, token.WaitHandle }, waitTime, false) == 0)
-                                               return true;
+                                       if (!handle.WaitOne (millisecondsTimeout, false))
+                                               return false;
                                }
                        }
 
                        return true;
                }
 
-               public bool Wait (TimeSpan ts, CancellationToken token)
+               public bool Wait (TimeSpan timeout, CancellationToken cancellationToken)
                {
-                       return Wait ((int)ts.TotalMilliseconds, token);
+                       return Wait (CheckTimeout (timeout), cancellationToken);
                }
 
                public WaitHandle WaitHandle {
                        get {
+                               ThrowIfDisposed ();
+
                                if (handle != null)
                                        return handle;
-                               return LazyInitializer.EnsureInitialized (ref handle,
-                                                                         () => new ManualResetEvent (state == isSet ? true : false));
+
+                               var isSet = IsSet;
+                               var mre = new ManualResetEvent (IsSet);
+                               if (Interlocked.CompareExchange (ref handle, mre, null) == null) {
+                                       //
+                                       // Ensure the Set has not ran meantime
+                                       //
+                                       if (isSet != IsSet) {
+                                               if (IsSet) {
+                                                       mre.Set ();
+                                               } else {
+                                                       mre.Reset ();
+                                               }
+                                       }
+                               } else {
+                                       //
+                                       // Release the event when other thread was faster
+                                       //
+                                       mre.Dispose ();
+                               }
+
+                               return handle;
                        }
                }
 
-               #region IDisposable implementation
                public void Dispose ()
                {
-                       Dispose(true);
+                       Dispose (true);
                }
 
-               protected virtual void Dispose(bool managedRes)
+               protected virtual void Dispose (bool disposing)
                {
+                       if (!disposed.TryRelaxedSet ())
+                               return;
 
+                       if (handle != null) {
+                               var tmpHandle = Interlocked.Exchange (ref handle, null);
+                               if (used > 0) {
+                                       // A tiny wait (just a few cycles normally) before releasing
+                                       SpinWait wait = new SpinWait ();
+                                       while (used > 0)
+                                               wait.SpinOnce ();
+                               }
+                               tmpHandle.Dispose ();
+                       }
+               }
+
+               void ThrowIfDisposed ()
+               {
+                       if (disposed.Value)
+                               throw new ObjectDisposedException ("ManualResetEventSlim");
                }
-               #endregion
 
+               static int CheckTimeout (TimeSpan timeout)
+               {
+                       try {
+                               return checked ((int)timeout.TotalMilliseconds);
+                       } catch (System.OverflowException) {
+                               throw new ArgumentOutOfRangeException ("timeout");
+                       }
+               }
        }
 }
 #endif