ThreadLocal.cs 33 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815
  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.Diagnostics.CodeAnalysis;
  7. // A class that provides a simple, lightweight implementation of thread-local lazy-initialization, where a value is initialized once per accessing
  8. // thread; this provides an alternative to using a ThreadStatic static variable and having
  9. // to check the variable prior to every access to see if it's been initialized.
  10. namespace System.Threading
  11. {
  12. /// <summary>
  13. /// Provides thread-local storage of data.
  14. /// </summary>
  15. /// <typeparam name="T">Specifies the type of data stored per-thread.</typeparam>
  16. /// <remarks>
  17. /// <para>
  18. /// With the exception of <see cref="Dispose()"/>, all public and protected members of
  19. /// <see cref="ThreadLocal{T}"/> are thread-safe and may be used
  20. /// concurrently from multiple threads.
  21. /// </para>
  22. /// </remarks>
  23. [DebuggerTypeProxy(typeof(SystemThreading_ThreadLocalDebugView<>))]
  24. [DebuggerDisplay("IsValueCreated={IsValueCreated}, Value={ValueForDebugDisplay}, Count={ValuesCountForDebugDisplay}")]
  25. public class ThreadLocal<T> : IDisposable
  26. {
  27. // a delegate that returns the created value, if null the created value will be default(T)
  28. private Func<T>? _valueFactory;
  29. // ts_slotArray is a table of thread-local values for all ThreadLocal<T> instances
  30. //
  31. // So, when a thread reads ts_slotArray, it gets back an array of *all* ThreadLocal<T> values for this thread and this T.
  32. // The slot relevant to this particular ThreadLocal<T> instance is determined by the _idComplement instance field stored in
  33. // the ThreadLocal<T> instance.
  34. [ThreadStatic]
  35. private static LinkedSlotVolatile[]? ts_slotArray;
  36. [ThreadStatic]
  37. private static FinalizationHelper? ts_finalizationHelper;
  38. // Slot ID of this ThreadLocal<> instance. We store a bitwise complement of the ID (that is ~ID), which allows us to distinguish
  39. // between the case when ID is 0 and an incompletely initialized object, either due to a thread abort in the constructor, or
  40. // possibly due to a memory model issue in user code.
  41. private int _idComplement;
  42. // This field is set to true when the constructor completes. That is helpful for recognizing whether a constructor
  43. // threw an exception - either due to invalid argument or due to a thread abort. Finally, the field is set to false
  44. // when the instance is disposed.
  45. private volatile bool _initialized;
  46. // IdManager assigns and reuses slot IDs. Additionally, the object is also used as a global lock.
  47. private static readonly IdManager s_idManager = new IdManager();
  48. // A linked list of all values associated with this ThreadLocal<T> instance.
  49. // We create a dummy head node. That allows us to remove any (non-dummy) node without having to locate the m_linkedSlot field.
  50. private LinkedSlot? _linkedSlot = new LinkedSlot(null);
  51. // Whether the Values property is supported
  52. private bool _trackAllValues;
  53. /// <summary>
  54. /// Initializes the <see cref="System.Threading.ThreadLocal{T}"/> instance.
  55. /// </summary>
  56. public ThreadLocal()
  57. {
  58. Initialize(null, false);
  59. }
  60. /// <summary>
  61. /// Initializes the <see cref="System.Threading.ThreadLocal{T}"/> instance.
  62. /// </summary>
  63. /// <param name="trackAllValues">Whether to track all values set on the instance and expose them through the Values property.</param>
  64. public ThreadLocal(bool trackAllValues)
  65. {
  66. Initialize(null, trackAllValues);
  67. }
  68. /// <summary>
  69. /// Initializes the <see cref="System.Threading.ThreadLocal{T}"/> instance with the
  70. /// specified <paramref name="valueFactory"/> function.
  71. /// </summary>
  72. /// <param name="valueFactory">
  73. /// The <see cref="T:System.Func{T}"/> invoked to produce a lazily-initialized value when
  74. /// an attempt is made to retrieve <see cref="Value"/> without it having been previously initialized.
  75. /// </param>
  76. /// <exception cref="T:System.ArgumentNullException">
  77. /// <paramref name="valueFactory"/> is a null reference (Nothing in Visual Basic).
  78. /// </exception>
  79. public ThreadLocal(Func<T> valueFactory)
  80. {
  81. if (valueFactory == null)
  82. throw new ArgumentNullException(nameof(valueFactory));
  83. Initialize(valueFactory, false);
  84. }
  85. /// <summary>
  86. /// Initializes the <see cref="System.Threading.ThreadLocal{T}"/> instance with the
  87. /// specified <paramref name="valueFactory"/> function.
  88. /// </summary>
  89. /// <param name="valueFactory">
  90. /// The <see cref="T:System.Func{T}"/> invoked to produce a lazily-initialized value when
  91. /// an attempt is made to retrieve <see cref="Value"/> without it having been previously initialized.
  92. /// </param>
  93. /// <param name="trackAllValues">Whether to track all values set on the instance and expose them via the Values property.</param>
  94. /// <exception cref="T:System.ArgumentNullException">
  95. /// <paramref name="valueFactory"/> is a null reference (Nothing in Visual Basic).
  96. /// </exception>
  97. public ThreadLocal(Func<T> valueFactory, bool trackAllValues)
  98. {
  99. if (valueFactory == null)
  100. throw new ArgumentNullException(nameof(valueFactory));
  101. Initialize(valueFactory, trackAllValues);
  102. }
  103. private void Initialize(Func<T>? valueFactory, bool trackAllValues)
  104. {
  105. _valueFactory = valueFactory;
  106. _trackAllValues = trackAllValues;
  107. // Assign the ID and mark the instance as initialized. To avoid leaking IDs, we assign the ID and set _initialized
  108. // in a finally block, to avoid a thread abort in between the two statements.
  109. try { }
  110. finally
  111. {
  112. _idComplement = ~s_idManager.GetId();
  113. // As the last step, mark the instance as fully initialized. (Otherwise, if _initialized=false, we know that an exception
  114. // occurred in the constructor.)
  115. _initialized = true;
  116. }
  117. }
  118. /// <summary>
  119. /// Releases the resources used by this <see cref="T:System.Threading.ThreadLocal{T}" /> instance.
  120. /// </summary>
  121. ~ThreadLocal()
  122. {
  123. // finalizer to return the type combination index to the pool
  124. Dispose(false);
  125. }
  126. #region IDisposable Members
  127. /// <summary>
  128. /// Releases the resources used by this <see cref="T:System.Threading.ThreadLocal{T}" /> instance.
  129. /// </summary>
  130. /// <remarks>
  131. /// Unlike most of the members of <see cref="T:System.Threading.ThreadLocal{T}"/>, this method is not thread-safe.
  132. /// </remarks>
  133. public void Dispose()
  134. {
  135. Dispose(true);
  136. GC.SuppressFinalize(this);
  137. }
  138. /// <summary>
  139. /// Releases the resources used by this <see cref="T:System.Threading.ThreadLocal{T}" /> instance.
  140. /// </summary>
  141. /// <param name="disposing">
  142. /// A Boolean value that indicates whether this method is being called due to a call to <see cref="Dispose()"/>.
  143. /// </param>
  144. /// <remarks>
  145. /// Unlike most of the members of <see cref="T:System.Threading.ThreadLocal{T}"/>, this method is not thread-safe.
  146. /// </remarks>
  147. protected virtual void Dispose(bool disposing)
  148. {
  149. int id;
  150. lock (s_idManager)
  151. {
  152. id = ~_idComplement;
  153. _idComplement = 0;
  154. if (id < 0 || !_initialized)
  155. {
  156. Debug.Assert(id >= 0 || !_initialized, "expected id >= 0 if initialized");
  157. // Handle double Dispose calls or disposal of an instance whose constructor threw an exception.
  158. return;
  159. }
  160. _initialized = false;
  161. Debug.Assert(_linkedSlot != null, "Should be non-null if not yet disposed");
  162. for (LinkedSlot? linkedSlot = _linkedSlot._next; linkedSlot != null; linkedSlot = linkedSlot._next)
  163. {
  164. LinkedSlotVolatile[]? slotArray = linkedSlot._slotArray;
  165. if (slotArray == null)
  166. {
  167. // The thread that owns this slotArray has already finished.
  168. continue;
  169. }
  170. // Remove the reference from the LinkedSlot to the slot table.
  171. linkedSlot._slotArray = null;
  172. // And clear the references from the slot table to the linked slot and the value so that
  173. // both can get garbage collected.
  174. slotArray[id].Value!._value = default;
  175. slotArray[id].Value = null;
  176. }
  177. }
  178. _linkedSlot = null;
  179. s_idManager.ReturnId(id);
  180. }
  181. #endregion
  182. /// <summary>Creates and returns a string representation of this instance for the current thread.</summary>
  183. /// <returns>The result of calling <see cref="System.Object.ToString"/> on the <see cref="Value"/>.</returns>
  184. /// <exception cref="T:System.NullReferenceException">
  185. /// The <see cref="Value"/> for the current thread is a null reference (Nothing in Visual Basic).
  186. /// </exception>
  187. /// <exception cref="T:System.InvalidOperationException">
  188. /// The initialization function referenced <see cref="Value"/> in an improper manner.
  189. /// </exception>
  190. /// <exception cref="T:System.ObjectDisposedException">
  191. /// The <see cref="ThreadLocal{T}"/> instance has been disposed.
  192. /// </exception>
  193. /// <remarks>
  194. /// Calling this method forces initialization for the current thread, as is the
  195. /// case with accessing <see cref="Value"/> directly.
  196. /// </remarks>
  197. public override string? ToString()
  198. {
  199. return Value!.ToString(); // Throws NullReferenceException as if caller called ToString on the value itself
  200. }
  201. /// <summary>
  202. /// Gets or sets the value of this instance for the current thread.
  203. /// </summary>
  204. /// <exception cref="T:System.InvalidOperationException">
  205. /// The initialization function referenced <see cref="Value"/> in an improper manner.
  206. /// </exception>
  207. /// <exception cref="T:System.ObjectDisposedException">
  208. /// The <see cref="ThreadLocal{T}"/> instance has been disposed.
  209. /// </exception>
  210. /// <remarks>
  211. /// If this instance was not previously initialized for the current thread,
  212. /// accessing <see cref="Value"/> will attempt to initialize it. If an initialization function was
  213. /// supplied during the construction, that initialization will happen by invoking the function
  214. /// to retrieve the initial value for <see cref="Value"/>. Otherwise, the default value of
  215. /// <typeparamref name="T"/> will be used.
  216. /// </remarks>
  217. [DebuggerBrowsable(DebuggerBrowsableState.Never)]
  218. [MaybeNull]
  219. public T Value
  220. {
  221. get
  222. {
  223. LinkedSlotVolatile[]? slotArray = ts_slotArray;
  224. LinkedSlot? slot;
  225. int id = ~_idComplement;
  226. //
  227. // Attempt to get the value using the fast path
  228. //
  229. if (slotArray != null // Has the slot array been initialized?
  230. && id >= 0 // Is the ID non-negative (i.e., instance is not disposed)?
  231. && id < slotArray.Length // Is the table large enough?
  232. && (slot = slotArray[id].Value) != null // Has a LinkedSlot object has been allocated for this ID?
  233. && _initialized // Has the instance *still* not been disposed (important for a race condition with Dispose)?
  234. )
  235. {
  236. // We verified that the instance has not been disposed *after* we got a reference to the slot.
  237. // This guarantees that we have a reference to the right slot.
  238. //
  239. // Volatile read of the LinkedSlotVolatile.Value property ensures that the m_initialized read
  240. // will not be reordered before the read of slotArray[id].
  241. return slot._value;
  242. }
  243. return GetValueSlow();
  244. }
  245. set
  246. {
  247. LinkedSlotVolatile[]? slotArray = ts_slotArray;
  248. LinkedSlot? slot;
  249. int id = ~_idComplement;
  250. // Attempt to set the value using the fast path
  251. if (slotArray != null // Has the slot array been initialized?
  252. && id >= 0 // Is the ID non-negative (i.e., instance is not disposed)?
  253. && id < slotArray.Length // Is the table large enough?
  254. && (slot = slotArray[id].Value) != null // Has a LinkedSlot object has been allocated for this ID?
  255. && _initialized // Has the instance *still* not been disposed (important for a race condition with Dispose)?
  256. )
  257. {
  258. // We verified that the instance has not been disposed *after* we got a reference to the slot.
  259. // This guarantees that we have a reference to the right slot.
  260. //
  261. // Volatile read of the LinkedSlotVolatile.Value property ensures that the m_initialized read
  262. // will not be reordered before the read of slotArray[id].
  263. slot._value = value;
  264. }
  265. else
  266. {
  267. SetValueSlow(value, slotArray);
  268. }
  269. }
  270. }
  271. [return: MaybeNull]
  272. private T GetValueSlow()
  273. {
  274. // If the object has been disposed, the id will be -1.
  275. int id = ~_idComplement;
  276. if (id < 0)
  277. {
  278. throw new ObjectDisposedException(SR.ThreadLocal_Disposed);
  279. }
  280. Debugger.NotifyOfCrossThreadDependency();
  281. // Determine the initial value
  282. T value;
  283. if (_valueFactory == null)
  284. {
  285. value = default!;
  286. }
  287. else
  288. {
  289. value = _valueFactory();
  290. if (IsValueCreated)
  291. {
  292. throw new InvalidOperationException(SR.ThreadLocal_Value_RecursiveCallsToValue);
  293. }
  294. }
  295. // Since the value has been previously uninitialized, we also need to set it (according to the ThreadLocal semantics).
  296. Value = value;
  297. return value;
  298. }
  299. private void SetValueSlow(T value, LinkedSlotVolatile[]? slotArray)
  300. {
  301. int id = ~_idComplement;
  302. // If the object has been disposed, id will be -1.
  303. if (id < 0)
  304. {
  305. throw new ObjectDisposedException(SR.ThreadLocal_Disposed);
  306. }
  307. // If a slot array has not been created on this thread yet, create it.
  308. if (slotArray == null)
  309. {
  310. slotArray = new LinkedSlotVolatile[GetNewTableSize(id + 1)];
  311. ts_finalizationHelper = new FinalizationHelper(slotArray, _trackAllValues);
  312. ts_slotArray = slotArray;
  313. }
  314. // If the slot array is not big enough to hold this ID, increase the table size.
  315. if (id >= slotArray.Length)
  316. {
  317. GrowTable(ref slotArray!, id + 1);
  318. Debug.Assert(ts_finalizationHelper != null, "Should have been initialized when this thread's slot array was created.");
  319. ts_finalizationHelper.SlotArray = slotArray;
  320. ts_slotArray = slotArray;
  321. }
  322. // If we are using the slot in this table for the first time, create a new LinkedSlot and add it into
  323. // the linked list for this ThreadLocal instance.
  324. if (slotArray[id].Value == null)
  325. {
  326. CreateLinkedSlot(slotArray, id, value);
  327. }
  328. else
  329. {
  330. // Volatile read of the LinkedSlotVolatile.Value property ensures that the m_initialized read
  331. // that follows will not be reordered before the read of slotArray[id].
  332. LinkedSlot? slot = slotArray[id].Value;
  333. // It is important to verify that the ThreadLocal instance has not been disposed. The check must come
  334. // after capturing slotArray[id], but before assigning the value into the slot. This ensures that
  335. // if this ThreadLocal instance was disposed on another thread and another ThreadLocal instance was
  336. // created, we definitely won't assign the value into the wrong instance.
  337. if (!_initialized)
  338. {
  339. throw new ObjectDisposedException(SR.ThreadLocal_Disposed);
  340. }
  341. slot!._value = value;
  342. }
  343. }
  344. /// <summary>
  345. /// Creates a LinkedSlot and inserts it into the linked list for this ThreadLocal instance.
  346. /// </summary>
  347. private void CreateLinkedSlot(LinkedSlotVolatile[] slotArray, int id, T value)
  348. {
  349. // Create a LinkedSlot
  350. var linkedSlot = new LinkedSlot(slotArray);
  351. // Insert the LinkedSlot into the linked list maintained by this ThreadLocal<> instance and into the slot array
  352. lock (s_idManager)
  353. {
  354. // Check that the instance has not been disposed. It is important to check this under a lock, since
  355. // Dispose also executes under a lock.
  356. if (!_initialized)
  357. {
  358. throw new ObjectDisposedException(SR.ThreadLocal_Disposed);
  359. }
  360. Debug.Assert(_linkedSlot != null, "Should only be null if disposed");
  361. LinkedSlot? firstRealNode = _linkedSlot._next;
  362. // Insert linkedSlot between nodes m_linkedSlot and firstRealNode.
  363. // (_linkedSlot is the dummy head node that should always be in the front.)
  364. linkedSlot._next = firstRealNode;
  365. linkedSlot._previous = _linkedSlot;
  366. linkedSlot._value = value;
  367. if (firstRealNode != null)
  368. {
  369. firstRealNode._previous = linkedSlot;
  370. }
  371. _linkedSlot._next = linkedSlot;
  372. // Assigning the slot under a lock prevents a race condition with Dispose (dispose also acquires the lock).
  373. // Otherwise, it would be possible that the ThreadLocal instance is disposed, another one gets created
  374. // with the same ID, and the write would go to the wrong instance.
  375. slotArray[id].Value = linkedSlot;
  376. }
  377. }
  378. /// <summary>
  379. /// Gets a list for all of the values currently stored by all of the threads that have accessed this instance.
  380. /// </summary>
  381. /// <exception cref="T:System.ObjectDisposedException">
  382. /// The <see cref="ThreadLocal{T}"/> instance has been disposed.
  383. /// </exception>
  384. public IList<T> Values
  385. {
  386. get
  387. {
  388. if (!_trackAllValues)
  389. {
  390. throw new InvalidOperationException(SR.ThreadLocal_ValuesNotAvailable);
  391. }
  392. var list = GetValuesAsList(); // returns null if disposed
  393. if (list == null) throw new ObjectDisposedException(SR.ThreadLocal_Disposed);
  394. return list;
  395. }
  396. }
  397. /// <summary>Gets all of the threads' values in a list.</summary>
  398. private List<T>? GetValuesAsList()
  399. {
  400. LinkedSlot? linkedSlot = _linkedSlot;
  401. int id = ~_idComplement;
  402. if (id == -1 || linkedSlot == null)
  403. {
  404. return null;
  405. }
  406. // Walk over the linked list of slots and gather the values associated with this ThreadLocal instance.
  407. var valueList = new List<T>();
  408. for (linkedSlot = linkedSlot._next; linkedSlot != null; linkedSlot = linkedSlot._next)
  409. {
  410. // We can safely read linkedSlot.Value. Even if this ThreadLocal has been disposed in the meantime, the LinkedSlot
  411. // objects will never be assigned to another ThreadLocal instance.
  412. valueList.Add(linkedSlot._value!);
  413. }
  414. return valueList;
  415. }
  416. internal IEnumerable<T> ValuesAsEnumerable
  417. {
  418. get
  419. {
  420. if (!_trackAllValues)
  421. {
  422. throw new InvalidOperationException(SR.ThreadLocal_ValuesNotAvailable);
  423. }
  424. LinkedSlot? linkedSlot = _linkedSlot;
  425. int id = ~_idComplement;
  426. if (id == -1 || linkedSlot == null)
  427. {
  428. throw new ObjectDisposedException(SR.ThreadLocal_Disposed);
  429. }
  430. // Walk over the linked list of slots and gather the values associated with this ThreadLocal instance.
  431. for (linkedSlot = linkedSlot._next; linkedSlot != null; linkedSlot = linkedSlot._next)
  432. {
  433. // We can safely read linkedSlot.Value. Even if this ThreadLocal has been disposed in the meantime, the LinkedSlot
  434. // objects will never be assigned to another ThreadLocal instance.
  435. yield return linkedSlot._value!;
  436. }
  437. }
  438. }
  439. /// <summary>Gets the number of threads that have data in this instance.</summary>
  440. private int ValuesCountForDebugDisplay
  441. {
  442. get
  443. {
  444. int count = 0;
  445. for (LinkedSlot? linkedSlot = _linkedSlot?._next; linkedSlot != null; linkedSlot = linkedSlot._next)
  446. {
  447. count++;
  448. }
  449. return count;
  450. }
  451. }
  452. /// <summary>
  453. /// Gets whether <see cref="Value"/> is initialized on the current thread.
  454. /// </summary>
  455. /// <exception cref="T:System.ObjectDisposedException">
  456. /// The <see cref="ThreadLocal{T}"/> instance has been disposed.
  457. /// </exception>
  458. public bool IsValueCreated
  459. {
  460. get
  461. {
  462. int id = ~_idComplement;
  463. if (id < 0)
  464. {
  465. throw new ObjectDisposedException(SR.ThreadLocal_Disposed);
  466. }
  467. LinkedSlotVolatile[]? slotArray = ts_slotArray;
  468. return slotArray != null && id < slotArray.Length && slotArray[id].Value != null;
  469. }
  470. }
  471. /// <summary>Gets the value of the ThreadLocal&lt;T&gt; for debugging display purposes. It takes care of getting
  472. /// the value for the current thread in the ThreadLocal mode.</summary>
  473. [MaybeNull]
  474. internal T ValueForDebugDisplay
  475. {
  476. get
  477. {
  478. LinkedSlotVolatile[]? slotArray = ts_slotArray;
  479. int id = ~_idComplement;
  480. LinkedSlot? slot;
  481. if (slotArray == null || id >= slotArray.Length || (slot = slotArray[id].Value) == null || !_initialized)
  482. return default!;
  483. return slot._value;
  484. }
  485. }
  486. /// <summary>Gets the values of all threads that accessed the ThreadLocal&lt;T&gt;.</summary>
  487. internal List<T>? ValuesForDebugDisplay // same as Values property, but doesn't throw if disposed
  488. {
  489. get { return GetValuesAsList(); }
  490. }
  491. /// <summary>
  492. /// Resizes a table to a certain length (or larger).
  493. /// </summary>
  494. private void GrowTable(ref LinkedSlotVolatile[] table, int minLength)
  495. {
  496. Debug.Assert(table.Length < minLength);
  497. // Determine the size of the new table and allocate it.
  498. int newLen = GetNewTableSize(minLength);
  499. LinkedSlotVolatile[] newTable = new LinkedSlotVolatile[newLen];
  500. //
  501. // The lock is necessary to avoid a race with ThreadLocal.Dispose. GrowTable has to point all
  502. // LinkedSlot instances referenced in the old table to reference the new table. Without locking,
  503. // Dispose could use a stale SlotArray reference and clear out a slot in the old array only, while
  504. // the value continues to be referenced from the new (larger) array.
  505. //
  506. lock (s_idManager)
  507. {
  508. for (int i = 0; i < table.Length; i++)
  509. {
  510. LinkedSlot? linkedSlot = table[i].Value;
  511. if (linkedSlot != null && linkedSlot._slotArray != null)
  512. {
  513. linkedSlot._slotArray = newTable;
  514. newTable[i] = table[i];
  515. }
  516. }
  517. }
  518. table = newTable;
  519. }
  520. /// <summary>
  521. /// Chooses the next larger table size
  522. /// </summary>
  523. private static int GetNewTableSize(int minSize)
  524. {
  525. if ((uint)minSize > Array.MaxArrayLength)
  526. {
  527. // Intentionally return a value that will result in an OutOfMemoryException
  528. return int.MaxValue;
  529. }
  530. Debug.Assert(minSize > 0);
  531. //
  532. // Round up the size to the next power of 2
  533. //
  534. // The algorithm takes three steps:
  535. // input -> subtract one -> propagate 1-bits to the right -> add one
  536. //
  537. // Let's take a look at the 3 steps in both interesting cases: where the input
  538. // is (Example 1) and isn't (Example 2) a power of 2.
  539. //
  540. // Example 1: 100000 -> 011111 -> 011111 -> 100000
  541. // Example 2: 011010 -> 011001 -> 011111 -> 100000
  542. //
  543. int newSize = minSize;
  544. // Step 1: Decrement
  545. newSize--;
  546. // Step 2: Propagate 1-bits to the right.
  547. newSize |= newSize >> 1;
  548. newSize |= newSize >> 2;
  549. newSize |= newSize >> 4;
  550. newSize |= newSize >> 8;
  551. newSize |= newSize >> 16;
  552. // Step 3: Increment
  553. newSize++;
  554. // Don't set newSize to more than Array.MaxArrayLength
  555. if ((uint)newSize > Array.MaxArrayLength)
  556. {
  557. newSize = Array.MaxArrayLength;
  558. }
  559. return newSize;
  560. }
  561. /// <summary>
  562. /// A wrapper struct used as LinkedSlotVolatile[] - an array of LinkedSlot instances, but with volatile semantics
  563. /// on array accesses.
  564. /// </summary>
  565. private struct LinkedSlotVolatile
  566. {
  567. internal volatile LinkedSlot? Value;
  568. }
  569. /// <summary>
  570. /// A node in the doubly-linked list stored in the ThreadLocal instance.
  571. ///
  572. /// The value is stored in one of two places:
  573. ///
  574. /// 1. If SlotArray is not null, the value is in SlotArray.Table[id]
  575. /// 2. If SlotArray is null, the value is in FinalValue.
  576. /// </summary>
  577. private sealed class LinkedSlot
  578. {
  579. internal LinkedSlot(LinkedSlotVolatile[]? slotArray)
  580. {
  581. _slotArray = slotArray;
  582. }
  583. // The next LinkedSlot for this ThreadLocal<> instance
  584. internal volatile LinkedSlot? _next;
  585. // The previous LinkedSlot for this ThreadLocal<> instance
  586. internal volatile LinkedSlot? _previous;
  587. // The SlotArray that stores this LinkedSlot at SlotArray.Table[id].
  588. internal volatile LinkedSlotVolatile[]? _slotArray;
  589. // The value for this slot.
  590. [AllowNull, MaybeNull] internal T _value = default;
  591. }
  592. /// <summary>
  593. /// A manager class that assigns IDs to ThreadLocal instances
  594. /// </summary>
  595. private class IdManager
  596. {
  597. // The next ID to try
  598. private int _nextIdToTry = 0;
  599. // Stores whether each ID is free or not. Additionally, the object is also used as a lock for the IdManager.
  600. private List<bool> _freeIds = new List<bool>();
  601. internal int GetId()
  602. {
  603. lock (_freeIds)
  604. {
  605. int availableId = _nextIdToTry;
  606. while (availableId < _freeIds.Count)
  607. {
  608. if (_freeIds[availableId]) { break; }
  609. availableId++;
  610. }
  611. if (availableId == _freeIds.Count)
  612. {
  613. _freeIds.Add(false);
  614. }
  615. else
  616. {
  617. _freeIds[availableId] = false;
  618. }
  619. _nextIdToTry = availableId + 1;
  620. return availableId;
  621. }
  622. }
  623. // Return an ID to the pool
  624. internal void ReturnId(int id)
  625. {
  626. lock (_freeIds)
  627. {
  628. _freeIds[id] = true;
  629. if (id < _nextIdToTry) _nextIdToTry = id;
  630. }
  631. }
  632. }
  633. /// <summary>
  634. /// A class that facilitates ThreadLocal cleanup after a thread exits.
  635. ///
  636. /// After a thread with an associated thread-local table has exited, the FinalizationHelper
  637. /// is responsible for removing back-references to the table. Since an instance of FinalizationHelper
  638. /// is only referenced from a single thread-local slot, the FinalizationHelper will be GC'd once
  639. /// the thread has exited.
  640. ///
  641. /// The FinalizationHelper then locates all LinkedSlot instances with back-references to the table
  642. /// (all those LinkedSlot instances can be found by following references from the table slots) and
  643. /// releases the table so that it can get GC'd.
  644. /// </summary>
  645. private class FinalizationHelper
  646. {
  647. internal LinkedSlotVolatile[] SlotArray;
  648. private bool _trackAllValues;
  649. internal FinalizationHelper(LinkedSlotVolatile[] slotArray, bool trackAllValues)
  650. {
  651. SlotArray = slotArray;
  652. _trackAllValues = trackAllValues;
  653. }
  654. ~FinalizationHelper()
  655. {
  656. LinkedSlotVolatile[] slotArray = SlotArray;
  657. Debug.Assert(slotArray != null);
  658. for (int i = 0; i < slotArray.Length; i++)
  659. {
  660. LinkedSlot? linkedSlot = slotArray[i].Value;
  661. if (linkedSlot == null)
  662. {
  663. // This slot in the table is empty
  664. continue;
  665. }
  666. if (_trackAllValues)
  667. {
  668. // Set the SlotArray field to null to release the slot array.
  669. linkedSlot._slotArray = null;
  670. }
  671. else
  672. {
  673. // Remove the LinkedSlot from the linked list. Once the FinalizationHelper is done, all back-references to
  674. // the table will be have been removed, and so the table can get GC'd.
  675. lock (s_idManager)
  676. {
  677. if (linkedSlot._next != null)
  678. {
  679. linkedSlot._next._previous = linkedSlot._previous;
  680. }
  681. // Since the list uses a dummy head node, the Previous reference should never be null.
  682. Debug.Assert(linkedSlot._previous != null);
  683. linkedSlot._previous._next = linkedSlot._next;
  684. }
  685. }
  686. }
  687. }
  688. }
  689. }
  690. /// <summary>A debugger view of the ThreadLocal&lt;T&gt; to surface additional debugging properties and
  691. /// to ensure that the ThreadLocal&lt;T&gt; does not become initialized if it was not already.</summary>
  692. internal sealed class SystemThreading_ThreadLocalDebugView<T>
  693. {
  694. //The ThreadLocal object being viewed.
  695. private readonly ThreadLocal<T> _tlocal;
  696. /// <summary>Constructs a new debugger view object for the provided ThreadLocal object.</summary>
  697. /// <param name="tlocal">A ThreadLocal object to browse in the debugger.</param>
  698. public SystemThreading_ThreadLocalDebugView(ThreadLocal<T> tlocal)
  699. {
  700. _tlocal = tlocal;
  701. }
  702. /// <summary>Returns whether the ThreadLocal object is initialized or not.</summary>
  703. public bool IsValueCreated => _tlocal.IsValueCreated;
  704. /// <summary>Returns the value of the ThreadLocal object.</summary>
  705. public T Value => _tlocal.ValueForDebugDisplay;
  706. /// <summary>Return all values for all threads that have accessed this instance.</summary>
  707. public List<T>? Values => _tlocal.ValuesForDebugDisplay;
  708. }
  709. }