Add [Category ("NotWorking")] to failing test.
[mono.git] / mcs / class / corlib / System.Threading / CancellationTokenSource.cs
1 // 
2 // CancellationTokenSource.cs
3 //  
4 // Authors:
5 //       Jérémie "Garuma" Laval <jeremie.laval@gmail.com>
6 //       Marek Safar (marek.safar@gmail.com)
7 // 
8 // Copyright (c) 2009 Jérémie "Garuma" Laval
9 // Copyright 2011 Xamarin, Inc (http://www.xamarin.com)
10 // 
11 // Permission is hereby granted, free of charge, to any person obtaining a copy
12 // of this software and associated documentation files (the "Software"), to deal
13 // in the Software without restriction, including without limitation the rights
14 // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
15 // copies of the Software, and to permit persons to whom the Software is
16 // furnished to do so, subject to the following conditions:
17 // 
18 // The above copyright notice and this permission notice shall be included in
19 // all copies or substantial portions of the Software.
20 // 
21 // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
22 // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
23 // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
24 // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
25 // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
26 // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
27 // THE SOFTWARE.
28
29 #if NET_4_0
30 using System.Collections.Generic;
31 using System.Collections.Concurrent;
32
33 namespace System.Threading
34 {
35 #if !NET_4_5
36         sealed
37 #endif
38         public class CancellationTokenSource : IDisposable
39         {
40                 bool canceled;
41                 bool disposed;
42                 
43                 int currId = int.MinValue;
44                 ConcurrentDictionary<CancellationTokenRegistration, Action> callbacks;
45                 CancellationTokenRegistration[] linkedTokens;
46
47                 ManualResetEvent handle;
48                 
49                 internal static readonly CancellationTokenSource NoneSource = new CancellationTokenSource ();
50                 internal static readonly CancellationTokenSource CanceledSource = new CancellationTokenSource ();
51                 
52 #if NET_4_5
53                 static readonly TimerCallback timer_callback;
54                 Timer timer;
55 #endif
56
57                 static CancellationTokenSource ()
58                 {
59                         CanceledSource.canceled = true;
60
61 #if NET_4_5
62                         timer_callback = token => {
63                                 var cts = (CancellationTokenSource) token;
64                                 cts.Cancel ();
65                         };
66 #endif
67                 }
68
69                 public CancellationTokenSource ()
70                 {
71                         callbacks = new ConcurrentDictionary<CancellationTokenRegistration, Action> ();
72                         handle = new ManualResetEvent (false);
73                 }
74
75 #if NET_4_5
76                 public CancellationTokenSource (int millisecondsDelay)
77                         : this ()
78                 {
79                         if (millisecondsDelay < -1)
80                                 throw new ArgumentOutOfRangeException ("millisecondsDelay");
81
82                         if (millisecondsDelay != Timeout.Infinite)
83                                 timer = new Timer (timer_callback, this, millisecondsDelay, Timeout.Infinite);
84                 }
85
86                 public CancellationTokenSource (TimeSpan delay)
87                         : this (CheckTimeout (delay))
88                 {
89                 }
90 #endif
91
92                 public CancellationToken Token {
93                         get {
94                                 CheckDisposed ();
95                                 return new CancellationToken (this);
96                         }
97                 }
98                 
99                 public bool IsCancellationRequested {
100                         get {
101                                 return canceled;
102                         }
103                 }
104                 
105                 internal WaitHandle WaitHandle {
106                         get {
107                                 CheckDisposed ();
108                                 return handle;
109                         }
110                 }
111                 
112                 public void Cancel ()
113                 {
114                         Cancel (false);
115                 }
116                 
117                 // If parameter is true we throw exception as soon as they appear otherwise we aggregate them
118                 public void Cancel (bool throwOnFirstException)
119                 {
120                         CheckDisposed ();
121
122                         if (canceled)
123                                 return;
124
125                         Thread.MemoryBarrier ();
126                         canceled = true;
127                         
128                         handle.Set ();
129                         if (linkedTokens != null)
130                                 UnregisterLinkedTokens ();
131                         
132                         List<Exception> exceptions = null;
133                         
134                         try {
135                                 Action cb;
136                                 for (int id = int.MinValue + 1; id <= currId; id++) {
137                                         if (!callbacks.TryRemove (new CancellationTokenRegistration (id, this), out cb))
138                                                 continue;
139                                         if (cb == null)
140                                                 continue;
141
142                                         if (throwOnFirstException) {
143                                                 cb ();
144                                         } else {
145                                                 try {
146                                                         cb ();
147                                                 } catch (Exception e) {
148                                                         if (exceptions == null)
149                                                                 exceptions = new List<Exception> ();
150
151                                                         exceptions.Add (e);
152                                                 }
153                                         }
154                                 }
155                         } finally {
156                                 callbacks.Clear ();
157                         }
158
159                         if (exceptions != null)
160                                 throw new AggregateException (exceptions);
161                 }
162
163                 /* This is the callback registered on linked tokens
164                  * so that they don't throw an ODE if the callback
165                  * is called concurrently with a Dispose
166                  */
167                 void SafeLinkedCancel ()
168                 {
169                         try {
170                                 Cancel ();
171                         } catch (ObjectDisposedException) {}
172                 }
173
174 #if NET_4_5
175                 public void CancelAfter (TimeSpan delay)
176                 {
177                         CancelAfter (CheckTimeout (delay));
178                 }
179
180                 public void CancelAfter (int millisecondsDelay)
181                 {
182                         if (millisecondsDelay < -1)
183                                 throw new ArgumentOutOfRangeException ("millisecondsDelay");
184
185                         CheckDisposed ();
186
187                         if (canceled || millisecondsDelay == Timeout.Infinite)
188                                 return;
189
190                         if (timer == null) {
191                                 // Have to be carefull not to create secondary background timer
192                                 var t = new Timer (timer_callback, this, Timeout.Infinite, Timeout.Infinite);
193                                 if (Interlocked.CompareExchange (ref timer, t, null) != null)
194                                         t.Dispose ();
195                         }
196
197                         timer.Change (millisecondsDelay, Timeout.Infinite);
198                 }
199 #endif
200
201                 public static CancellationTokenSource CreateLinkedTokenSource (CancellationToken token1, CancellationToken token2)
202                 {
203                         return CreateLinkedTokenSource (new [] { token1, token2 });
204                 }
205                 
206                 public static CancellationTokenSource CreateLinkedTokenSource (params CancellationToken[] tokens)
207                 {
208                         if (tokens == null)
209                                 throw new ArgumentNullException ("tokens");
210
211                         if (tokens.Length == 0)
212                                 throw new ArgumentException ("Empty tokens array");
213
214                         CancellationTokenSource src = new CancellationTokenSource ();
215                         Action action = src.SafeLinkedCancel;
216                         var registrations = new List<CancellationTokenRegistration> (tokens.Length);
217
218                         foreach (CancellationToken token in tokens) {
219                                 if (token.CanBeCanceled)
220                                         registrations.Add (token.Register (action));
221                         }
222                         src.linkedTokens = registrations.ToArray ();
223                         
224                         return src;
225                 }
226
227                 static int CheckTimeout (TimeSpan delay)
228                 {
229                         try {
230                                 return checked ((int) delay.TotalMilliseconds);
231                         } catch (OverflowException) {
232                                 throw new ArgumentOutOfRangeException ("delay");
233                         }
234                 }
235
236                 void CheckDisposed ()
237                 {
238                         if (disposed)
239                                 throw new ObjectDisposedException (GetType ().Name);
240                 }
241
242                 public void Dispose ()
243                 {
244                         Dispose (true);
245                 }
246
247 #if NET_4_5
248                 protected virtual
249 #endif
250                 void Dispose (bool disposing)
251                 {
252                         if (disposing && !disposed) {
253                                 Thread.MemoryBarrier ();
254                                 disposed = true;
255
256                                 if (!canceled) {
257                                         Thread.MemoryBarrier ();
258                                         UnregisterLinkedTokens ();
259                                         callbacks = null;
260                                 }
261 #if NET_4_5
262                                 if (timer != null)
263                                         timer.Dispose ();
264 #endif
265                                 handle.Dispose ();
266                         }
267                 }
268
269                 void UnregisterLinkedTokens ()
270                 {
271                         var registrations = Interlocked.Exchange (ref linkedTokens, null);
272                         if (registrations == null)
273                                 return;
274                         foreach (var linked in registrations)
275                                 linked.Dispose ();
276                 }
277                 
278                 internal CancellationTokenRegistration Register (Action callback, bool useSynchronizationContext)
279                 {
280                         CheckDisposed ();
281
282                         var tokenReg = new CancellationTokenRegistration (Interlocked.Increment (ref currId), this);
283
284                         /* If the source is already canceled we execute the callback immediately
285                          * if not, we try to add it to the queue and if it is currently being processed
286                          * we try to execute it back ourselves to be sure the callback is ran
287                          */
288                         if (canceled)
289                                 callback ();
290                         else {
291                                 callbacks.TryAdd (tokenReg, callback);
292                                 if (canceled && callbacks.TryRemove (tokenReg, out callback))
293                                         callback ();
294                         }
295                         
296                         return tokenReg;
297                 }
298
299                 internal void RemoveCallback (CancellationTokenRegistration reg)
300                 {
301                         // Ignore call if the source has been disposed
302                         if (disposed)
303                                 return;
304                         Action dummy;
305                         var cbs = callbacks;
306                         if (cbs != null)
307                                 cbs.TryRemove (reg, out dummy);
308                 }
309         }
310 }
311 #endif