ThreadLocal.cs 32 KB

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