Merge pull request #819 from brendanzagaeski/patch-1
[mono.git] / mcs / class / corlib / System.Threading / CancellationTokenSource.cs
index 05ca53a182f1f85e389adf6278282b35e0ff1e0c..c5557f0446e69b7808e73a295b08c7c244d4e469 100644 (file)
@@ -1,10 +1,12 @@
 // 
 // CancellationTokenSource.cs
 //  
-// Author:
+// Authors:
 //       Jérémie "Garuma" Laval <jeremie.laval@gmail.com>
+//       Marek Safar (marek.safar@gmail.com)
 // 
 // Copyright (c) 2009 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
 // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
 // THE SOFTWARE.
 
-#if NET_4_0 || BOOTSTRAP_NET_4_0
-using System;
+#if NET_4_0
 using System.Collections.Generic;
+using System.Collections.Concurrent;
 
 namespace System.Threading
 {
-       
-       public sealed class CancellationTokenSource : IDisposable
+#if !NET_4_5
+       sealed
+#endif
+       public class CancellationTokenSource : IDisposable
        {
                bool canceled;
-               bool processed;
+               bool disposed;
                
                int currId = int.MinValue;
+               ConcurrentDictionary<CancellationTokenRegistration, Action> callbacks;
+               CancellationTokenRegistration[] linkedTokens;
+
+               ManualResetEvent handle;
                
-               Dictionary<CancellationTokenRegistration, Action> callbacks
-                       = new Dictionary<CancellationTokenRegistration, Action> ();
+               internal static readonly CancellationTokenSource NoneSource = new CancellationTokenSource ();
+               internal static readonly CancellationTokenSource CanceledSource = new CancellationTokenSource ();
                
-               ManualResetEvent handle = new ManualResetEvent (false);
+#if NET_4_5
+               static readonly TimerCallback timer_callback;
+               Timer timer;
+#endif
+
+               static CancellationTokenSource ()
+               {
+                       CanceledSource.canceled = true;
+
+#if NET_4_5
+                       timer_callback = token => {
+                               var cts = (CancellationTokenSource) token;
+                               cts.CancelSafe ();
+                       };
+#endif
+               }
+
+               public CancellationTokenSource ()
+               {
+                       callbacks = new ConcurrentDictionary<CancellationTokenRegistration, Action> ();
+                       handle = new ManualResetEvent (false);
+               }
+
+#if NET_4_5
+               public CancellationTokenSource (int millisecondsDelay)
+                       : this ()
+               {
+                       if (millisecondsDelay < -1)
+                               throw new ArgumentOutOfRangeException ("millisecondsDelay");
+
+                       if (millisecondsDelay != Timeout.Infinite)
+                               timer = new Timer (timer_callback, this, millisecondsDelay, Timeout.Infinite);
+               }
+
+               public CancellationTokenSource (TimeSpan delay)
+                       : this (CheckTimeout (delay))
+               {
+               }
+#endif
+
+               public CancellationToken Token {
+                       get {
+                               CheckDisposed ();
+                               return new CancellationToken (this);
+                       }
+               }
                
-               object syncRoot = new object ();
+               public bool IsCancellationRequested {
+                       get {
+                               return canceled;
+                       }
+               }
                
-               internal static readonly CancellationTokenSource NoneSource = new CancellationTokenSource ();
+               internal WaitHandle WaitHandle {
+                       get {
+                               CheckDisposed ();
+                               return handle;
+                       }
+               }
                
                public void Cancel ()
                {
@@ -53,124 +115,210 @@ namespace System.Threading
                }
                
                // If parameter is true we throw exception as soon as they appear otherwise we aggregate them
-               public void Cancel (bool throwOnFirst)
+               public void Cancel (bool throwOnFirstException)
+               {
+                       CheckDisposed ();
+                       Cancellation (throwOnFirstException);
+               }
+
+               //
+               // Don't throw ObjectDisposedException if the callback
+               // is called concurrently with a Dispose
+               //
+               public void CancelSafe ()
+               {
+                       if (!disposed)
+                               Cancellation (true);
+               }
+
+               void Cancellation (bool throwOnFirstException)
                {
+                       if (canceled)
+                               return;
+
+                       Thread.MemoryBarrier ();
                        canceled = true;
-                       handle.Set ();
-                       
+
+                       Thread.MemoryBarrier ();
+
+                       // Dispose might be running at same time
+                       if (!disposed)
+                               handle.Set ();
+
+                       if (linkedTokens != null)
+                               UnregisterLinkedTokens ();
+
+                       var cbs = callbacks;
+                       if (cbs == null)
+                               return;
+
                        List<Exception> exceptions = null;
-                       if (!throwOnFirst)
-                               exceptions = new List<Exception> ();
-                       
-                       lock (callbacks) {
-                               foreach (KeyValuePair<CancellationTokenRegistration, Action> item in callbacks) {
-                                       if (throwOnFirst) {
-                                               item.Value ();
+
+                       try {
+                               Action cb;
+                               for (int id = currId; id != int.MinValue; id--) {
+                                       if (!cbs.TryRemove (new CancellationTokenRegistration (id, this), out cb))
+                                               continue;
+                                       if (cb == null)
+                                               continue;
+
+                                       if (throwOnFirstException) {
+                                               cb ();
                                        } else {
                                                try {
-                                                       item.Value ();
+                                                       cb ();
                                                } catch (Exception e) {
+                                                       if (exceptions == null)
+                                                               exceptions = new List<Exception> ();
+
                                                        exceptions.Add (e);
                                                }
                                        }
                                }
+                       } finally {
+                               cbs.Clear ();
                        }
-                       
-                       Thread.MemoryBarrier ();
-                       processed = true;
-                       
-                       if (exceptions != null && exceptions.Count > 0)
+
+                       if (exceptions != null)
                                throw new AggregateException (exceptions);
                }
-               
-               public void Dispose ()
+
+#if NET_4_5
+               public void CancelAfter (TimeSpan delay)
                {
-                       
+                       CancelAfter (CheckTimeout (delay));
                }
-               
+
+               public void CancelAfter (int millisecondsDelay)
+               {
+                       if (millisecondsDelay < -1)
+                               throw new ArgumentOutOfRangeException ("millisecondsDelay");
+
+                       CheckDisposed ();
+
+                       if (canceled || millisecondsDelay == Timeout.Infinite)
+                               return;
+
+                       if (timer == null) {
+                               // Have to be carefull not to create secondary background timer
+                               var t = new Timer (timer_callback, this, Timeout.Infinite, Timeout.Infinite);
+                               if (Interlocked.CompareExchange (ref timer, t, null) != null)
+                                       t.Dispose ();
+                       }
+
+                       timer.Change (millisecondsDelay, Timeout.Infinite);
+               }
+#endif
+
                public static CancellationTokenSource CreateLinkedTokenSource (CancellationToken token1, CancellationToken token2)
                {
-                       return CreateLinkedTokenSource (new CancellationToken[] { token1, token2 });
+                       return CreateLinkedTokenSource (new [] { token1, token2 });
                }
                
                public static CancellationTokenSource CreateLinkedTokenSource (params CancellationToken[] tokens)
                {
+                       if (tokens == null)
+                               throw new ArgumentNullException ("tokens");
+
+                       if (tokens.Length == 0)
+                               throw new ArgumentException ("Empty tokens array");
+
                        CancellationTokenSource src = new CancellationTokenSource ();
-                       Action action = src.Cancel;
-                       
-                       foreach (CancellationToken token in tokens)
-                               token.Register (action);
+                       Action action = src.CancelSafe;
+                       var registrations = new List<CancellationTokenRegistration> (tokens.Length);
+
+                       foreach (CancellationToken token in tokens) {
+                               if (token.CanBeCanceled)
+                                       registrations.Add (token.Register (action));
+                       }
+                       src.linkedTokens = registrations.ToArray ();
                        
                        return src;
                }
-               
-               public CancellationToken Token {
-                       get {
-                               return CreateToken ();
+
+               static int CheckTimeout (TimeSpan delay)
+               {
+                       try {
+                               return checked ((int) delay.TotalMilliseconds);
+                       } catch (OverflowException) {
+                               throw new ArgumentOutOfRangeException ("delay");
                        }
                }
-               
-               public bool IsCancellationRequested {
-                       get {
-                               return canceled;
-                       }
+
+               void CheckDisposed ()
+               {
+                       if (disposed)
+                               throw new ObjectDisposedException (GetType ().Name);
                }
-               
-               internal WaitHandle WaitHandle {
-                       get {
-                               return handle;
+
+               public void Dispose ()
+               {
+                       Dispose (true);
+               }
+
+#if NET_4_5
+               protected virtual
+#endif
+               void Dispose (bool disposing)
+               {
+                       if (disposing && !disposed) {
+                               disposed = true;
+                               Thread.MemoryBarrier ();
+
+                               if (!canceled) {
+                                       UnregisterLinkedTokens ();
+                                       callbacks = null;
+                               } else {
+                                       handle.WaitOne ();
+                               }
+#if NET_4_5
+                               if (timer != null)
+                                       timer.Dispose ();
+#endif
+
+                               handle.Dispose ();
                        }
                }
+
+               void UnregisterLinkedTokens ()
+               {
+                       var registrations = Interlocked.Exchange (ref linkedTokens, null);
+                       if (registrations == null)
+                               return;
+                       foreach (var linked in registrations)
+                               linked.Dispose ();
+               }
                
                internal CancellationTokenRegistration Register (Action callback, bool useSynchronizationContext)
                {
-                       CancellationTokenRegistration tokenReg = GetTokenReg ();
-                       if (canceled) {
+                       CheckDisposed ();
+
+                       var tokenReg = new CancellationTokenRegistration (Interlocked.Increment (ref currId), this);
+
+                       /* If the source is already canceled we execute the callback immediately
+                        * if not, we try to add it to the queue and if it is currently being processed
+                        * we try to execute it back ourselves to be sure the callback is ran
+                        */
+                       if (canceled)
                                callback ();
-                       } else {
-                               bool temp = false;
-                               lock (syncRoot) {
-                                       if (!(temp = canceled))
-                                               callbacks.Add (tokenReg, callback);
-                               }
-                               if (temp)
+                       else {
+                               callbacks.TryAdd (tokenReg, callback);
+                               if (canceled && callbacks.TryRemove (tokenReg, out callback))
                                        callback ();
                        }
                        
                        return tokenReg;
                }
-               
-               internal void RemoveCallback (CancellationTokenRegistration tokenReg)
-               {
-                       if (!canceled) {
-                               lock (syncRoot) {
-                                       if (!canceled) {
-                                               callbacks.Remove (tokenReg);
-                                               return;
-                                       }
-                               }
-                       }
-                       
-                       SpinWait sw = new SpinWait ();
-                       while (!processed)
-                               sw.SpinOnce ();
-                       
-               }
 
-               CancellationTokenRegistration GetTokenReg ()
+               internal void RemoveCallback (CancellationTokenRegistration reg)
                {
-                       CancellationTokenRegistration registration
-                               = new CancellationTokenRegistration (Interlocked.Increment (ref currId), this);
-                       
-                       return registration;
-               }
-               
-               CancellationToken CreateToken ()
-               {
-                       CancellationToken tk = new CancellationToken (canceled);
-                       tk.Source = this;
-                       
-                       return tk;
+                       // Ignore call if the source has been disposed
+                       if (disposed)
+                               return;
+                       Action dummy;
+                       var cbs = callbacks;
+                       if (cbs != null)
+                               cbs.TryRemove (reg, out dummy);
                }
        }
 }