CancellationTokenSource.cs 52 KB

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