9ace8d354473383b168d89da8b94fe90c0ccb054
[platform/upstream/coreclr.git] / src / mscorlib / src / System / Threading / CancellationTokenSource.cs
1 // Licensed to the .NET Foundation under one or more agreements.
2 // The .NET Foundation licenses this file to you under the MIT license.
3 // See the LICENSE file in the project root for more information.
4
5 using System.Collections.Generic;
6 using System.Diagnostics;
7
8 namespace System.Threading
9 {
10     /// <summary>Signals to a <see cref="CancellationToken"/> that it should be canceled.</summary>
11     /// <remarks>
12     /// <para>
13     /// <see cref="CancellationTokenSource"/> is used to instantiate a <see cref="CancellationToken"/> (via
14     /// the source's <see cref="Token">Token</see> property) that can be handed to operations that wish to be
15     /// notified of cancellation or that can be used to register asynchronous operations for cancellation. That
16     /// token may have cancellation requested by calling to the source's <see cref="Cancel()"/> method.
17     /// </para>
18     /// <para>
19     /// All members of this class, except <see cref="Dispose"/>, are thread-safe and may be used
20     /// concurrently from multiple threads.
21     /// </para>
22     /// </remarks>
23     public class CancellationTokenSource : IDisposable
24     {
25         /// <summary>A <see cref="CancellationTokenSource"/> that's already canceled.</summary>
26         internal static readonly CancellationTokenSource s_canceledSource = new CancellationTokenSource() { _state = NotifyingCompleteState };
27         /// <summary>A <see cref="CancellationTokenSource"/> that's never canceled.  This isn't enforced programmatically, only by usage.  Do not cancel!</summary>
28         internal static readonly CancellationTokenSource s_neverCanceledSource = new CancellationTokenSource();
29
30         /// <summary>Delegate used with <see cref="Timer"/> to trigger cancellation of a <see cref="CancellationTokenSource"/>.</summary>
31         private static readonly TimerCallback s_timerCallback = obj =>
32             ((CancellationTokenSource)obj).NotifyCancellation(throwOnFirstException: false); // skip ThrowIfDisposed() check in Cancel()
33
34         /// <summary>The number of callback partitions to use in a <see cref="CancellationTokenSource"/>. Must be a power of 2.</summary>
35         private static readonly int s_numPartitions = GetPartitionCount();
36         /// <summary><see cref="s_numPartitions"/> - 1, used to quickly mod into <see cref="_callbackPartitions"/>.</summary>
37         private static readonly int s_numPartitionsMask = s_numPartitions - 1;
38
39         /// <summary>The current state of the CancellationTokenSource.</summary>
40         private volatile int _state;
41         /// <summary>The ID of the thread currently executing the main body of CTS.Cancel()</summary>
42         /// <remarks>
43         /// This helps us to know if a call to ctr.Dispose() is running 'within' a cancellation callback.
44         /// This is updated as we move between the main thread calling cts.Cancel() and any syncContexts
45         /// that are used to actually run the callbacks.
46         /// </remarks>
47         private volatile int _threadIDExecutingCallbacks = -1;
48         /// <summary>Tracks the running callback to assist ctr.Dispose() to wait for the target callback to complete.</summary>
49         private long _executingCallbackId;
50         /// <summary>Partitions of callbacks.  Split into multiple partitions to help with scalability of registering/unregistering; each is protected by its own lock.</summary>
51         private volatile CallbackPartition[] _callbackPartitions;
52         /// <summary>Timer used by CancelAfter and Timer-related ctors.</summary>
53         private volatile Timer _timer;
54         /// <summary><see cref="System.Threading.WaitHandle"/> lazily initialized and returned from <see cref="WaitHandle"/>.</summary>
55         private volatile ManualResetEvent _kernelEvent;
56         /// <summary>Whether this <see cref="CancellationTokenSource"/> has been disposed.</summary>
57         private bool _disposed;
58
59         // legal values for _state
60         private const int NotCanceledState = 1;
61         private const int NotifyingState = 2;
62         private const int NotifyingCompleteState = 3;
63
64         /// <summary>Gets whether cancellation has been requested for this <see cref="CancellationTokenSource" />.</summary>
65         /// <value>Whether cancellation has been requested for this <see cref="CancellationTokenSource" />.</value>
66         /// <remarks>
67         /// <para>
68         /// This property indicates whether cancellation has been requested for this token source, such as
69         /// due to a call to its <see cref="Cancel()"/> method.
70         /// </para>
71         /// <para>
72         /// If this property returns true, it only guarantees that cancellation has been requested. It does not
73         /// guarantee that every handler registered with the corresponding token has finished executing, nor
74         /// that cancellation requests have finished propagating to all registered handlers. Additional
75         /// synchronization may be required, particularly in situations where related objects are being
76         /// canceled concurrently.
77         /// </para>
78         /// </remarks>
79         public bool IsCancellationRequested => _state >= NotifyingState;
80
81         /// <summary>A simple helper to determine whether cancellation has finished.</summary>
82         internal bool IsCancellationCompleted => _state == NotifyingCompleteState;
83
84         /// <summary>A simple helper to determine whether disposal has occurred.</summary>
85         internal bool IsDisposed => _disposed;
86
87         /// <summary>The ID of the thread that is running callbacks.</summary>
88         internal int ThreadIDExecutingCallbacks
89         {
90             get => _threadIDExecutingCallbacks;
91             set => _threadIDExecutingCallbacks = value;
92         }
93
94         /// <summary>Gets the <see cref="CancellationToken"/> associated with this <see cref="CancellationTokenSource"/>.</summary>
95         /// <value>The <see cref="CancellationToken"/> associated with this <see cref="CancellationTokenSource"/>.</value>
96         /// <exception cref="ObjectDisposedException">The token source has been disposed.</exception>
97         public CancellationToken Token
98         {
99             get
100             {
101                 ThrowIfDisposed();
102                 return new CancellationToken(this);
103             }
104         }
105
106         internal WaitHandle WaitHandle
107         {
108             get
109             {
110                 ThrowIfDisposed();
111
112                 // Return the handle if it was already allocated.
113                 if (_kernelEvent != null)
114                 {
115                     return _kernelEvent;
116                 }
117
118                 // Lazily-initialize the handle.
119                 var mre = new ManualResetEvent(false);
120                 if (Interlocked.CompareExchange(ref _kernelEvent, mre, null) != null)
121                 {
122                     mre.Dispose();
123                 }
124
125                 // There is a race condition between checking IsCancellationRequested and setting the event.
126                 // However, at this point, the kernel object definitely exists and the cases are:
127                 //   1. if IsCancellationRequested = true, then we will call Set()
128                 //   2. if IsCancellationRequested = false, then NotifyCancellation will see that the event exists, and will call Set().
129                 if (IsCancellationRequested)
130                 {
131                     _kernelEvent.Set();
132                 }
133
134                 return _kernelEvent;
135             }
136         }
137
138
139         /// <summary>Gets the ID of the currently executing callback.</summary>
140         internal long ExecutingCallback => Volatile.Read(ref _executingCallbackId);
141
142         /// <summary>Initializes the <see cref="CancellationTokenSource"/>.</summary>
143         public CancellationTokenSource() => _state = NotCanceledState;
144
145         /// <summary>
146         /// Constructs a <see cref="CancellationTokenSource"/> that will be canceled after a specified time span.
147         /// </summary>
148         /// <param name="delay">The time span to wait before canceling this <see cref="CancellationTokenSource"/></param>
149         /// <exception cref="ArgumentOutOfRangeException">
150         /// The exception that is thrown when <paramref name="delay"/> is less than -1 or greater than Int32.MaxValue.
151         /// </exception>
152         /// <remarks>
153         /// <para>
154         /// The countdown for the delay starts during the call to the constructor.  When the delay expires, 
155         /// the constructed <see cref="CancellationTokenSource"/> is canceled, if it has
156         /// not been canceled already.
157         /// </para>
158         /// <para>
159         /// Subsequent calls to CancelAfter will reset the delay for the constructed 
160         /// <see cref="CancellationTokenSource"/>, if it has not been
161         /// canceled already.
162         /// </para>
163         /// </remarks>
164         public CancellationTokenSource(TimeSpan delay)
165         {
166             long totalMilliseconds = (long)delay.TotalMilliseconds;
167             if (totalMilliseconds < -1 || totalMilliseconds > int.MaxValue)
168             {
169                 throw new ArgumentOutOfRangeException(nameof(delay));
170             }
171
172             InitializeWithTimer((int)totalMilliseconds);
173         }
174
175         /// <summary>
176         /// Constructs a <see cref="CancellationTokenSource"/> that will be canceled after a specified time span.
177         /// </summary>
178         /// <param name="millisecondsDelay">The time span to wait before canceling this <see cref="CancellationTokenSource"/></param>
179         /// <exception cref="ArgumentOutOfRangeException">
180         /// The exception that is thrown when <paramref name="millisecondsDelay"/> is less than -1.
181         /// </exception>
182         /// <remarks>
183         /// <para>
184         /// The countdown for the millisecondsDelay starts during the call to the constructor.  When the millisecondsDelay expires, 
185         /// the constructed <see cref="CancellationTokenSource"/> is canceled (if it has
186         /// not been canceled already).
187         /// </para>
188         /// <para>
189         /// Subsequent calls to CancelAfter will reset the millisecondsDelay for the constructed 
190         /// <see cref="CancellationTokenSource"/>, if it has not been
191         /// canceled already.
192         /// </para>
193         /// </remarks>
194         public CancellationTokenSource(int millisecondsDelay)
195         {
196             if (millisecondsDelay < -1)
197             {
198                 throw new ArgumentOutOfRangeException(nameof(millisecondsDelay));
199             }
200
201             InitializeWithTimer(millisecondsDelay);
202         }
203
204         /// <summary>Common initialization logic when constructing a CTS with a delay parameter</summary>
205         private void InitializeWithTimer(int millisecondsDelay)
206         {
207             _state = NotCanceledState;
208             _timer = new Timer(s_timerCallback, this, millisecondsDelay, -1);
209         }
210
211         /// <summary>Communicates a request for cancellation.</summary>
212         /// <remarks>
213         /// <para>
214         /// The associated <see cref="CancellationToken" /> will be notified of the cancellation
215         /// and will transition to a state where <see cref="CancellationToken.IsCancellationRequested"/> returns true. 
216         /// Any callbacks or cancelable operations registered with the <see cref="CancellationToken"/>  will be executed.
217         /// </para>
218         /// <para>
219         /// Cancelable operations and callbacks registered with the token should not throw exceptions.
220         /// However, this overload of Cancel will aggregate any exceptions thrown into a <see cref="AggregateException"/>,
221         /// such that one callback throwing an exception will not prevent other registered callbacks from being executed.
222         /// </para>
223         /// <para>
224         /// The <see cref="ExecutionContext"/> that was captured when each callback was registered
225         /// will be reestablished when the callback is invoked.
226         /// </para>
227         /// </remarks>
228         /// <exception cref="AggregateException">An aggregate exception containing all the exceptions thrown
229         /// by the registered callbacks on the associated <see cref="CancellationToken"/>.</exception>
230         /// <exception cref="ObjectDisposedException">This <see cref="CancellationTokenSource"/> has been disposed.</exception> 
231         public void Cancel() => Cancel(false);
232
233         /// <summary>Communicates a request for cancellation.</summary>
234         /// <remarks>
235         /// <para>
236         /// The associated <see cref="CancellationToken" /> will be notified of the cancellation and will transition to a state where 
237         /// <see cref="CancellationToken.IsCancellationRequested"/> returns true. Any callbacks or cancelable operationsregistered
238         /// with the <see cref="CancellationToken"/>  will be executed.
239         /// </para>
240         /// <para>
241         /// Cancelable operations and callbacks registered with the token should not throw exceptions. 
242         /// If <paramref name="throwOnFirstException"/> is true, an exception will immediately propagate out of the
243         /// call to Cancel, preventing the remaining callbacks and cancelable operations from being processed.
244         /// If <paramref name="throwOnFirstException"/> is false, this overload will aggregate any 
245         /// exceptions thrown into a <see cref="AggregateException"/>,
246         /// such that one callback throwing an exception will not prevent other registered callbacks from being executed.
247         /// </para>
248         /// <para>
249         /// The <see cref="ExecutionContext"/> that was captured when each callback was registered
250         /// will be reestablished when the callback is invoked.
251         /// </para>
252         /// </remarks>
253         /// <param name="throwOnFirstException">Specifies whether exceptions should immediately propagate.</param>
254         /// <exception cref="AggregateException">An aggregate exception containing all the exceptions thrown
255         /// by the registered callbacks on the associated <see cref="CancellationToken"/>.</exception>
256         /// <exception cref="ObjectDisposedException">This <see cref="CancellationTokenSource"/> has been disposed.</exception> 
257         public void Cancel(bool throwOnFirstException)
258         {
259             ThrowIfDisposed();
260             NotifyCancellation(throwOnFirstException);
261         }
262
263         /// <summary>Schedules a Cancel operation on this <see cref="CancellationTokenSource"/>.</summary>
264         /// <param name="delay">The time span to wait before canceling this <see cref="CancellationTokenSource"/>.
265         /// </param>
266         /// <exception cref="ObjectDisposedException">The exception thrown when this <see
267         /// cref="CancellationTokenSource"/> has been disposed.
268         /// </exception>
269         /// <exception cref="ArgumentOutOfRangeException">
270         /// The exception thrown when <paramref name="delay"/> is less than -1 or 
271         /// greater than Int32.MaxValue.
272         /// </exception>
273         /// <remarks>
274         /// <para>
275         /// The countdown for the delay starts during this call.  When the delay expires, 
276         /// this <see cref="CancellationTokenSource"/> is canceled, if it has
277         /// not been canceled already.
278         /// </para>
279         /// <para>
280         /// Subsequent calls to CancelAfter will reset the delay for this  
281         /// <see cref="CancellationTokenSource"/>, if it has not been canceled already.
282         /// </para>
283         /// </remarks>
284         public void CancelAfter(TimeSpan delay)
285         {
286             long totalMilliseconds = (long)delay.TotalMilliseconds;
287             if (totalMilliseconds < -1 || totalMilliseconds > int.MaxValue)
288             {
289                 throw new ArgumentOutOfRangeException(nameof(delay));
290             }
291
292             CancelAfter((int)totalMilliseconds);
293         }
294
295         /// <summary>
296         /// Schedules a Cancel operation on this <see cref="CancellationTokenSource"/>.
297         /// </summary>
298         /// <param name="millisecondsDelay">The time span to wait before canceling this <see
299         /// cref="CancellationTokenSource"/>.
300         /// </param>
301         /// <exception cref="ObjectDisposedException">The exception thrown when this <see
302         /// cref="CancellationTokenSource"/> has been disposed.
303         /// </exception>
304         /// <exception cref="ArgumentOutOfRangeException">
305         /// The exception thrown when <paramref name="millisecondsDelay"/> is less than -1.
306         /// </exception>
307         /// <remarks>
308         /// <para>
309         /// The countdown for the millisecondsDelay starts during this call.  When the millisecondsDelay expires, 
310         /// this <see cref="CancellationTokenSource"/> is canceled, if it has
311         /// not been canceled already.
312         /// </para>
313         /// <para>
314         /// Subsequent calls to CancelAfter will reset the millisecondsDelay for this  
315         /// <see cref="CancellationTokenSource"/>, if it has not been
316         /// canceled already.
317         /// </para>
318         /// </remarks>
319         public void CancelAfter(int millisecondsDelay)
320         {
321             ThrowIfDisposed();
322
323             if (millisecondsDelay < -1)
324             {
325                 throw new ArgumentOutOfRangeException(nameof(millisecondsDelay));
326             }
327
328             if (IsCancellationRequested)
329             {
330                 return;
331             }
332
333             // There is a race condition here as a Cancel could occur between the check of
334             // IsCancellationRequested and the creation of the timer.  This is benign; in the 
335             // worst case, a timer will be created that has no effect when it expires.
336
337             // Also, if Dispose() is called right here (after ThrowIfDisposed(), before timer
338             // creation), it would result in a leaked Timer object (at least until the timer
339             // expired and Disposed itself).  But this would be considered bad behavior, as
340             // Dispose() is not thread-safe and should not be called concurrently with CancelAfter().
341
342             if (_timer == null)
343             {
344                 // Lazily initialize the timer in a thread-safe fashion.
345                 // Initially set to "never go off" because we don't want to take a
346                 // chance on a timer "losing" the initialization and then
347                 // cancelling the token before it (the timer) can be disposed.
348                 Timer newTimer = new Timer(s_timerCallback, this, -1, -1);
349                 if (Interlocked.CompareExchange(ref _timer, newTimer, null) != null)
350                 {
351                     // We did not initialize the timer.  Dispose the new timer.
352                     newTimer.Dispose();
353                 }
354             }
355
356             // It is possible that m_timer has already been disposed, so we must do
357             // the following in a try/catch block.
358             try
359             {
360                 _timer.Change(millisecondsDelay, -1);
361             }
362             catch (ObjectDisposedException)
363             {
364                 // Just eat the exception.  There is no other way to tell that
365                 // the timer has been disposed, and even if there were, there
366                 // would not be a good way to deal with the observe/dispose
367                 // race condition.
368             }
369         }
370
371
372
373         /// <summary>Releases the resources used by this <see cref="CancellationTokenSource" />.</summary>
374         /// <remarks>This method is not thread-safe for any other concurrent calls.</remarks>
375         public void Dispose()
376         {
377             Dispose(true);
378             GC.SuppressFinalize(this);
379         }
380
381         /// <summary>
382         /// Releases the unmanaged resources used by the <see cref="CancellationTokenSource" /> class and optionally releases the managed resources.
383         /// </summary>
384         /// <param name="disposing">true to release both managed and unmanaged resources; false to release only unmanaged resources.</param>
385         protected virtual void Dispose(bool disposing)
386         {
387             if (disposing && !_disposed)
388             {
389                 // We specifically tolerate that a callback can be deregistered
390                 // after the CTS has been disposed and/or concurrently with cts.Dispose().
391                 // This is safe without locks because Dispose doesn't interact with values
392                 // in the callback partitions, only nulling out the ref to existing partitions.
393                 // 
394                 // We also tolerate that a callback can be registered after the CTS has been
395                 // disposed.  This is safe because InternalRegister is tolerant
396                 // of _callbackPartitions becoming null during its execution.  However,
397                 // we run the acceptable risk of _callbackPartitions getting reinitialized
398                 // to non-null if there is a race between Dispose and Register, in which case this
399                 // instance may unnecessarily hold onto a registered callback.  But that's no worse
400                 // than if Dispose wasn't safe to use concurrently, as Dispose would never be called,
401                 // and thus no handlers would be dropped.
402                 //
403                 // And, we tolerate Dispose being used concurrently with Cancel.  This is necessary
404                 // to properly support, e.g., LinkedCancellationTokenSource, where, due to common usage patterns,
405                 // it's possible for this pairing to occur with valid usage (e.g. a component accepts
406                 // an external CancellationToken and uses CreateLinkedTokenSource to combine it with an
407                 // internal source of cancellation, then Disposes of that linked source, which could
408                 // happen at the same time the external entity is requesting cancellation).
409
410                 _timer?.Dispose(); // Timer.Dispose is thread-safe
411
412                 _callbackPartitions = null; // free for GC; Cancel correctly handles a null field
413
414                 // If a kernel event was created via WaitHandle, we'd like to Dispose of it.  However,
415                 // we only want to do so if it's not being used by Cancel concurrently.  First, we
416                 // interlocked exchange it to be null, and then we check whether cancellation is currently
417                 // in progress.  NotifyCancellation will only try to set the event if it exists after it's
418                 // transitioned to and while it's in the NotifyingState.
419                 if (_kernelEvent != null)
420                 {
421                     ManualResetEvent mre = Interlocked.Exchange(ref _kernelEvent, null);
422                     if (mre != null && _state != NotifyingState)
423                     {
424                         mre.Dispose();
425                     }
426                 }
427
428                 _disposed = true;
429             }
430         }
431
432         /// <summary>Throws an exception if the source has been disposed.</summary>
433         private void ThrowIfDisposed()
434         {
435             if (_disposed)
436             {
437                 ThrowObjectDisposedException();
438             }
439         }
440
441         /// <summary>Throws an <see cref="ObjectDisposedException"/>.  Separated out from ThrowIfDisposed to help with inlining.</summary>
442         private static void ThrowObjectDisposedException() =>
443             throw new ObjectDisposedException(null, SR.CancellationTokenSource_Disposed);
444
445         /// <summary>
446         /// Registers a callback object. If cancellation has already occurred, the
447         /// callback will have been run by the time this method returns.
448         /// </summary>
449         internal CancellationTokenRegistration InternalRegister(
450             Action<object> callback, object stateForCallback, SynchronizationContext syncContext, ExecutionContext executionContext)
451         {
452             Debug.Assert(this != s_neverCanceledSource, "This source should never be exposed via a CancellationToken.");
453
454             // If not canceled, register the handler; if canceled already, run the callback synchronously.
455             // This also ensures that during ExecuteCallbackHandlers() there will be no mutation of the _callbackPartitions.
456             if (!IsCancellationRequested)
457             {
458                 // In order to enable code to not leak too many handlers, we allow Dispose to be called concurrently
459                 // with Register.  While this is not a recommended practice, consumers can and do use it this way.
460                 // We don't make any guarantees about whether the CTS will hold onto the supplied callback if the CTS
461                 // has already been disposed when the callback is registered, but we try not to while at the same time
462                 // not paying any non-negligible overhead.  The simple compromise is to check whether we're disposed
463                 // (not volatile), and if we see we are, to return an empty registration. If there's a race and _disposed
464                 // is false even though it's been disposed, or if the disposal request comes in after this line, we simply
465                 // run the minor risk of having _callbackPartitions reinitialized (after it was cleared to null during Dispose).
466                 if (_disposed)
467                 {
468                     return new CancellationTokenRegistration();
469                 }
470
471                 // Get the partitions...
472                 CallbackPartition[] partitions = _callbackPartitions;
473                 if (partitions == null)
474                 {
475                     partitions = new CallbackPartition[s_numPartitions];
476                     partitions = Interlocked.CompareExchange(ref _callbackPartitions, partitions, null) ?? partitions;
477                 }
478
479                 // ...and determine which partition to use.
480                 int partitionIndex = Environment.CurrentManagedThreadId & s_numPartitionsMask;
481                 Debug.Assert(partitionIndex < partitions.Length, $"Expected {partitionIndex} to be less than {partitions.Length}");
482                 CallbackPartition partition = partitions[partitionIndex];
483                 if (partition == null)
484                 {
485                     partition = new CallbackPartition(this);
486                     partition = Interlocked.CompareExchange(ref partitions[partitionIndex], partition, null) ?? partition;
487                 }
488
489                 // Store the callback information into the callback arrays.
490                 long id;
491                 CallbackNode node;
492                 bool lockTaken = false;
493                 partition.Lock.Enter(ref lockTaken);
494                 try
495                 {
496                     // Assign the next available unique ID.
497                     id = partition.NextAvailableId++;
498
499                     // Get a node, from the free list if possible or else a new one.
500                     node = partition.FreeNodeList;
501                     if (node != null)
502                     {
503                         partition.FreeNodeList = node.Next;
504                         Debug.Assert(node.Prev == null, "Nodes in the free list should all have a null Prev");
505                         // node.Next will be overwritten below so no need to set it here.
506                     }
507                     else
508                     {
509                         node = new CallbackNode(partition);
510                     }
511
512                     // Configure the node.
513                     node.Id = id;
514                     node.Callback = callback;
515                     node.CallbackState = stateForCallback;
516                     node.ExecutionContext = executionContext;
517                     node.SynchronizationContext = syncContext;
518
519                     // Add it to the callbacks list.
520                     node.Next = partition.Callbacks;
521                     if (node.Next != null)
522                     {
523                         node.Next.Prev = node;
524                     }
525                     partition.Callbacks = node;
526                 }
527                 finally
528                 {
529                     partition.Lock.Exit(useMemoryBarrier: false); // no check on lockTaken needed without thread aborts
530                 }
531
532                 // If cancellation hasn't been requested, return the registration.
533                 // if cancellation has been requested, try to undo the registration and run the callback
534                 // ourselves, but if we can't unregister it (e.g. the thread running Cancel snagged
535                 // our callback for execution), return the registration so that the caller can wait
536                 // for callback completion in ctr.Dispose().
537                 var ctr = new CancellationTokenRegistration(id, node);
538                 if (!IsCancellationRequested || !partition.Unregister(id, node))
539                 {
540                     return ctr;
541                 }
542             }
543
544             // Cancellation already occurred.  Run the callback on this thread and return an empty registration.
545             callback(stateForCallback);
546             return default(CancellationTokenRegistration);
547         }
548
549         private void NotifyCancellation(bool throwOnFirstException)
550         {
551             // If we're the first to signal cancellation, do the main extra work.
552             if (!IsCancellationRequested && Interlocked.CompareExchange(ref _state, NotifyingState, NotCanceledState) == NotCanceledState)
553             {
554                 // Dispose of the timer, if any.  Dispose may be running concurrently here, but Timer.Dispose is thread-safe.
555                 _timer?.Dispose();
556
557                 // Set the event if it's been lazily initialized and hasn't yet been disposed of.  Dispose may
558                 // be running concurrently, in which case either it'll have set m_kernelEvent back to null and
559                 // we won't see it here, or it'll see that we've transitioned to NOTIFYING and will skip disposing it,
560                 // leaving cleanup to finalization.
561                 _kernelEvent?.Set(); // update the MRE value.
562
563                 // - late enlisters to the Canceled event will have their callbacks called immediately in the Register() methods.
564                 // - Callbacks are not called inside a lock.
565                 // - After transition, no more delegates will be added to the 
566                 // - list of handlers, and hence it can be consumed and cleared at leisure by ExecuteCallbackHandlers.
567                 ExecuteCallbackHandlers(throwOnFirstException);
568                 Debug.Assert(IsCancellationCompleted, "Expected cancellation to have finished");
569             }
570         }
571
572         /// <summary>Invoke all registered callbacks.</summary>
573         /// <remarks>The handlers are invoked synchronously in LIFO order.</remarks>
574         private void ExecuteCallbackHandlers(bool throwOnFirstException)
575         {
576             Debug.Assert(IsCancellationRequested, "ExecuteCallbackHandlers should only be called after setting IsCancellationRequested->true");
577
578             // Record the threadID being used for running the callbacks.
579             ThreadIDExecutingCallbacks = Thread.CurrentThread.ManagedThreadId;
580
581             // If there are no callbacks to run, we can safely exit.  Any race conditions to lazy initialize it
582             // will see IsCancellationRequested and will then run the callback themselves.
583             CallbackPartition[] partitions = Interlocked.Exchange(ref _callbackPartitions, null);
584             if (partitions == null)
585             {
586                 Interlocked.Exchange(ref _state, NotifyingCompleteState);
587                 return;
588             }
589
590             List<Exception> exceptionList = null;
591             try
592             {
593                 // For each partition, and each callback in that partition, execute the associated handler.
594                 // We call the delegates in LIFO order on each partition so that callbacks fire 'deepest first'.
595                 // This is intended to help with nesting scenarios so that child enlisters cancel before their parents.
596                 foreach (CallbackPartition partition in partitions)
597                 {
598                     if (partition == null)
599                     {
600                         // Uninitialized partition. Nothing to do.
601                         continue;
602                     }
603
604                     // Get the callbacks from the partition, substituting in null so that anyone
605                     // else coming along (e.g. CTR.Dispose) will find the callbacks gone.
606                     CallbackNode node;
607                     bool lockTaken = false;
608                     partition.Lock.Enter(ref lockTaken); // try/finally not needed without thread aborts
609                     {
610                         node = partition.Callbacks;
611                         partition.Callbacks = null;
612                     }
613                     partition.Lock.Exit(useMemoryBarrier: false);
614
615                     for (; node != null; node = node.Next)
616                     {
617                         // Publish the intended callback, to ensure ctr.Dispose can tell if a wait is necessary.
618                         Volatile.Write(ref _executingCallbackId, node.Id);
619
620                         // Invoke the callback on this thread if there's no sync context or on the
621                         // target sync context if there is one.
622                         try
623                         {
624                             if (node.SynchronizationContext != null)
625                             {
626                                 // Transition to the target syncContext and continue there.
627                                 node.SynchronizationContext.Send(s =>
628                                 {
629                                     var n = (CallbackNode)s;
630                                     n.Partition.Source.ThreadIDExecutingCallbacks = Thread.CurrentThread.ManagedThreadId;
631                                     n.ExecuteCallback();
632                                 }, node);
633                                 ThreadIDExecutingCallbacks = Thread.CurrentThread.ManagedThreadId; // above may have altered ThreadIDExecutingCallbacks, so reset it
634                             }
635                             else
636                             {
637                                 node.ExecuteCallback();
638                             }
639                         }
640                         catch (Exception ex) when (!throwOnFirstException)
641                         {
642                             // Store the exception and continue
643                             (exceptionList ?? (exceptionList = new List<Exception>())).Add(ex);
644                         }
645                     }
646                 }
647             }
648             finally
649             {
650                 _state = NotifyingCompleteState;
651                 Volatile.Write(ref _executingCallbackId, 0);
652                 Interlocked.MemoryBarrier(); // for safety, prevent reorderings crossing this point and seeing inconsistent state.
653             }
654
655             if (exceptionList != null)
656             {
657                 Debug.Assert(exceptionList.Count > 0, $"Expected {exceptionList.Count} > 0");
658                 throw new AggregateException(exceptionList);
659             }
660         }
661
662         /// <summary>Gets the number of callback partitions to use based on the number of cores.</summary>
663         /// <returns>A power of 2 representing the number of partitions to use.</returns>
664         private static int GetPartitionCount()
665         {
666             int procs = PlatformHelper.ProcessorCount;
667             int count =
668                 procs > 8 ? 16 : // capped at 16 to limit memory usage on larger machines
669                 procs > 4 ? 8 :
670                 procs > 2 ? 4 :
671                 procs > 1 ? 2 :
672                 1;
673             Debug.Assert(count > 0 && (count & (count - 1)) == 0, $"Got {count}, but expected a power of 2");
674             return count;
675         }
676
677         /// <summary>
678         /// Creates a <see cref="CancellationTokenSource"/> that will be in the canceled state
679         /// when any of the source tokens are in the canceled state.
680         /// </summary>
681         /// <param name="token1">The first <see cref="CancellationToken">CancellationToken</see> to observe.</param>
682         /// <param name="token2">The second <see cref="CancellationToken">CancellationToken</see> to observe.</param>
683         /// <returns>A <see cref="CancellationTokenSource"/> that is linked 
684         /// to the source tokens.</returns>
685         public static CancellationTokenSource CreateLinkedTokenSource(CancellationToken token1, CancellationToken token2) =>
686             !token1.CanBeCanceled ? CreateLinkedTokenSource(token2) :
687             token2.CanBeCanceled ? new Linked2CancellationTokenSource(token1, token2) :
688             (CancellationTokenSource)new Linked1CancellationTokenSource(token1);
689
690         /// <summary>
691         /// Creates a <see cref="CancellationTokenSource"/> that will be in the canceled state
692         /// when any of the source tokens are in the canceled state.
693         /// </summary>
694         /// <param name="token">The first <see cref="CancellationToken">CancellationToken</see> to observe.</param>
695         /// <returns>A <see cref="CancellationTokenSource"/> that is linked to the source tokens.</returns>
696         internal static CancellationTokenSource CreateLinkedTokenSource(CancellationToken token) =>
697             token.CanBeCanceled ? new Linked1CancellationTokenSource(token) : new CancellationTokenSource();
698
699         /// <summary>
700         /// Creates a <see cref="CancellationTokenSource"/> that will be in the canceled state
701         /// when any of the source tokens are in the canceled state.
702         /// </summary>
703         /// <param name="tokens">The <see cref="CancellationToken">CancellationToken</see> instances to observe.</param>
704         /// <returns>A <see cref="CancellationTokenSource"/> that is linked to the source tokens.</returns>
705         /// <exception cref="System.ArgumentNullException"><paramref name="tokens"/> is null.</exception>
706         public static CancellationTokenSource CreateLinkedTokenSource(params CancellationToken[] tokens)
707         {
708             if (tokens == null)
709             {
710                 throw new ArgumentNullException(nameof(tokens));
711             }
712
713             switch (tokens.Length)
714             {
715                 case 0:
716                     throw new ArgumentException(SR.CancellationToken_CreateLinkedToken_TokensIsEmpty);
717                 case 1:
718                     return CreateLinkedTokenSource(tokens[0]);
719                 case 2:
720                     return CreateLinkedTokenSource(tokens[0], tokens[1]);
721                 default:
722                     // a defensive copy is not required as the array has value-items that have only a single reference field,
723                     // hence each item cannot be null itself, and reads of the payloads cannot be torn.
724                     return new LinkedNCancellationTokenSource(tokens);
725             }
726         }
727
728
729
730         /// <summary>
731         /// Wait for a single callback to complete (or, more specifically, to not be running).
732         /// It is ok to call this method if the callback has already finished.
733         /// Calling this method before the target callback has been selected for execution would be an error.
734         /// </summary>
735         internal void WaitForCallbackToComplete(long id)
736         {
737             var sw = new SpinWait();
738             while (ExecutingCallback == id)
739             {
740                 sw.SpinOnce();  // spin, as we assume callback execution is fast and that this situation is rare.
741             }
742         }
743
744         private sealed class Linked1CancellationTokenSource : CancellationTokenSource
745         {
746             private readonly CancellationTokenRegistration _reg1;
747
748             internal Linked1CancellationTokenSource(CancellationToken token1)
749             {
750                 _reg1 = token1.InternalRegisterWithoutEC(LinkedNCancellationTokenSource.s_linkedTokenCancelDelegate, this);
751             }
752
753             protected override void Dispose(bool disposing)
754             {
755                 if (!disposing || _disposed)
756                 {
757                     return;
758                 }
759
760                 _reg1.Dispose();
761                 base.Dispose(disposing);
762             }
763         }
764
765         private sealed class Linked2CancellationTokenSource : CancellationTokenSource
766         {
767             private readonly CancellationTokenRegistration _reg1;
768             private readonly CancellationTokenRegistration _reg2;
769
770             internal Linked2CancellationTokenSource(CancellationToken token1, CancellationToken token2)
771             {
772                 _reg1 = token1.InternalRegisterWithoutEC(LinkedNCancellationTokenSource.s_linkedTokenCancelDelegate, this);
773                 _reg2 = token2.InternalRegisterWithoutEC(LinkedNCancellationTokenSource.s_linkedTokenCancelDelegate, this);
774             }
775
776             protected override void Dispose(bool disposing)
777             {
778                 if (!disposing || _disposed)
779                 {
780                     return;
781                 }
782
783                 _reg1.Dispose();
784                 _reg2.Dispose();
785                 base.Dispose(disposing);
786             }
787         }
788
789         private sealed class LinkedNCancellationTokenSource : CancellationTokenSource
790         {
791             internal static readonly Action<object> s_linkedTokenCancelDelegate =
792                 s => ((CancellationTokenSource)s).NotifyCancellation(throwOnFirstException: false); // skip ThrowIfDisposed() check in Cancel()
793             private CancellationTokenRegistration[] m_linkingRegistrations;
794
795             internal LinkedNCancellationTokenSource(params CancellationToken[] tokens)
796             {
797                 m_linkingRegistrations = new CancellationTokenRegistration[tokens.Length];
798
799                 for (int i = 0; i < tokens.Length; i++)
800                 {
801                     if (tokens[i].CanBeCanceled)
802                     {
803                         m_linkingRegistrations[i] = tokens[i].InternalRegisterWithoutEC(s_linkedTokenCancelDelegate, this);
804                     }
805                     // Empty slots in the array will be default(CancellationTokenRegistration), which are nops to Dispose.
806                     // Based on usage patterns, such occurrences should also be rare, such that it's not worth resizing
807                     // the array and incurring the related costs.
808                 }
809             }
810
811             protected override void Dispose(bool disposing)
812             {
813                 if (!disposing || _disposed)
814                 {
815                     return;
816                 }
817
818                 CancellationTokenRegistration[] linkingRegistrations = m_linkingRegistrations;
819                 if (linkingRegistrations != null)
820                 {
821                     m_linkingRegistrations = null; // release for GC once we're done enumerating
822                     for (int i = 0; i < linkingRegistrations.Length; i++)
823                     {
824                         linkingRegistrations[i].Dispose();
825                     }
826                 }
827
828                 base.Dispose(disposing);
829             }
830         }
831
832         internal sealed class CallbackPartition
833         {
834             /// <summary>The associated source that owns this partition.</summary>
835             public readonly CancellationTokenSource Source;
836             /// <summary>Lock that protects all state in the partition.</summary>
837             public SpinLock Lock = new SpinLock(enableThreadOwnerTracking: false); // mutable struct; do not make this readonly
838             /// <summary>
839             /// The array of callbacks registered in the partition.  Slots may be empty, meaning a default value of the struct.
840             /// <see cref="NextCallbacksSlot"/> - 1 defines the last filled slot.
841             /// </summary>
842             /// <remarks>
843             /// Initialized to an array with at least 1 slot because a partition is only ever created if we're about
844             /// to store something into it.  And initialized with at most 1 slot to help optimize the common case where
845             /// there's only ever a single registration in a CTS (that and many registrations are the most common cases).
846             /// </remarks>
847             public CallbackNode Callbacks;
848             public CallbackNode FreeNodeList;
849             /// <summary>
850             /// Every callback is assigned a unique, never-reused ID.  This defines the next available ID.
851             /// </summary>
852             public long NextAvailableId = 1; // avoid using 0, as that's the default long value and used to represent an empty slot
853
854             public CallbackPartition(CancellationTokenSource source)
855             {
856                 Debug.Assert(source != null, "Expected non-null source");
857                 Source = source;
858             }
859
860             internal bool Unregister(long id, CallbackNode node)
861             {
862                 Debug.Assert(id != 0, "Expected non-zero id");
863                 Debug.Assert(node != null, "Expected non-null node");
864
865                 bool lockTaken = false;
866                 Lock.Enter(ref lockTaken);
867                 try
868                 {
869                     if (Callbacks == null || node.Id != id)
870                     {
871                         // Cancellation was already requested or the callback was already disposed.
872                         // Even though we have the node itself, it's important to check Callbacks
873                         // in order to synchronize with callback execution.
874                         return false;
875                     }
876
877                     // Remove the registration from the list.
878                     if (node.Prev != null) node.Prev.Next = node.Next;
879                     if (node.Next != null) node.Next.Prev = node.Prev;
880                     if (Callbacks == node) Callbacks = node.Next;
881
882                     // Clear it out and put it on the free list
883                     node.Clear();
884                     node.Prev = null;
885                     node.Next = FreeNodeList;
886                     FreeNodeList = node;
887
888                     return true;
889                 }
890                 finally
891                 {
892                     Lock.Exit(useMemoryBarrier: false); // no check on lockTaken needed without thread aborts
893                 }
894             }
895         }
896
897         /// <summary>All of the state associated a registered callback, in a node that's part of a linked list of registered callbacks.</summary>
898         internal sealed class CallbackNode
899         {
900             public readonly CallbackPartition Partition;
901             public CallbackNode Prev;
902             public CallbackNode Next;
903
904             public long Id;
905             public Action<object> Callback;
906             public object CallbackState;
907             public ExecutionContext ExecutionContext;
908             public SynchronizationContext SynchronizationContext;
909             
910             public CallbackNode(CallbackPartition partition)
911             {
912                 Debug.Assert(partition != null, "Expected non-null partition");
913                 Partition = partition;
914             }
915
916             public void Clear()
917             {
918                 Id = 0;
919                 Callback = null;
920                 CallbackState = null;
921                 ExecutionContext = null;
922                 SynchronizationContext = null;
923             }
924
925             public void ExecuteCallback()
926             {
927                 if (ExecutionContext != null)
928                 {
929                     ExecutionContext.Run(ExecutionContext, s =>
930                     {
931                         CallbackNode n = (CallbackNode)s;
932                         n.Callback(n.CallbackState);
933                     }, this);
934                 }
935                 else
936                 {
937                     Callback(CallbackState);
938                 }
939             }
940         }
941     }
942 }