Merge pull request #2964 from ludovic-henry/sgen-monocontext
[mono.git] / mcs / class / referencesource / System.Web / Util / CancellationTokenHelper.cs
1 //------------------------------------------------------------------------------
2 // <copyright file="CancellationTokenHelper.cs" company="Microsoft">
3 //     Copyright (c) Microsoft Corporation.  All rights reserved.
4 // </copyright>                                                                
5 //------------------------------------------------------------------------------
6
7 namespace System.Web.Util {
8     using System;
9     using System.Threading;
10
11     // Helper class for dealing with CancellationToken instances. Our Cancel and Dispose methods
12     // are fully thread-safe and will never block or throw, while a normal CancellationTokenSource
13     // doesn't make these guarantees.
14
15     internal sealed class CancellationTokenHelper : IDisposable {
16
17         private const int STATE_CREATED = 0;
18         private const int STATE_CANCELING = 1;
19         private const int STATE_CANCELED = 2;
20         private const int STATE_DISPOSING = 3;
21         private const int STATE_DISPOSED = 4; // terminal state
22
23         // A CancellationTokenHelper which is already marked as disposed; useful for avoiding
24         // allocations of CancellationTokenHelper instances which are never observed.
25         internal static readonly CancellationTokenHelper StaticDisposed = GetStaticDisposedHelper();
26
27         private readonly CancellationTokenSource _cts = new CancellationTokenSource();
28         private int _state;
29
30         public CancellationTokenHelper(bool canceled) {
31             if (canceled) {
32                 _cts.Cancel();
33             }
34             _state = (canceled) ? STATE_CANCELED : STATE_CREATED;
35         }
36
37         internal bool IsCancellationRequested {
38             get { return _cts.IsCancellationRequested; }
39         }
40
41         internal CancellationToken Token {
42             get { return _cts.Token; }
43         }
44
45         // Cancels the token.
46         public void Cancel() {
47             if (Interlocked.CompareExchange(ref _state, STATE_CANCELING, STATE_CREATED) == STATE_CREATED) {
48                 // Only allow cancellation if the token hasn't yet been canceled or disposed.
49                 // Cancel on a ThreadPool thread so that we can release the original thread back to IIS.
50                 // We can use UnsafeQUWI to avoid an extra ExecutionContext capture since CancellationToken already captures it.
51                 ThreadPool.UnsafeQueueUserWorkItem(_ => {
52                     try {
53                         _cts.Cancel();
54                     }
55                     catch {
56                         // ---- all exceptions to avoid killing the worker process.
57                     }
58                     finally {
59                         if (Interlocked.CompareExchange(ref _state, STATE_CANCELED, STATE_CANCELING) == STATE_DISPOSING) {
60                             // A call to Dispose() came in on another thread while we were in the middle of a cancel
61                             // operation. That thread will no-op, so we'll dispose of it here.
62                             _cts.Dispose();
63                             Interlocked.Exchange(ref _state, STATE_DISPOSED);
64                         }
65                     }
66                 }, null);
67             }
68         }
69
70         // Disposes of the token.
71         public void Dispose() {
72             // Only allow a single call to Dispose.
73             int originalState = Interlocked.Exchange(ref _state, STATE_DISPOSING);
74             switch (originalState) {
75                 case STATE_CREATED:
76                 case STATE_CANCELED:
77                     // If Cancel() hasn't yet been called or has already run to completion,
78                     // the underlying CTS guarantees that the Dispose method won't block
79                     // or throw, so we can just call it directly.
80                     _cts.Dispose();
81                     Interlocked.Exchange(ref _state, STATE_DISPOSED);
82                     break;
83
84                 case STATE_DISPOSED:
85                     // If the object was already disposed, we need to reset the flag here
86                     // since we accidentally blew it away with the original Exchange.
87                     Interlocked.Exchange(ref _state, STATE_DISPOSED);
88                     break;
89
90                 // Otherwise, the object is already canceling or disposing, so the
91                 // other thread will handle the call to Dispose().
92             }
93         }
94
95         private static CancellationTokenHelper GetStaticDisposedHelper() {
96             CancellationTokenHelper helper = new CancellationTokenHelper(false);
97             helper.Dispose();
98             return helper;
99         }
100
101     }
102 }