c53db2050947092e40a6cde8484a7db02cf8446c
[mono.git] / mcs / class / referencesource / mscorlib / system / threading / CancellationTokenSource.cs
1 #pragma warning disable 0420
2 // ==++==
3 // 
4 //   Copyright (c) Microsoft Corporation.  All rights reserved.
5 // 
6 // ==--==
7 //
8 // <OWNER>Microsoft</OWNER>
9 ////////////////////////////////////////////////////////////////////////////////
10
11 using System;
12 using System.Security;
13 using System.Collections.Generic;
14 using System.Runtime.InteropServices;
15 using System.Security.Permissions;
16 using System.Diagnostics.Contracts;
17 using System.Runtime;
18
19 namespace System.Threading
20 {
21     /// <summary>
22     /// Signals to a <see cref="System.Threading.CancellationToken"/> that it should be canceled.
23     /// </summary>
24     /// <remarks>
25     /// <para>
26     /// <see cref="T:System.Threading.CancellationTokenSource"/> is used to instantiate a <see
27     /// cref="T:System.Threading.CancellationToken"/>
28     /// (via the source's <see cref="System.Threading.CancellationTokenSource.Token">Token</see> property)
29     /// that can be handed to operations that wish to be notified of cancellation or that can be used to
30     /// register asynchronous operations for cancellation. That token may have cancellation requested by
31     /// calling to the source's <see cref="System.Threading.CancellationTokenSource.Cancel()">Cancel</see>
32     /// method.
33     /// </para>
34     /// <para>
35     /// All members of this class, except <see cref="Dispose">Dispose</see>, are thread-safe and may be used
36     /// concurrently from multiple threads.
37     /// </para>
38     /// </remarks>
39     [ComVisible(false)]
40     [HostProtection(Synchronization = true, ExternalThreading = true)]
41
42     public class CancellationTokenSource : IDisposable
43     {
44         //static sources that can be used as the backing source for 'fixed' CancellationTokens that never change state.
45         private static readonly CancellationTokenSource _staticSource_Set = new CancellationTokenSource(true);
46         private static readonly CancellationTokenSource _staticSource_NotCancelable = new CancellationTokenSource(false);
47
48         //Note: the callback lists array is only created on first registration.
49         //      the actual callback lists are only created on demand.
50         //      Storing a registered callback costs around >60bytes, hence some overhead for the lists array is OK
51         // At most 24 lists seems reasonable, and caps the cost of the listsArray to 96bytes(32-bit,24-way) or 192bytes(64-bit,24-way).
52         private static readonly int s_nLists = (PlatformHelper.ProcessorCount > 24) ? 24 : PlatformHelper.ProcessorCount; 
53
54         private volatile ManualResetEvent m_kernelEvent; //lazily initialized if required.
55
56         private volatile SparselyPopulatedArray<CancellationCallbackInfo>[] m_registeredCallbacksLists;
57  
58         // legal values for m_state
59         private const int CANNOT_BE_CANCELED = 0;
60         private const int NOT_CANCELED = 1;
61         private const int NOTIFYING = 2;
62         private const int NOTIFYINGCOMPLETE = 3;
63         
64         //m_state uses the pattern "volatile int32 reads, with cmpxch writes" which is safe for updates and cannot suffer torn reads.
65         private volatile int m_state;
66
67
68         /// The ID of the thread currently executing the main body of CTS.Cancel()
69         /// this helps us to know if a call to ctr.Dispose() is running 'within' a cancellation callback.
70         /// This is updated as we move between the main thread calling cts.Cancel() and any syncContexts that are used to 
71         /// actually run the callbacks.
72         private volatile int m_threadIDExecutingCallbacks = -1;
73
74         private bool m_disposed;
75
76         private CancellationTokenRegistration [] m_linkingRegistrations; //lazily initialized if required.
77         
78         private static readonly Action<object> s_LinkedTokenCancelDelegate = new Action<object>(LinkedTokenCancelDelegate);
79         
80         // we track the running callback to assist ctr.Dispose() to wait for the target callback to complete.
81         private volatile CancellationCallbackInfo m_executingCallback;
82
83         // provided for CancelAfter and timer-related constructors
84         private volatile Timer m_timer;
85
86         private static void LinkedTokenCancelDelegate(object source)
87         {
88             CancellationTokenSource cts = source as CancellationTokenSource;
89             Contract.Assert(source != null);
90             cts.Cancel();
91         }
92         
93         // ---------------------- 
94         // ** public properties
95
96         /// <summary>
97         /// Gets whether cancellation has been requested for this <see
98         /// cref="System.Threading.CancellationTokenSource">CancellationTokenSource</see>.
99         /// </summary>
100         /// <value>Whether cancellation has been requested for this <see
101         /// cref="System.Threading.CancellationTokenSource">CancellationTokenSource</see>.</value>
102         /// <remarks>
103         /// <para>
104         /// This property indicates whether cancellation has been requested for this token source, such as
105         /// due to a call to its
106         /// <see cref="System.Threading.CancellationTokenSource.Cancel()">Cancel</see> method.
107         /// </para>
108         /// <para>
109         /// If this property returns true, it only guarantees that cancellation has been requested. It does not
110         /// guarantee that every handler registered with the corresponding token has finished executing, nor
111         /// that cancellation requests have finished propagating to all registered handlers. Additional
112         /// synchronization may be required, particularly in situations where related objects are being
113         /// canceled concurrently.
114         /// </para>
115         /// </remarks>
116         public bool IsCancellationRequested
117         {
118             get { return m_state >= NOTIFYING; }
119         }
120
121         /// <summary>
122         /// A simple helper to determine whether cancellation has finished.
123         /// </summary>
124         internal bool IsCancellationCompleted
125         {
126             get { return m_state == NOTIFYINGCOMPLETE; }
127         }
128
129         /// <summary>
130         /// A simple helper to determine whether disposal has occured.
131         /// </summary>
132         internal bool IsDisposed
133         {
134             get { return m_disposed; }
135         }
136
137         /// <summary>
138         /// The ID of the thread that is running callbacks.
139         /// </summary>
140         internal int ThreadIDExecutingCallbacks
141         {
142             set { m_threadIDExecutingCallbacks = value; }
143             get { return m_threadIDExecutingCallbacks; }
144         }
145
146         /// <summary>
147         /// Gets the <see cref="System.Threading.CancellationToken">CancellationToken</see>
148         /// associated with this <see cref="CancellationTokenSource"/>.
149         /// </summary>
150         /// <value>The <see cref="System.Threading.CancellationToken">CancellationToken</see>
151         /// associated with this <see cref="CancellationTokenSource"/>.</value>
152         /// <exception cref="T:System.ObjectDisposedException">The token source has been
153         /// disposed.</exception>
154         public CancellationToken Token
155         {
156             get
157             {
158                 ThrowIfDisposed();
159                 return new CancellationToken(this);
160             }
161         }
162
163         // ---------------------- 
164         // ** internal and private properties.
165
166         /// <summary>
167         ///
168         /// </summary>
169         internal bool CanBeCanceled
170         {
171             get { return m_state != CANNOT_BE_CANCELED; }
172         }
173
174         /// <summary>
175         ///
176         /// </summary>
177         internal WaitHandle WaitHandle
178         {
179             get
180             {
181                 ThrowIfDisposed();
182
183                 // fast path if already allocated.
184                 if (m_kernelEvent != null)
185                     return m_kernelEvent;
186                 
187                 // lazy-init the mre.
188                 ManualResetEvent mre = new ManualResetEvent(false);
189                 if (Interlocked.CompareExchange(ref m_kernelEvent, mre, null) != null)
190                 {    
191                     ((IDisposable)mre).Dispose();
192                 }
193
194                 // There is a ---- between checking IsCancellationRequested and setting the event.
195                 // However, at this point, the kernel object definitely exists and the cases are:
196                 //   1. if IsCancellationRequested = true, then we will call Set()
197                 //   2. if IsCancellationRequested = false, then NotifyCancellation will see that the event exists, and will call Set().
198                 if (IsCancellationRequested)
199                     m_kernelEvent.Set();
200
201                 return m_kernelEvent;
202             }
203         }
204
205
206         /// <summary>
207         /// The currently executing callback
208         /// </summary>
209         internal CancellationCallbackInfo ExecutingCallback
210         {
211             get { return m_executingCallback; }
212         }
213
214 #if DEBUG
215         /// <summary>
216         /// Used by the dev unit tests to check the number of outstanding registrations.
217         /// They use private reflection to gain access.  Because this would be dead retail
218         /// code, however, it is ifdef'd out to work only in debug builds.
219         /// </summary>
220         private int CallbackCount
221         {
222             get
223             {
224                 SparselyPopulatedArray<CancellationCallbackInfo>[] callbackLists = m_registeredCallbacksLists;
225                 if (callbackLists == null)
226                     return 0;
227
228                 int count = 0;
229                 foreach(SparselyPopulatedArray<CancellationCallbackInfo> sparseArray in callbackLists)
230                 {
231                     if(sparseArray != null)
232                     {
233                         SparselyPopulatedArrayFragment<CancellationCallbackInfo> currCallbacks = sparseArray.Head;
234                         while (currCallbacks != null)
235                         {
236                             for (int i = 0; i < currCallbacks.Length; i++)
237                                 if (currCallbacks[i] != null)
238                                     count++;
239
240                             currCallbacks = currCallbacks.Next;
241                         }
242                     }
243                 }
244                 return count;
245             }
246         }
247 #endif
248
249         // ** Public Constructors
250
251         /// <summary>
252         /// Initializes the <see cref="T:System.Threading.CancellationTokenSource"/>.
253         /// </summary>
254         public CancellationTokenSource()
255         {
256             m_state = NOT_CANCELED;
257         }
258
259         // ** Private constructors for static sources.
260         // set=false ==> cannot be canceled.
261         // set=true  ==> is canceled. 
262         private CancellationTokenSource(bool set)
263         {
264             m_state = set ? NOTIFYINGCOMPLETE : CANNOT_BE_CANCELED;
265         }
266
267         /// <summary>
268         /// Constructs a <see cref="T:System.Threading.CancellationTokenSource"/> that will be canceled after a specified time span.
269         /// </summary>
270         /// <param name="delay">The time span to wait before canceling this <see cref="T:System.Threading.CancellationTokenSource"/></param>
271         /// <exception cref="T:System.ArgumentOutOfRangeException">
272         /// The exception that is thrown when <paramref name="delay"/> is less than -1 or greater than Int32.MaxValue.
273         /// </exception>
274         /// <remarks>
275         /// <para>
276         /// The countdown for the delay starts during the call to the constructor.  When the delay expires, 
277         /// the constructed <see cref="T:System.Threading.CancellationTokenSource"/> is canceled, if it has
278         /// not been canceled already.
279         /// </para>
280         /// <para>
281         /// Subsequent calls to CancelAfter will reset the delay for the constructed 
282         /// <see cref="T:System.Threading.CancellationTokenSource"/>, if it has not been
283         /// canceled already.
284         /// </para>
285         /// </remarks>
286         public CancellationTokenSource(TimeSpan delay)
287         {
288             long totalMilliseconds = (long)delay.TotalMilliseconds;
289             if (totalMilliseconds < -1 || totalMilliseconds > Int32.MaxValue)
290             {
291                 throw new ArgumentOutOfRangeException("delay");
292             }
293
294             InitializeWithTimer((int)totalMilliseconds);
295         }
296
297         /// <summary>
298         /// Constructs a <see cref="T:System.Threading.CancellationTokenSource"/> that will be canceled after a specified time span.
299         /// </summary>
300         /// <param name="millisecondsDelay">The time span to wait before canceling this <see cref="T:System.Threading.CancellationTokenSource"/></param>
301         /// <exception cref="T:System.ArgumentOutOfRangeException">
302         /// The exception that is thrown when <paramref name="millisecondsDelay"/> is less than -1.
303         /// </exception>
304         /// <remarks>
305         /// <para>
306         /// The countdown for the millisecondsDelay starts during the call to the constructor.  When the millisecondsDelay expires, 
307         /// the constructed <see cref="T:System.Threading.CancellationTokenSource"/> is canceled (if it has
308         /// not been canceled already).
309         /// </para>
310         /// <para>
311         /// Subsequent calls to CancelAfter will reset the millisecondsDelay for the constructed 
312         /// <see cref="T:System.Threading.CancellationTokenSource"/>, if it has not been
313         /// canceled already.
314         /// </para>
315         /// </remarks>
316         public CancellationTokenSource(Int32 millisecondsDelay)
317         {
318             if (millisecondsDelay < -1)
319             {
320                 throw new ArgumentOutOfRangeException("millisecondsDelay");
321             }
322
323             InitializeWithTimer(millisecondsDelay);
324         }
325
326         // Common initialization logic when constructing a CTS with a delay parameter
327         private void InitializeWithTimer(Int32 millisecondsDelay)
328         {
329             m_state = NOT_CANCELED;
330             m_timer = new Timer(s_timerCallback, this, millisecondsDelay, -1);
331         }
332
333         // ** Public Methods
334
335         /// <summary>
336         /// Communicates a request for cancellation.
337         /// </summary>
338         /// <remarks>
339         /// <para>
340         /// The associated <see cref="T:System.Threading.CancellationToken" /> will be
341         /// notified of the cancellation and will transition to a state where 
342         /// <see cref="System.Threading.CancellationToken.IsCancellationRequested">IsCancellationRequested</see> returns true. 
343         /// Any callbacks or cancelable operations
344         /// registered with the <see cref="T:System.Threading.CancellationToken"/>  will be executed.
345         /// </para>
346         /// <para>
347         /// Cancelable operations and callbacks registered with the token should not throw exceptions.
348         /// However, this overload of Cancel will aggregate any exceptions thrown into a <see cref="System.AggregateException"/>,
349         /// such that one callback throwing an exception will not prevent other registered callbacks from being executed.
350         /// </para>
351         /// <para>
352         /// The <see cref="T:System.Threading.ExecutionContext"/> that was captured when each callback was registered
353         /// will be reestablished when the callback is invoked.
354         /// </para>
355         /// </remarks>
356         /// <exception cref="T:System.AggregateException">An aggregate exception containing all the exceptions thrown
357         /// by the registered callbacks on the associated <see cref="T:System.Threading.CancellationToken"/>.</exception>
358         /// <exception cref="T:System.ObjectDisposedException">This <see
359         /// cref="T:System.Threading.CancellationTokenSource"/> has been disposed.</exception> 
360         public void Cancel()
361         {
362             Cancel(false);
363         }
364
365         /// <summary>
366         /// Communicates a request for cancellation.
367         /// </summary>
368         /// <remarks>
369         /// <para>
370         /// The associated <see cref="T:System.Threading.CancellationToken" /> will be
371         /// notified of the cancellation and will transition to a state where 
372         /// <see cref="System.Threading.CancellationToken.IsCancellationRequested">IsCancellationRequested</see> returns true. 
373         /// Any callbacks or cancelable operations
374         /// registered with the <see cref="T:System.Threading.CancellationToken"/>  will be executed.
375         /// </para>
376         /// <para>
377         /// Cancelable operations and callbacks registered with the token should not throw exceptions. 
378         /// If <paramref name="throwOnFirstException"/> is true, an exception will immediately propagate out of the
379         /// call to Cancel, preventing the remaining callbacks and cancelable operations from being processed.
380         /// If <paramref name="throwOnFirstException"/> is false, this overload will aggregate any 
381         /// exceptions thrown into a <see cref="System.AggregateException"/>,
382         /// such that one callback throwing an exception will not prevent other registered callbacks from being executed.
383         /// </para>
384         /// <para>
385         /// The <see cref="T:System.Threading.ExecutionContext"/> that was captured when each callback was registered
386         /// will be reestablished when the callback is invoked.
387         /// </para>
388         /// </remarks>
389         /// <param name="throwOnFirstException">Specifies whether exceptions should immediately propagate.</param>
390         /// <exception cref="T:System.AggregateException">An aggregate exception containing all the exceptions thrown
391         /// by the registered callbacks on the associated <see cref="T:System.Threading.CancellationToken"/>.</exception>
392         /// <exception cref="T:System.ObjectDisposedException">This <see
393         /// cref="T:System.Threading.CancellationTokenSource"/> has been disposed.</exception> 
394         public void Cancel(bool throwOnFirstException)
395         {
396             ThrowIfDisposed();
397             NotifyCancellation(throwOnFirstException);            
398         }
399
400         /// <summary>
401         /// Schedules a Cancel operation on this <see cref="T:System.Threading.CancellationTokenSource"/>.
402         /// </summary>
403         /// <param name="delay">The time span to wait before canceling this <see
404         /// cref="T:System.Threading.CancellationTokenSource"/>.
405         /// </param>
406         /// <exception cref="T:System.ObjectDisposedException">The exception thrown when this <see
407         /// cref="T:System.Threading.CancellationTokenSource"/> has been disposed.
408         /// </exception>
409         /// <exception cref="T:System.ArgumentOutOfRangeException">
410         /// The exception thrown when <paramref name="delay"/> is less than -1 or 
411         /// greater than Int32.MaxValue.
412         /// </exception>
413         /// <remarks>
414         /// <para>
415         /// The countdown for the delay starts during this call.  When the delay expires, 
416         /// this <see cref="T:System.Threading.CancellationTokenSource"/> is canceled, if it has
417         /// not been canceled already.
418         /// </para>
419         /// <para>
420         /// Subsequent calls to CancelAfter will reset the delay for this  
421         /// <see cref="T:System.Threading.CancellationTokenSource"/>, if it has not been
422         /// canceled already.
423         /// </para>
424         /// </remarks>
425         public void CancelAfter(TimeSpan delay)
426         {
427             long totalMilliseconds = (long)delay.TotalMilliseconds;
428             if (totalMilliseconds < -1 || totalMilliseconds > Int32.MaxValue)
429             {
430                 throw new ArgumentOutOfRangeException("delay");
431             }
432
433             CancelAfter((int)totalMilliseconds);
434         }
435
436         /// <summary>
437         /// Schedules a Cancel operation on this <see cref="T:System.Threading.CancellationTokenSource"/>.
438         /// </summary>
439         /// <param name="millisecondsDelay">The time span to wait before canceling this <see
440         /// cref="T:System.Threading.CancellationTokenSource"/>.
441         /// </param>
442         /// <exception cref="T:System.ObjectDisposedException">The exception thrown when this <see
443         /// cref="T:System.Threading.CancellationTokenSource"/> has been disposed.
444         /// </exception>
445         /// <exception cref="T:System.ArgumentOutOfRangeException">
446         /// The exception thrown when <paramref name="millisecondsDelay"/> is less than -1.
447         /// </exception>
448         /// <remarks>
449         /// <para>
450         /// The countdown for the millisecondsDelay starts during this call.  When the millisecondsDelay expires, 
451         /// this <see cref="T:System.Threading.CancellationTokenSource"/> is canceled, if it has
452         /// not been canceled already.
453         /// </para>
454         /// <para>
455         /// Subsequent calls to CancelAfter will reset the millisecondsDelay for this  
456         /// <see cref="T:System.Threading.CancellationTokenSource"/>, if it has not been
457         /// canceled already.
458         /// </para>
459         /// </remarks>
460         public void CancelAfter(Int32 millisecondsDelay)
461         {
462             ThrowIfDisposed();
463
464             if (millisecondsDelay < -1)
465             {
466                 throw new ArgumentOutOfRangeException("millisecondsDelay");
467             }
468
469             if (IsCancellationRequested) return;
470
471             // There is a race condition here as a Cancel could occur between the check of
472             // IsCancellationRequested and the creation of the timer.  This is benign; in the 
473             // worst case, a timer will be created that has no effect when it expires.
474
475             // Also, if Dispose() is called right here (after ThrowIfDisposed(), before timer
476             // creation), it would result in a leaked Timer object (at least until the timer
477             // expired and Disposed itself).  But this would be considered bad behavior, as
478             // Dispose() is not thread-safe and should not be called concurrently with CancelAfter().
479
480             if (m_timer == null)
481             {
482                 // Lazily initialize the timer in a thread-safe fashion.
483                 // Initially set to "never go off" because we don't want to take a
484                 // chance on a timer "losing" the initialization ---- and then
485                 // cancelling the token before it (the timer) can be disposed.
486                 Timer newTimer = new Timer(s_timerCallback, this, -1, -1);
487                 if (Interlocked.CompareExchange(ref m_timer, newTimer, null) != null)
488                 {
489                     // We lost the ---- to initialize the timer.  Dispose the new timer.
490                     newTimer.Dispose();
491                 }
492             }
493
494             
495             // It is possible that m_timer has already been disposed, so we must do
496             // the following in a try/catch block.
497             try
498             {
499                 m_timer.Change(millisecondsDelay, -1);
500             }
501             catch (ObjectDisposedException)
502             {
503                 // Just eat the exception.  There is no other way to tell that
504                 // the timer has been disposed, and even if there were, there
505                 // would not be a good way to deal with the observe/dispose
506                 // race condition.
507             }
508
509         }
510
511         private static readonly TimerCallback s_timerCallback = new TimerCallback(TimerCallbackLogic);
512
513         // Common logic for a timer delegate
514         private static void TimerCallbackLogic(object obj)
515         {
516             CancellationTokenSource cts = (CancellationTokenSource)obj;
517
518             // Cancel the source; handle a race condition with cts.Dispose()
519             if (!cts.IsDisposed)
520             {
521                 // There is a small window for a race condition where a cts.Dispose can sneak
522                 // in right here.  I'll wrap the cts.Cancel() in a try/catch to proof us
523                 // against this ----.
524                 try
525                 {
526                     cts.Cancel(); // will take care of disposing of m_timer
527                 }
528                 catch (ObjectDisposedException)
529                 {
530                     // If the ODE was not due to the target cts being disposed, then propagate the ODE.
531                     if (!cts.IsDisposed) throw;
532                 }
533             }
534         }
535
536         /// <summary>
537         /// Releases the resources used by this <see cref="T:System.Threading.CancellationTokenSource" />.
538         /// </summary>
539         /// <remarks>
540         /// This method is not thread-safe for any other concurrent calls.
541         /// </remarks>
542         public void Dispose()
543         {
544             Dispose(true);
545             GC.SuppressFinalize(this);
546         }
547
548         /// <summary>
549         /// Releases the unmanaged resources used by the <see cref="T:System.Threading.CancellationTokenSource" /> class and optionally releases the managed resources.
550         /// </summary>
551         /// <param name="disposing">true to release both managed and unmanaged resources; false to release only unmanaged resources.</param>
552         protected virtual void Dispose(bool disposing)
553         {
554             // There is nothing to do if disposing=false because the CancellationTokenSource holds no unmanaged resources.
555
556             if (disposing)
557             {
558                 //NOTE: We specifically tolerate that a callback can be deregistered
559                 //      after the CTS has been disposed and/or concurrently with cts.Dispose().
560                 //      This is safe without locks because the reg.Dispose() only
561                 //      mutates a sparseArrayFragment and then reads from properties of the CTS that are not
562                 //      invalidated by cts.Dispose().
563                 //     
564                 //      We also tolerate that a callback can be registered after the CTS has been
565                 //      disposed.  This is safe without locks because InternalRegister is tolerant
566                 //      of m_registeredCallbacksLists becoming null during its execution.  However,
567                 //      we run the acceptable risk of m_registeredCallbacksLists getting reinitialized
568                 //      to non-null if there is a ---- between Dispose and Register, in which case this
569                 //      instance may unnecessarily hold onto a registered callback.  But that's no worse
570                 //      than if Dispose wasn't safe to use concurrently, as Dispose would never be called,
571                 //      and thus no handlers would be dropped.
572
573                 if (m_disposed)
574                     return;
575
576                 if (m_timer != null) m_timer.Dispose();
577
578                 var linkingRegistrations = m_linkingRegistrations;
579                 if (linkingRegistrations != null)
580                 {
581                     m_linkingRegistrations = null; // free for GC once we're done enumerating
582                     for (int i = 0; i < linkingRegistrations.Length; i++)
583                     {
584                         linkingRegistrations[i].Dispose();
585                     }
586                 }
587
588                 // registered callbacks are now either complete or will never run, due to guarantees made by ctr.Dispose()
589                 // so we can now perform main disposal work without risk of linking callbacks trying to use this CTS.
590
591                 m_registeredCallbacksLists = null; // free for GC.
592
593                 if (m_kernelEvent != null)
594                 {
595                     m_kernelEvent.Close(); // the critical cleanup to release an OS handle
596                     m_kernelEvent = null; // free for GC.
597                 }
598
599                 m_disposed = true;
600             }
601         }
602
603         // -- Internal methods.
604
605         /// <summary>
606         /// Throws an exception if the source has been disposed.
607         /// </summary>
608         internal void ThrowIfDisposed()
609         {
610             if (m_disposed)
611                 ThrowObjectDisposedException();
612         }
613
614         // separation enables inlining of ThrowIfDisposed
615         private static void ThrowObjectDisposedException()
616         {
617             throw new ObjectDisposedException(null, Environment.GetResourceString("CancellationTokenSource_Disposed"));
618         }
619
620         /// <summary>
621         /// InternalGetStaticSource()
622         /// </summary>
623         /// <param name="set">Whether the source should be set.</param>
624         /// <returns>A static source to be shared among multiple tokens.</returns>
625         internal static CancellationTokenSource InternalGetStaticSource(bool set)
626         {
627             return set ? _staticSource_Set : _staticSource_NotCancelable;
628         }
629
630         /// <summary>
631         /// Registers a callback object. If cancellation has already occurred, the
632         /// callback will have been run by the time this method returns.
633         /// </summary>
634         internal CancellationTokenRegistration InternalRegister(
635             Action<object> callback, object stateForCallback, SynchronizationContext targetSyncContext, ExecutionContext executionContext)
636         {
637             if (AppContextSwitches.ThrowExceptionIfDisposedCancellationTokenSource)
638             {
639                 ThrowIfDisposed();
640             }
641
642             // the CancellationToken has already checked that the token is cancelable before calling this method.
643             Contract.Assert(CanBeCanceled, "Cannot register for uncancelable token src");
644
645             // if not canceled, register the event handlers
646             // if canceled already, run the callback synchronously
647             // Apart from the semantics of late-enlistment, this also ensures that during ExecuteCallbackHandlers() there
648             // will be no mutation of the _registeredCallbacks list
649
650             if (!IsCancellationRequested)
651             {
652                 // In order to enable code to not leak too many handlers, we allow Dispose to be called concurrently
653                 // with Register.  While this is not a recommended practice, consumers can and do use it this way.
654                 // We don't make any guarantees about whether the CTS will hold onto the supplied callback
655                 // if the CTS has already been disposed when the callback is registered, but we try not to
656                 // while at the same time not paying any non-negligible overhead.  The simple compromise
657                 // is to check whether we're disposed (not volatile), and if we see we are, to return an empty
658                 // registration, just as if CanBeCanceled was false for the check made in CancellationToken.Register.
659                 // If there's a ---- and m_disposed is false even though it's been disposed, or if the disposal request
660                 // comes in after this line, we simply run the minor risk of having m_registeredCallbacksLists reinitialized
661                 // (after it was cleared to null during Dispose).
662
663                 if (m_disposed && !AppContextSwitches.ThrowExceptionIfDisposedCancellationTokenSource)
664                     return new CancellationTokenRegistration();
665
666                 int myIndex = Thread.CurrentThread.ManagedThreadId % s_nLists;
667
668                 CancellationCallbackInfo callbackInfo = new CancellationCallbackInfo(callback, stateForCallback, targetSyncContext, executionContext, this);
669
670                 //allocate the callback list array
671                 var registeredCallbacksLists = m_registeredCallbacksLists;
672                 if (registeredCallbacksLists == null)
673                 {
674                     SparselyPopulatedArray<CancellationCallbackInfo>[] list = new SparselyPopulatedArray<CancellationCallbackInfo>[s_nLists];
675                     registeredCallbacksLists = Interlocked.CompareExchange(ref m_registeredCallbacksLists, list, null);
676                     if (registeredCallbacksLists == null) registeredCallbacksLists = list;
677                 }
678
679                 //allocate the actual lists on-demand to save mem in low-use situations, and to avoid false-sharing.
680                 var callbacks = Volatile.Read<SparselyPopulatedArray<CancellationCallbackInfo>>(ref registeredCallbacksLists[myIndex]);
681                 if (callbacks == null)
682                 {
683                     SparselyPopulatedArray<CancellationCallbackInfo> callBackArray = new SparselyPopulatedArray<CancellationCallbackInfo>(4);
684                     Interlocked.CompareExchange(ref (registeredCallbacksLists[myIndex]), callBackArray, null);
685                     callbacks = registeredCallbacksLists[myIndex];
686                 }
687
688                 // Now add the registration to the list.
689                 SparselyPopulatedArrayAddInfo<CancellationCallbackInfo> addInfo = callbacks.Add(callbackInfo);
690                 CancellationTokenRegistration registration = new CancellationTokenRegistration(callbackInfo, addInfo);
691
692                 if (!IsCancellationRequested)
693                     return registration;
694
695                 // If a cancellation has since come in, we will try to undo the registration and run the callback ourselves.
696                 // (this avoids leaving the callback orphaned)
697                 bool deregisterOccurred = registration.TryDeregister();
698
699                 if (!deregisterOccurred)
700                 {
701                     // The thread that is running Cancel() snagged our callback for execution.
702                     // So we don't need to run it, but we do return the registration so that 
703                     // ctr.Dispose() will wait for callback completion.
704                     return registration;
705                 }
706             }
707
708             // If cancellation already occurred, we run the callback on this thread and return an empty registration.
709             callback(stateForCallback);
710             return new CancellationTokenRegistration();
711         }
712
713         /// <summary>
714         /// 
715         /// </summary>
716         private void NotifyCancellation(bool throwOnFirstException)
717         {
718             // fast-path test to check if Notify has been called previously
719             if (IsCancellationRequested)
720                 return;
721
722             // If we're the first to signal cancellation, do the main extra work.
723             if (Interlocked.CompareExchange(ref m_state, NOTIFYING, NOT_CANCELED) == NOT_CANCELED)
724             {
725                 // Dispose of the timer, if any
726                 Timer timer = m_timer;
727                 if(timer != null) timer.Dispose();
728
729                 //record the threadID being used for running the callbacks.
730                 ThreadIDExecutingCallbacks = Thread.CurrentThread.ManagedThreadId;
731                 
732                 //If the kernel event is null at this point, it will be set during lazy construction.
733                 if (m_kernelEvent != null)
734                     m_kernelEvent.Set(); // update the MRE value.
735
736                 // - late enlisters to the Canceled event will have their callbacks called immediately in the Register() methods.
737                 // - Callbacks are not called inside a lock.
738                 // - After transition, no more delegates will be added to the 
739                 // - list of handlers, and hence it can be consumed and cleared at leisure by ExecuteCallbackHandlers.
740                 ExecuteCallbackHandlers(throwOnFirstException);
741                 Contract.Assert(IsCancellationCompleted, "Expected cancellation to have finished");
742             }
743         }
744
745         /// <summary>
746         /// Invoke the Canceled event.
747         /// </summary>
748         /// <remarks>
749         /// The handlers are invoked synchronously in LIFO order.
750         /// </remarks>
751         private void ExecuteCallbackHandlers(bool throwOnFirstException)
752         {
753             Contract.Assert(IsCancellationRequested, "ExecuteCallbackHandlers should only be called after setting IsCancellationRequested->true");
754             Contract.Assert(ThreadIDExecutingCallbacks != -1, "ThreadIDExecutingCallbacks should have been set.");
755
756             // Design decision: call the delegates in LIFO order so that callbacks fire 'deepest first'.
757             // This is intended to help with nesting scenarios so that child enlisters cancel before their parents.
758             List<Exception> exceptionList = null;
759             SparselyPopulatedArray<CancellationCallbackInfo>[] callbackLists = m_registeredCallbacksLists;
760
761             // If there are no callbacks to run, we can safely exit.  Any ----s to lazy initialize it
762             // will see IsCancellationRequested and will then run the callback themselves.
763             if (callbackLists == null)
764             {
765                 Interlocked.Exchange(ref m_state, NOTIFYINGCOMPLETE);
766                 return;
767             }
768             
769             try
770             {
771                 for (int index = 0; index < callbackLists.Length; index++)
772                 {
773                     SparselyPopulatedArray<CancellationCallbackInfo> list = Volatile.Read<SparselyPopulatedArray<CancellationCallbackInfo>>(ref callbackLists[index]);
774                     if (list != null)
775                     {
776                         SparselyPopulatedArrayFragment<CancellationCallbackInfo> currArrayFragment = list.Tail;
777
778                         while (currArrayFragment != null)
779                         {
780                             for (int i = currArrayFragment.Length - 1; i >= 0; i--)
781                             {
782                                 // 1a. publish the indended callback, to ensure ctr.Dipose can tell if a wait is necessary.
783                                 // 1b. transition to the target syncContext and continue there..
784                                 //  On the target SyncContext.
785                                 //   2. actually remove the callback
786                                 //   3. execute the callback
787                                 // re:#2 we do the remove on the syncCtx so that we can be sure we have control of the syncCtx before
788                                 //        grabbing the callback.  This prevents a deadlock if ctr.Dispose() might run on the syncCtx too.
789                                 m_executingCallback = currArrayFragment[i];
790                                 if (m_executingCallback != null)
791                                 {
792                                     //Transition to the target sync context (if necessary), and continue our work there.
793                                     CancellationCallbackCoreWorkArguments args = new CancellationCallbackCoreWorkArguments(currArrayFragment, i);
794
795                                     // marshal exceptions: either aggregate or perform an immediate rethrow
796                                     // We assume that syncCtx.Send() has forwarded on user exceptions when appropriate.
797                                     try
798                                     {
799                                         if (m_executingCallback.TargetSyncContext != null)
800                                         {
801
802                                             m_executingCallback.TargetSyncContext.Send(CancellationCallbackCoreWork_OnSyncContext, args);
803                                             // CancellationCallbackCoreWork_OnSyncContext may have altered ThreadIDExecutingCallbacks, so reset it. 
804                                             ThreadIDExecutingCallbacks = Thread.CurrentThread.ManagedThreadId;
805                                         }
806                                         else
807                                         {
808                                             CancellationCallbackCoreWork(args);
809                                         }
810                                     }
811                                     catch(Exception ex)
812                                     {
813                                         if (throwOnFirstException)
814                                             throw;
815     
816                                         // Otherwise, log it and proceed.
817                                         if(exceptionList == null)
818                                             exceptionList = new List<Exception>();
819                                         exceptionList.Add(ex);
820                                     }
821                                 }
822                             }
823
824                             currArrayFragment = currArrayFragment.Prev;
825                         }
826                     }
827                 }
828             }
829             finally
830             {
831                 m_state = NOTIFYINGCOMPLETE;
832                 m_executingCallback = null;
833                 Thread.MemoryBarrier(); // for safety, prevent reorderings crossing this point and seeing inconsistent state.
834             }
835
836             if (exceptionList != null)
837             {
838                 Contract.Assert(exceptionList.Count > 0, "Expected exception count > 0");
839                 throw new AggregateException(exceptionList);
840             }
841         }
842
843         // The main callback work that executes on the target synchronization context
844         private void CancellationCallbackCoreWork_OnSyncContext(object obj)
845         {
846             CancellationCallbackCoreWork((CancellationCallbackCoreWorkArguments)obj);
847         }
848
849         private void CancellationCallbackCoreWork(CancellationCallbackCoreWorkArguments args)
850         {
851             // remove the intended callback..and ensure that it worked.
852             // otherwise the callback has disappeared in the interim and we can immediately return.
853             CancellationCallbackInfo callback = args.m_currArrayFragment.SafeAtomicRemove(args.m_currArrayIndex, m_executingCallback);
854             if (callback == m_executingCallback)
855             {
856                 if (callback.TargetExecutionContext != null)
857                 {
858                     // we are running via a custom sync context, so update the executing threadID
859                     callback.CancellationTokenSource.ThreadIDExecutingCallbacks = Thread.CurrentThread.ManagedThreadId;
860                 }
861                 callback.ExecuteCallback();
862             }
863         }
864
865
866         /// <summary>
867         /// Creates a <see cref="T:System.Threading.CancellationTokenSource">CancellationTokenSource</see> that will be in the canceled state
868         /// when any of the source tokens are in the canceled state.
869         /// </summary>
870         /// <param name="token1">The first <see cref="T:System.Threading.CancellationToken">CancellationToken</see> to observe.</param>
871         /// <param name="token2">The second <see cref="T:System.Threading.CancellationToken">CancellationToken</see> to observe.</param>
872         /// <returns>A <see cref="T:System.Threading.CancellationTokenSource">CancellationTokenSource</see> that is linked 
873         /// to the source tokens.</returns>
874         public static CancellationTokenSource CreateLinkedTokenSource(CancellationToken token1, CancellationToken token2)
875         {
876             CancellationTokenSource linkedTokenSource = new CancellationTokenSource();
877
878             bool token2CanBeCanceled = token2.CanBeCanceled;
879
880             if( token1.CanBeCanceled )
881             {
882                 linkedTokenSource.m_linkingRegistrations = new CancellationTokenRegistration[token2CanBeCanceled ? 2 : 1]; // there will be at least 1 and at most 2 linkings
883                 linkedTokenSource.m_linkingRegistrations[0] = token1.InternalRegisterWithoutEC(s_LinkedTokenCancelDelegate, linkedTokenSource);
884             }
885             
886             if( token2CanBeCanceled )
887             {
888                 int index = 1;
889                 if( linkedTokenSource.m_linkingRegistrations == null )
890                 {
891                     linkedTokenSource.m_linkingRegistrations = new CancellationTokenRegistration[1]; // this will be the only linking
892                     index = 0;
893                 }
894                 linkedTokenSource.m_linkingRegistrations[index] = token2.InternalRegisterWithoutEC(s_LinkedTokenCancelDelegate, linkedTokenSource);
895             }
896             
897             return linkedTokenSource;
898         }
899
900         /// <summary>
901         /// Creates a <see cref="T:System.Threading.CancellationTokenSource">CancellationTokenSource</see> that will be in the canceled state
902         /// when any of the source tokens are in the canceled state.
903         /// </summary>
904         /// <param name="tokens">The <see cref="T:System.Threading.CancellationToken">CancellationToken</see> instances to observe.</param>
905         /// <returns>A <see cref="T:System.Threading.CancellationTokenSource">CancellationTokenSource</see> that is linked 
906         /// to the source tokens.</returns>
907         /// <exception cref="T:System.ArgumentNullException"><paramref name="tokens"/> is null.</exception>
908         public static CancellationTokenSource CreateLinkedTokenSource(params CancellationToken[] tokens)
909         {
910             if (tokens == null)
911                 throw new ArgumentNullException("tokens");
912
913             if (tokens.Length == 0)
914                 throw new ArgumentException(Environment.GetResourceString("CancellationToken_CreateLinkedToken_TokensIsEmpty"));
915
916             // a defensive copy is not required as the array has value-items that have only a single IntPtr field,
917             // hence each item cannot be null itself, and reads of the payloads cannot be torn.
918             Contract.EndContractBlock();
919
920             CancellationTokenSource linkedTokenSource = new CancellationTokenSource();
921             linkedTokenSource.m_linkingRegistrations = new CancellationTokenRegistration[tokens.Length];
922
923             for (int i = 0; i < tokens.Length; i++)
924             {
925                 if (tokens[i].CanBeCanceled)
926                 {
927                     linkedTokenSource.m_linkingRegistrations[i] = tokens[i].InternalRegisterWithoutEC(s_LinkedTokenCancelDelegate, linkedTokenSource);
928                 }
929                 // Empty slots in the array will be default(CancellationTokenRegistration), which are nops to Dispose.
930                 // Based on usage patterns, such occurrences should also be rare, such that it's not worth resizing
931                 // the array and incurring the related costs.
932             }
933
934             return linkedTokenSource;
935         }
936
937
938         // Wait for a single callback to complete (or, more specifically, to not be running).
939         // It is ok to call this method if the callback has already finished.
940         // Calling this method before the target callback has been selected for execution would be an error.
941         internal void WaitForCallbackToComplete(CancellationCallbackInfo callbackInfo)
942         {
943             SpinWait sw = new SpinWait();
944             while (ExecutingCallback == callbackInfo)
945             {
946                 sw.SpinOnce();  //spin as we assume callback execution is fast and that this situation is rare.
947             }
948         }
949     }
950
951     // ----------------------------------------------------------
952     // -- CancellationCallbackCoreWorkArguments --
953     // ----------------------------------------------------------
954     // Helper struct for passing data to the target sync context
955     internal struct CancellationCallbackCoreWorkArguments
956     {
957         internal SparselyPopulatedArrayFragment<CancellationCallbackInfo> m_currArrayFragment;
958         internal int m_currArrayIndex;
959         
960         public CancellationCallbackCoreWorkArguments(SparselyPopulatedArrayFragment<CancellationCallbackInfo> currArrayFragment, int currArrayIndex)
961         {
962             m_currArrayFragment = currArrayFragment;
963             m_currArrayIndex = currArrayIndex;
964         }
965     }
966
967     // ----------------------------------------------------------
968     // -- CancellationCallbackInfo --
969     // ----------------------------------------------------------
970
971     /// <summary>
972     /// A helper class for collating the various bits of information required to execute 
973     /// cancellation callbacks.
974     /// </summary>
975     internal class CancellationCallbackInfo
976     {
977         internal readonly Action<object> Callback;
978         internal readonly object StateForCallback;
979         internal readonly SynchronizationContext TargetSyncContext;
980         internal readonly ExecutionContext TargetExecutionContext;
981         internal readonly CancellationTokenSource CancellationTokenSource;
982
983         internal CancellationCallbackInfo(
984             Action<object> callback, object stateForCallback, SynchronizationContext targetSyncContext, ExecutionContext targetExecutionContext,
985             CancellationTokenSource cancellationTokenSource)
986         {
987             Callback = callback;
988             StateForCallback = stateForCallback;
989             TargetSyncContext = targetSyncContext;
990             TargetExecutionContext = targetExecutionContext;
991             CancellationTokenSource = cancellationTokenSource;
992         }
993
994         // Cached callback delegate that's lazily initialized due to ContextCallback being SecurityCritical
995         [SecurityCritical]
996         private static ContextCallback s_executionContextCallback;
997
998         /// <summary>
999         /// InternalExecuteCallbackSynchronously_GeneralPath
1000         /// This will be called on the target synchronization context, however, we still need to restore the required execution context
1001         /// </summary>
1002         [SecuritySafeCritical]
1003         internal void ExecuteCallback()
1004         {
1005             if (TargetExecutionContext != null)
1006             {
1007                 // Lazily initialize the callback delegate; benign ----
1008                 var callback = s_executionContextCallback;
1009                 if (callback == null) s_executionContextCallback = callback = new ContextCallback(ExecutionContextCallback);
1010                 
1011                 ExecutionContext.Run(
1012                     TargetExecutionContext,
1013                     callback,
1014                     this);
1015             }
1016             else
1017             {
1018                 //otherwise run directly
1019                 ExecutionContextCallback(this);
1020             }
1021         }
1022
1023         // the worker method to actually run the callback
1024         // The signature is such that it can be used as a 'ContextCallback'
1025         [SecurityCritical]
1026         private static void ExecutionContextCallback(object obj)
1027         {
1028             CancellationCallbackInfo callbackInfo = obj as CancellationCallbackInfo;
1029             Contract.Assert(callbackInfo != null);
1030             callbackInfo.Callback(callbackInfo.StateForCallback);
1031         }
1032     }
1033
1034
1035     // ----------------------------------------------------------
1036     // -- SparselyPopulatedArray --
1037     // ----------------------------------------------------------
1038
1039     /// <summary>
1040     /// A sparsely populated array.  Elements can be sparse and some null, but this allows for
1041     /// lock-free additions and growth, and also for constant time removal (by nulling out).
1042     /// </summary>
1043     /// <typeparam name="T">The kind of elements contained within.</typeparam>
1044     internal class SparselyPopulatedArray<T> where T : class
1045     {
1046         private readonly SparselyPopulatedArrayFragment<T> m_head;
1047         private volatile SparselyPopulatedArrayFragment<T> m_tail;
1048
1049         /// <summary>
1050         /// Allocates a new array with the given initial size.
1051         /// </summary>
1052         /// <param name="initialSize">How many array slots to pre-allocate.</param>
1053         internal SparselyPopulatedArray(int initialSize)
1054         {
1055             m_head = m_tail = new SparselyPopulatedArrayFragment<T>(initialSize);
1056         }
1057
1058 #if DEBUG
1059         // Used in DEBUG mode by CancellationTokenSource.CallbackCount
1060         /// <summary>
1061         /// The head of the doubly linked list.
1062         /// </summary>
1063         internal SparselyPopulatedArrayFragment<T> Head
1064         {
1065             get { return m_head; }
1066         }
1067 #endif
1068
1069         /// <summary>
1070         /// The tail of the doubly linked list.
1071         /// </summary>
1072         internal SparselyPopulatedArrayFragment<T> Tail
1073         {
1074             get { return m_tail; }
1075         }
1076
1077         /// <summary>
1078         /// Adds an element in the first available slot, beginning the search from the tail-to-head.
1079         /// If no slots are available, the array is grown.  The method doesn't return until successful.
1080         /// </summary>
1081         /// <param name="element">The element to add.</param>
1082         /// <returns>Information about where the add happened, to enable O(1) deregistration.</returns>
1083         internal SparselyPopulatedArrayAddInfo<T> Add(T element)
1084         {
1085             while (true)
1086             {
1087                 // Get the tail, and ensure it's up to date.
1088                 SparselyPopulatedArrayFragment<T> tail = m_tail;
1089                 while (tail.m_next != null)
1090                     m_tail = (tail = tail.m_next);
1091
1092                 // Search for a free index, starting from the tail.
1093                 SparselyPopulatedArrayFragment<T> curr = tail;
1094                 while (curr != null)
1095                 {
1096                     const int RE_SEARCH_THRESHOLD = -10; // Every 10 skips, force a search.
1097                     if (curr.m_freeCount < 1)
1098                         --curr.m_freeCount;
1099
1100                     if (curr.m_freeCount > 0 || curr.m_freeCount < RE_SEARCH_THRESHOLD)
1101                     {
1102                         int c = curr.Length;
1103
1104                         // We'll compute a start offset based on how many free slots we think there
1105                         // are.  This optimizes for ordinary the LIFO deregistration pattern, and is
1106                         // far from perfect due to the non-threadsafe ++ and -- of the free counter.
1107                         int start = ((c - curr.m_freeCount) % c);
1108                         if (start < 0)
1109                         {
1110                             start = 0;
1111                             curr.m_freeCount--; // Too many free elements; fix up.
1112                         }
1113                         Contract.Assert(start >= 0 && start < c, "start is outside of bounds");
1114
1115                         // Now walk the array until we find a free slot (or reach the end).
1116                         for (int i = 0; i < c; i++)
1117                         {
1118                             // If the slot is null, try to CAS our element into it.
1119                             int tryIndex = (start + i) % c;
1120                             Contract.Assert(tryIndex >= 0 && tryIndex < curr.m_elements.Length, "tryIndex is outside of bounds");
1121                             
1122                             if (curr.m_elements[tryIndex] == null && Interlocked.CompareExchange(ref curr.m_elements[tryIndex], element, null) == null)
1123                             {
1124                                 // We adjust the free count by --. Note: if this drops to 0, we will skip
1125                                 // the fragment on the next search iteration.  Searching threads will -- the
1126                                 // count and force a search every so often, just in case fragmentation occurs.
1127                                 int newFreeCount = curr.m_freeCount - 1;
1128                                 curr.m_freeCount = newFreeCount > 0 ? newFreeCount : 0;
1129                                 return new SparselyPopulatedArrayAddInfo<T>(curr, tryIndex);
1130                             }
1131                         }
1132                     }
1133
1134                     curr = curr.m_prev;
1135                 }
1136
1137                 // If we got here, we need to add a new chunk to the tail and try again.
1138                 SparselyPopulatedArrayFragment<T> newTail = new SparselyPopulatedArrayFragment<T>(
1139                     tail.m_elements.Length == 4096 ? 4096 : tail.m_elements.Length * 2, tail);
1140                 if (Interlocked.CompareExchange(ref tail.m_next, newTail, null) == null)
1141                 {
1142                     m_tail = newTail;
1143                 }
1144             }
1145         }
1146     }
1147
1148     /// <summary>
1149     /// A struct to hold a link to the exact spot in an array an element was inserted, enabling
1150     /// constant time removal later on.
1151     /// </summary>
1152     internal struct SparselyPopulatedArrayAddInfo<T> where T : class
1153     {
1154         private SparselyPopulatedArrayFragment<T> m_source;
1155         private int m_index;
1156
1157         internal SparselyPopulatedArrayAddInfo(SparselyPopulatedArrayFragment<T> source, int index)
1158         {
1159             Contract.Assert(source != null);
1160             Contract.Assert(index >= 0 && index < source.Length);
1161             m_source = source;
1162             m_index = index;
1163         }
1164
1165         internal SparselyPopulatedArrayFragment<T> Source
1166         {
1167             get { return m_source; }
1168         }
1169
1170         internal int Index
1171         {
1172             get { return m_index; }
1173         }
1174     }
1175
1176     /// <summary>
1177     /// A fragment of a sparsely populated array, doubly linked.
1178     /// </summary>
1179     /// <typeparam name="T">The kind of elements contained within.</typeparam>
1180     internal class SparselyPopulatedArrayFragment<T> where T : class
1181     {
1182         internal readonly T[] m_elements; // The contents, sparsely populated (with nulls).
1183         internal volatile int m_freeCount; // A hint of the number of free elements.
1184         internal volatile SparselyPopulatedArrayFragment<T> m_next; // The next fragment in the chain.
1185         internal volatile SparselyPopulatedArrayFragment<T> m_prev; // The previous fragment in the chain.
1186
1187         internal SparselyPopulatedArrayFragment(int size) : this(size, null)
1188         {
1189         }
1190
1191         internal SparselyPopulatedArrayFragment(int size, SparselyPopulatedArrayFragment<T> prev)
1192         {
1193             m_elements = new T[size];
1194             m_freeCount = size;
1195             m_prev = prev;
1196         }
1197
1198         internal T this[int index]
1199         {
1200             get { return Volatile.Read<T>(ref m_elements[index]); }
1201         }
1202
1203         internal int Length
1204         {
1205             get { return m_elements.Length; }
1206         }
1207
1208 #if DEBUG
1209         // Used in DEBUG mode by CancellationTokenSource.CallbackCount
1210         internal SparselyPopulatedArrayFragment<T> Next
1211         {
1212             get { return m_next; }
1213         }
1214 #endif
1215         internal SparselyPopulatedArrayFragment<T> Prev
1216         {
1217             get { return m_prev; }
1218         }
1219
1220         // only removes the item at the specified index if it is still the expected one.
1221         // Returns the prevailing value.
1222         // The remove occured successfully if the return value == expected element
1223         // otherwise the remove did not occur.
1224         internal T SafeAtomicRemove(int index, T expectedElement)
1225         {
1226             T prevailingValue = Interlocked.CompareExchange(ref m_elements[index], null, expectedElement);
1227             if (prevailingValue != null) 
1228                 ++m_freeCount;
1229             return prevailingValue;
1230         }
1231     }
1232 }