Merge pull request #5714 from alexischr/update_bockbuild
[mono.git] / mcs / class / corlib / Test / System.Threading / CancellationTokenSourceTest.cs
1 //
2 // CancellationTokenSourceTest.cs
3 //
4 // Authors:
5 //       Marek Safar (marek.safar@gmail.com)
6 //       Jeremie Laval (jeremie.laval@gmail.com)
7 //
8 // Copyright 2011 Xamarin, Inc (http://www.xamarin.com)
9 //
10 // Permission is hereby granted, free of charge, to any person obtaining
11 // a copy of this software and associated documentation files (the
12 // "Software"), to deal in the Software without restriction, including
13 // without limitation the rights to use, copy, modify, merge, publish,
14 // distribute, sublicense, and/or sell copies of the Software, and to
15 // permit persons to whom the Software is furnished to do so, subject to
16 // the following conditions:
17 // 
18 // The above copyright notice and this permission notice shall be
19 // included in all copies or substantial portions of the Software.
20 // 
21 // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
22 // EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
23 // MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
24 // NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
25 // LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
26 // OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
27 // WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
28 //
29
30
31 using System;
32 using System.Threading;
33 using NUnit.Framework;
34 using System.Threading.Tasks;
35
36 namespace MonoTests.System.Threading
37 {
38         [TestFixture]
39         public class CancellationTokenSourceTest
40         {
41
42                 [Test]
43                 public void Ctor_Invalid ()
44                 {
45                         try {
46                                 new CancellationTokenSource (-4);
47                                 Assert.Fail ("#1");
48                         } catch (ArgumentException) {
49                         }
50                 }
51
52                 [Test]
53                 public void Ctor_Timeout ()
54                 {
55                         int called = 0;
56                         var cts = new CancellationTokenSource (TimeSpan.FromMilliseconds (20));
57                         var mre = new ManualResetEvent (false);
58                         cts.Token.Register (() => { called++; mre.Set (); });
59
60                         Assert.IsTrue (mre.WaitOne (1000), "Not called in 1000ms");
61                         Assert.AreEqual (1, called, "#1");
62                 }
63
64                 [Test]
65                 public void CancelAfter ()
66                 {
67                         int called = 0;
68                         var cts = new CancellationTokenSource ();
69                         var mre = new ManualResetEvent(false);
70                         cts.Token.Register (() => { called++; mre.Set (); });
71                         cts.CancelAfter (20);
72
73                         Assert.IsTrue(mre.WaitOne (1000), "Should be cancelled in ~20ms");
74                         Assert.AreEqual (1, called, "#1");
75                 }
76
77                 [Test]
78                 public void CancelAfter_Invalid ()
79                 {
80                         var cts = new CancellationTokenSource ();
81                         try {
82                                 cts.CancelAfter (-9);
83                                 Assert.Fail ("#1");
84                         } catch (ArgumentException) {
85                         }
86                 }
87
88                 [Test]
89                 public void CancelAfter_Disposed ()
90                 {
91                         int called = 0;
92                         var cts = new CancellationTokenSource ();
93                         var mre = new ManualResetEvent (false);
94                         cts.Token.Register (() => { called++; mre.Set (); });
95                         cts.CancelAfter (50);
96                         cts.Dispose ();
97
98                         Assert.IsFalse (mre.WaitOne (100), "Shouldn't have been called");
99                         Assert.AreEqual (0, called, "#1");
100                 }
101
102
103                 [Test]
104                 public void Token ()
105                 {
106                         CancellationTokenSource cts = new CancellationTokenSource ();
107                         Assert.IsTrue (cts.Token.CanBeCanceled, "#1");
108                         Assert.IsFalse (cts.Token.IsCancellationRequested, "#2");
109                         Assert.IsNotNull (cts.Token.WaitHandle, "#3");
110                 }
111
112                 [Test]
113                 public void Cancel_NoRegistration ()
114                 {
115                         CancellationTokenSource cts = new CancellationTokenSource ();
116                         cts.Cancel ();
117                 }
118
119                 [Test]
120                 public void Cancel ()
121                 {
122                         var cts = new CancellationTokenSource ();
123
124                         int called = 0;
125                         cts.Token.Register (l => { Assert.AreEqual ("v", l); ++called; }, "v");
126                         cts.Cancel ();
127                         Assert.AreEqual (1, called, "#1");
128
129                         called = 0;
130                         cts.Token.Register (() => { called += 12; });
131                         cts.Cancel ();
132                         Assert.AreEqual (12, called, "#2");
133                 }
134
135
136                 [Test]
137                 public void Cancel_Order ()
138                 {
139                         var cts = new CancellationTokenSource ();
140                         var current = 0;
141                         Action<object> a = x => { Assert.AreEqual(current, x); current++; };
142
143                         cts.Token.Register (a, 2);
144                         cts.Token.Register (a, 1);
145                         cts.Token.Register (a, 0);
146                         cts.Cancel ();
147                 }
148
149
150                 [Test]
151                 public void CancelWithDispose ()
152                 {
153                         CancellationTokenSource cts = new CancellationTokenSource ();
154                         CancellationToken c = cts.Token;
155                         c.Register (() => {
156                                 cts.Dispose ();
157                         });
158
159                         int called = 0;
160                         c.Register (() => {
161                                 called++;
162                         });
163
164                         cts.Cancel ();
165                         Assert.AreEqual (1, called, "#1");
166                 }
167
168                 [Test]
169                 public void Cancel_SingleException ()
170                 {
171                         var cts = new CancellationTokenSource ();
172
173                         cts.Token.Register (() => { throw new ApplicationException (); });
174                         try {
175                                 cts.Cancel ();
176                                 Assert.Fail ("#1");
177                         } catch (AggregateException e) {
178                                 Assert.AreEqual (1, e.InnerExceptions.Count, "#2");
179                         }
180
181                         cts.Cancel ();
182                 }
183
184                 [Test]
185                 public void Cancel_MultipleExceptions ()
186                 {
187                         var cts = new CancellationTokenSource ();
188
189                         cts.Token.Register (() => { throw new ApplicationException ("1"); });
190                         cts.Token.Register (() => { throw new ApplicationException ("2"); });
191                         cts.Token.Register (() => { throw new ApplicationException ("3"); });
192
193                         try {
194                                 cts.Cancel ();
195                                 Assert.Fail ("#1");
196                         } catch (AggregateException e) {
197                                 Assert.AreEqual (3, e.InnerExceptions.Count, "#2");
198                         }
199
200                         cts.Cancel ();
201
202                         try {
203                                 cts.Token.Register (() => { throw new ApplicationException ("1"); });
204                                 Assert.Fail ("#11");
205                         } catch (ApplicationException) {
206                         }
207
208                         cts.Cancel ();
209                 }
210
211                 [Test]
212                 public void Cancel_ExceptionOrder ()
213                 {
214                         var cts = new CancellationTokenSource ();
215
216                         cts.Token.Register (() => { throw new ApplicationException ("1"); });
217                         cts.Token.Register (() => { throw new ApplicationException ("2"); });
218                         cts.Token.Register (() => { throw new ApplicationException ("3"); });
219
220                         try {
221                                 cts.Cancel ();
222                         } catch (AggregateException e) {
223                                 Assert.AreEqual (3, e.InnerExceptions.Count, "#2");
224                                 Assert.AreEqual ("3", e.InnerExceptions[0].Message, "#3");
225                                 Assert.AreEqual ("2", e.InnerExceptions[1].Message, "#4");
226                                 Assert.AreEqual ("1", e.InnerExceptions[2].Message, "#5");
227                         }
228                 }
229
230                 [Test]
231                 public void Cancel_MultipleException_Recursive ()
232                 {
233                         CancellationTokenSource cts = new CancellationTokenSource ();
234                         CancellationToken c = cts.Token;
235                         c.Register (() => {
236                                 cts.Cancel ();
237                         });
238
239                         c.Register (() => {
240                                 throw new ApplicationException ();
241                         });
242
243                         c.Register (() => {
244                                 throw new NotSupportedException ();
245                         });
246
247                         try {
248                                 cts.Cancel (false);
249                                 Assert.Fail ("#1");
250                         } catch (AggregateException e) {
251                                 Assert.AreEqual (2, e.InnerExceptions.Count, "#2");
252                         }
253                 }
254
255                 [Test]
256                 public void Cancel_MultipleExceptionsFirstThrows ()
257                 {
258                         var cts = new CancellationTokenSource ();
259
260                         cts.Token.Register (() => { throw new ApplicationException ("1"); });
261                         cts.Token.Register (() => { throw new ApplicationException ("2"); });
262                         cts.Token.Register (() => { throw new ApplicationException ("3"); });
263
264                         try {
265                                 cts.Cancel (true);
266                                 Assert.Fail ("#1");
267                         } catch (ApplicationException) {
268                         }
269
270                         cts.Cancel ();
271                 }
272
273                 [Test]
274                 public void CreateLinkedTokenSource_InvalidArguments ()
275                 {
276                         var cts = new CancellationTokenSource ();
277                         var token = cts.Token;
278
279                         try {
280                                 CancellationTokenSource.CreateLinkedTokenSource (null);
281                                 Assert.Fail ("#1");
282                         } catch (ArgumentNullException) {
283                         }
284
285                         try {
286                                 CancellationTokenSource.CreateLinkedTokenSource (new CancellationToken[0]);
287                                 Assert.Fail ("#2");
288                         } catch (ArgumentException) {
289                         }
290                 }
291
292                 [Test]
293                 public void CreateLinkedTokenSource ()
294                 {
295                         var cts = new CancellationTokenSource ();
296                         cts.Cancel ();
297
298                         var linked = CancellationTokenSource.CreateLinkedTokenSource (cts.Token);
299                         Assert.IsTrue (linked.IsCancellationRequested, "#1");
300
301                         linked = CancellationTokenSource.CreateLinkedTokenSource (new CancellationToken ());
302                         Assert.IsFalse (linked.IsCancellationRequested, "#2");
303                 }
304
305                 [Test]
306                 public void Dispose ()
307                 {
308                         var cts = new CancellationTokenSource ();
309                         var token = cts.Token;
310
311                         cts.Dispose ();
312                         cts.Dispose ();
313                         var b = cts.IsCancellationRequested;
314                         token.ThrowIfCancellationRequested ();
315
316                         try {
317                                 cts.Cancel ();
318                                 Assert.Fail ("#1");
319                         } catch (ObjectDisposedException) {
320                         }
321
322                         try {
323                                 var t = cts.Token;
324                                 Assert.Fail ("#2");
325                         } catch (ObjectDisposedException) {
326                         }
327
328                         bool throwOnDispose = false;
329                         AppContext.TryGetSwitch ("Switch.System.Threading.ThrowExceptionIfDisposedCancellationTokenSource", out throwOnDispose);
330                         if (throwOnDispose) { 
331                                 try {
332                                         token.Register (() => { });
333                                         Assert.Fail ("#3");
334                                 } catch (ObjectDisposedException) {
335                                 }
336                         }
337
338                         try {
339                                 var wh = token.WaitHandle;
340                                 Assert.Fail ("#4");
341                         } catch (ObjectDisposedException) {
342                         }
343
344                         if (throwOnDispose) {
345                                 try {
346                                         CancellationTokenSource.CreateLinkedTokenSource (token);
347                                         Assert.Fail ("#5");
348                                 } catch (ObjectDisposedException) {
349                                 }
350                         }
351
352                         try {
353                                 cts.CancelAfter (1);
354                                 Assert.Fail ("#6");
355                         } catch (ObjectDisposedException) {
356                         }
357                 }
358
359                 [Test]
360                 public void RegisterThenDispose ()
361                 {
362                         var cts1 = new CancellationTokenSource ();
363                         var reg1 = cts1.Token.Register (() => { throw new ApplicationException (); });
364
365                         var cts2 = new CancellationTokenSource ();
366                         var reg2 = cts2.Token.Register (() => { throw new ApplicationException (); });
367
368                         Assert.AreNotEqual (cts1, cts2, "#1");
369                         Assert.AreNotSame (cts1, cts2, "#2");
370
371                         reg1.Dispose ();
372                         cts1.Cancel ();
373
374                         try {
375                                 cts2.Cancel ();
376                                 Assert.Fail ("#3");
377                         } catch (AggregateException) {
378                         }
379                 }
380
381                 [Test]
382                 public void RegisterWhileCancelling ()
383                 {
384                         var cts = new CancellationTokenSource ();
385                         var mre = new ManualResetEvent (false);
386                         var mre2 = new ManualResetEvent (false);
387                         int called = 0;
388
389                         cts.Token.Register (() => {
390                                 Assert.IsTrue (cts.IsCancellationRequested, "#10");
391                                 Assert.IsTrue (cts.Token.WaitHandle.WaitOne (0), "#11");
392                                 mre2.Set ();
393                                 mre.WaitOne (3000);
394                                 called += 11;
395                         });
396
397                         var t = Task.Factory.StartNew (() => { cts.Cancel (); });
398
399                         Assert.IsTrue (mre2.WaitOne (1000), "#0");
400                         cts.Token.Register (() => { called++; });
401                         Assert.AreEqual (1, called, "#1");
402                         Assert.IsFalse (t.IsCompleted, "#2");
403
404                         mre.Set ();
405                         Assert.IsTrue (t.Wait (1000), "#3");
406                         Assert.AreEqual (12, called, "#4");
407                 }
408
409                 [Test]
410                 public void ReEntrantRegistrationTest ()
411                 {
412                         bool unregister = false;
413                         bool register = false;
414                         var source = new CancellationTokenSource ();
415                         var token = source.Token;
416
417                         Console.WriteLine ("Test1");
418                         var reg = token.Register (() => unregister = true);
419                         token.Register (() => reg.Dispose ());
420                         token.Register (() => { Console.WriteLine ("Gnyah"); token.Register (() => register = true); });
421                         source.Cancel ();
422
423                         Assert.IsFalse (unregister);
424                         Assert.IsTrue (register);
425                 }
426
427                 [Test]
428                 public void DisposeAfterRegistrationTest ()
429                 {
430                         var source = new CancellationTokenSource ();
431                         bool ran = false;
432                         var req = source.Token.Register (() => ran = true);
433                         source.Dispose ();
434                         req.Dispose ();
435                         Assert.IsFalse (ran);
436                 }
437
438                 [Test]
439                 public void CancelLinkedTokenSource ()
440                 {
441                         var cts = new CancellationTokenSource ();
442                         bool canceled = false;
443                         cts.Token.Register (() => canceled = true);
444
445                         using (var linked = CancellationTokenSource.CreateLinkedTokenSource (cts.Token))
446                                 ;
447
448                         Assert.IsFalse (canceled, "#1");
449                         Assert.IsFalse (cts.IsCancellationRequested, "#2");
450
451                         cts.Cancel ();
452
453                         Assert.IsTrue (canceled, "#3");
454                 }
455
456                 [Category ("NotWorking")] // why would linked token be imune to ObjectDisposedException on Cancel?
457                 [Test]
458                 public void ConcurrentCancelLinkedTokenSourceWhileDisposing ()
459                 {
460                         for (int i = 0, total = 500; i < total; ++i) {
461                                 var src = new CancellationTokenSource ();
462                                 var linked = CancellationTokenSource.CreateLinkedTokenSource (src.Token);
463                                 var cntd = new CountdownEvent (2);
464
465                                 var t1 = new Thread (() => {
466                                         if (!cntd.Signal ())
467                                                 cntd.Wait (200);
468                                         src.Cancel ();
469                                 });
470                                 var t2 = new Thread (() => {
471                                         if (!cntd.Signal ())
472                                                 cntd.Wait (200);
473                                         linked.Dispose ();
474                                 });
475
476                                 t1.Start ();
477                                 t2.Start ();
478                                 t1.Join (500);
479                                 t2.Join (500);
480                         }
481                 }
482
483                 [Test]
484                 public void DisposeRace ()
485                 {
486                         for (int i = 0, total = 1000; i < total; ++i) {
487                                 var c1 = new CancellationTokenSource ();
488                                 var wh = c1.Token.WaitHandle;
489                                 c1.CancelAfter (1);
490                                 Thread.Sleep (1);
491                                 c1.Dispose ();
492                         }
493                 }
494         }
495 }
496
497