#if (NET20 || NET35) #pragma warning disable 0420 // ==++== // // Copyright (c) Microsoft Corporation. All rights reserved. // // ==--== // =+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+ // // ThreadLocal.cs // // Microsoft // // A class that provides a simple, lightweight implementation of thread-local lazy-initialization, where a value is initialized once per accessing // thread; this provides an alternative to using a ThreadStatic static variable and having // to check the variable prior to every access to see if it's been initialized. // // // // =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=- using System.Diagnostics; using System.Collections.Generic; using System.Security.Permissions; namespace System.Threading { /// /// Provides thread-local storage of data. /// /// Specifies the type of data stored per-thread. /// /// /// With the exception of , all public and protected members of /// are thread-safe and may be used /// concurrently from multiple threads. /// /// [DebuggerTypeProxy(typeof(SystemThreading_ThreadLocalDebugView<>))] [DebuggerDisplay("IsValueCreated={IsValueCreated}, Value={ValueForDebugDisplay}, Count={ValuesCountForDebugDisplay}")] [HostProtection(Synchronization = true, ExternalThreading = true)] internal class ThreadLocal : IDisposable { private const int MaxArrayLength = 0X7FEFFFFF; // a delegate that returns the created value, if null the created value will be default(T) private Func m_valueFactory; // // ts_slotArray is a table of thread-local values for all ThreadLocal instances // // So, when a thread reads ts_slotArray, it gets back an array of *all* ThreadLocal values for this thread and this T. // The slot relevant to this particular ThreadLocal instance is determined by the m_idComplement instance field stored in // the ThreadLocal instance. // [ThreadStatic] static LinkedSlotVolatile[] ts_slotArray; [ThreadStatic] static FinalizationHelper ts_finalizationHelper; // Slot ID of this ThreadLocal<> instance. We store a bitwise complement of the ID (that is ~ID), which allows us to distinguish // between the case when ID is 0 and an incompletely initialized object, either due to a thread abort in the constructor, or // possibly due to a memory model issue in user code. private int m_idComplement; // This field is set to true when the constructor completes. That is helpful for recognizing whether a constructor // threw an exception - either due to invalid argument or due to a thread abort. Finally, the field is set to false // when the instance is disposed. private volatile bool m_initialized; // IdManager assigns and reuses slot IDs. Additionally, the object is also used as a global lock. private static IdManager s_idManager = new IdManager(); // A linked list of all values associated with this ThreadLocal instance. // We create a dummy head node. That allows us to remove any (non-dummy) node without having to locate the m_linkedSlot field. private LinkedSlot m_linkedSlot = new LinkedSlot(null); // Whether the Values property is supported private bool m_trackAllValues; /// /// Initializes the instance. /// public ThreadLocal() { Initialize(null, false); } /// /// Initializes the instance. /// /// Whether to track all values set on the instance and expose them through the Values property. public ThreadLocal(bool trackAllValues) { Initialize(null, trackAllValues); } /// /// Initializes the instance with the /// specified function. /// /// /// The invoked to produce a lazily-initialized value when /// an attempt is made to retrieve without it having been previously initialized. /// /// /// is a null reference (Nothing in Visual Basic). /// public ThreadLocal(Func valueFactory) { if (valueFactory == null) throw new ArgumentNullException("valueFactory"); Initialize(valueFactory, false); } /// /// Initializes the instance with the /// specified function. /// /// /// The invoked to produce a lazily-initialized value when /// an attempt is made to retrieve without it having been previously initialized. /// /// Whether to track all values set on the instance and expose them via the Values property. /// /// is a null reference (Nothing in Visual Basic). /// public ThreadLocal(Func valueFactory, bool trackAllValues) { if (valueFactory == null) throw new ArgumentNullException("valueFactory"); Initialize(valueFactory, trackAllValues); } private void Initialize(Func valueFactory, bool trackAllValues) { m_valueFactory = valueFactory; m_trackAllValues = trackAllValues; // Assign the ID and mark the instance as initialized. To avoid leaking IDs, we assign the ID and set m_initialized // in a finally block, to avoid a thread abort in between the two statements. try { } finally { m_idComplement = ~s_idManager.GetId(); // As the last step, mark the instance as fully initialized. (Otherwise, if m_initialized=false, we know that an exception // occurred in the constructor.) m_initialized = true; } } /// /// Releases the resources used by this instance. /// ~ThreadLocal() { // finalizer to return the type combination index to the pool Dispose(false); } #region IDisposable Members /// /// Releases the resources used by this instance. /// /// /// Unlike most of the members of , this method is not thread-safe. /// public void Dispose() { Dispose(true); GC.SuppressFinalize(this); } /// /// Releases the resources used by this instance. /// /// /// A Boolean value that indicates whether this method is being called due to a call to . /// /// /// Unlike most of the members of , this method is not thread-safe. /// protected virtual void Dispose(bool disposing) { int id; lock (s_idManager) { id = ~m_idComplement; m_idComplement = 0; if (id < 0 || !m_initialized) { // Handle double Dispose calls or disposal of an instance whose constructor threw an exception. return; } m_initialized = false; for (LinkedSlot linkedSlot = m_linkedSlot.Next; linkedSlot != null; linkedSlot = linkedSlot.Next) { LinkedSlotVolatile[] slotArray = linkedSlot.SlotArray; if (slotArray == null) { // The thread that owns this slotArray has already finished. continue; } // Remove the reference from the LinkedSlot to the slot table. linkedSlot.SlotArray = null; // And clear the references from the slot table to the linked slot and the value so that // both can get garbage collected. slotArray[id].Value.Value = default(T); slotArray[id].Value = null; } } m_linkedSlot = null; s_idManager.ReturnId(id); } #endregion /// Creates and returns a string representation of this instance for the current thread. /// The result of calling on the . /// /// The for the current thread is a null reference (Nothing in Visual Basic). /// /// /// The initialization function referenced in an improper manner. /// /// /// The instance has been disposed. /// /// /// Calling this method forces initialization for the current thread, as is the /// case with accessing directly. /// public override string ToString() { return Value.ToString(); } /// /// Gets or sets the value of this instance for the current thread. /// /// /// The initialization function referenced in an improper manner. /// /// /// The instance has been disposed. /// /// /// If this instance was not previously initialized for the current thread, /// accessing will attempt to initialize it. If an initialization function was /// supplied during the construction, that initialization will happen by invoking the function /// to retrieve the initial value for . Otherwise, the default value of /// will be used. /// [DebuggerBrowsable(DebuggerBrowsableState.Never)] public T Value { get { LinkedSlotVolatile[] slotArray = ts_slotArray; LinkedSlot slot; int id = ~m_idComplement; // // Attempt to get the value using the fast path // if (slotArray != null // Has the slot array been initialized? && id >= 0 // Is the ID non-negative (i.e., instance is not disposed)? && id < slotArray.Length // Is the table large enough? && (slot = slotArray[id].Value) != null // Has a LinkedSlot object has been allocated for this ID? && m_initialized // Has the instance *still* not been disposed (important for ----s with Dispose)? ) { // We verified that the instance has not been disposed *after* we got a reference to the slot. // This guarantees that we have a reference to the right slot. // // Volatile read of the LinkedSlotVolatile.Value property ensures that the m_initialized read // will not be reordered before the read of slotArray[id]. return slot.Value; } return GetValueSlow(); } set { LinkedSlotVolatile[] slotArray = ts_slotArray; LinkedSlot slot; int id = ~m_idComplement; // // Attempt to set the value using the fast path // if (slotArray != null // Has the slot array been initialized? && id >= 0 // Is the ID non-negative (i.e., instance is not disposed)? && id < slotArray.Length // Is the table large enough? && (slot = slotArray[id].Value) != null // Has a LinkedSlot object has been allocated for this ID? && m_initialized // Has the instance *still* not been disposed (important for ----s with Dispose)? ) { // We verified that the instance has not been disposed *after* we got a reference to the slot. // This guarantees that we have a reference to the right slot. // // Volatile read of the LinkedSlotVolatile.Value property ensures that the m_initialized read // will not be reordered before the read of slotArray[id]. slot.Value = value; } else { SetValueSlow(value, slotArray); } } } private T GetValueSlow() { // If the object has been disposed, the id will be -1. int id = ~m_idComplement; if (id < 0) { throw new ObjectDisposedException("The ThreadLocal instance has been disposed."); } // Determine the initial value T value; if (m_valueFactory == null) { value = default(T); } else { value = m_valueFactory(); if (IsValueCreated) { throw new InvalidOperationException("The initialization function attempted to reference Value recursively."); } } // Since the value has been previously uninitialized, we also need to set it (according to the ThreadLocal semantics). Value = value; return value; } private void SetValueSlow(T value, LinkedSlotVolatile[] slotArray) { int id = ~m_idComplement; // If the object has been disposed, id will be -1. if (id < 0) { throw new ObjectDisposedException("The ThreadLocal instance has been disposed."); } // If a slot array has not been created on this thread yet, create it. if (slotArray == null) { slotArray = new LinkedSlotVolatile[GetNewTableSize(id + 1)]; ts_finalizationHelper = new FinalizationHelper(slotArray, m_trackAllValues); ts_slotArray = slotArray; } // If the slot array is not big enough to hold this ID, increase the table size. if (id >= slotArray.Length) { GrowTable(ref slotArray, id + 1); ts_finalizationHelper.SlotArray = slotArray; ts_slotArray = slotArray; } // If we are using the slot in this table for the first time, create a new LinkedSlot and add it into // the linked list for this ThreadLocal instance. if (slotArray[id].Value == null) { CreateLinkedSlot(slotArray, id, value); } else { // Volatile read of the LinkedSlotVolatile.Value property ensures that the m_initialized read // that follows will not be reordered before the read of slotArray[id]. LinkedSlot slot = slotArray[id].Value; // It is important to verify that the ThreadLocal instance has not been disposed. The check must come // after capturing slotArray[id], but before assigning the value into the slot. This ensures that // if this ThreadLocal instance was disposed on another thread and another ThreadLocal instance was // created, we definitely won't assign the value into the wrong instance. if (!m_initialized) { throw new ObjectDisposedException("The ThreadLocal instance has been disposed."); } slot.Value = value; } } /// /// Creates a LinkedSlot and inserts it into the linked list for this ThreadLocal instance. /// private void CreateLinkedSlot(LinkedSlotVolatile[] slotArray, int id, T value) { // Create a LinkedSlot var linkedSlot = new LinkedSlot(slotArray); // Insert the LinkedSlot into the linked list maintained by this ThreadLocal<> instance and into the slot array lock (s_idManager) { // Check that the instance has not been disposed. It is important to check this under a lock, since // Dispose also executes under a lock. if (!m_initialized) { throw new ObjectDisposedException("The ThreadLocal instance has been disposed."); } LinkedSlot firstRealNode = m_linkedSlot.Next; // // Insert linkedSlot between nodes m_linkedSlot and firstRealNode. // (m_linkedSlot is the dummy head node that should always be in the front.) // linkedSlot.Next = firstRealNode; linkedSlot.Previous = m_linkedSlot; linkedSlot.Value = value; if (firstRealNode != null) { firstRealNode.Previous = linkedSlot; } m_linkedSlot.Next = linkedSlot; // Assigning the slot under a lock prevents a ---- with Dispose (dispose also acquires the lock). // Otherwise, it would be possible that the ThreadLocal instance is disposed, another one gets created // with the same ID, and the write would go to the wrong instance. slotArray[id].Value = linkedSlot; } } /// /// Gets a list for all of the values currently stored by all of the threads that have accessed this instance. /// /// /// The instance has been disposed. /// public IList Values { get { if (!m_trackAllValues) { throw new InvalidOperationException("Values stored by all threads are not available because this instance was initialized with the trackAllValues argument set to false in the call to a class constructor."); } var list = GetValuesAsList(); // returns null if disposed if (list == null) throw new ObjectDisposedException("The ThreadLocal instance has been disposed."); return list; } } /// Gets all of the threads' values in a list. private List GetValuesAsList() { List valueList = new List(); int id = ~m_idComplement; if (id == -1) { return null; } // Walk over the linked list of slots and gather the values associated with this ThreadLocal instance. for (LinkedSlot linkedSlot = m_linkedSlot.Next; linkedSlot != null; linkedSlot = linkedSlot.Next) { // We can safely read linkedSlot.Value. Even if this ThreadLocal has been disposed in the meantime, the LinkedSlot // objects will never be assigned to another ThreadLocal instance. valueList.Add(linkedSlot.Value); } return valueList; } /// Gets the number of threads that have data in this instance. private int ValuesCountForDebugDisplay { get { int count = 0; for (LinkedSlot linkedSlot = m_linkedSlot.Next; linkedSlot != null; linkedSlot = linkedSlot.Next) { count++; } return count; } } /// /// Gets whether is initialized on the current thread. /// /// /// The instance has been disposed. /// public bool IsValueCreated { get { int id = ~m_idComplement; if (id < 0) { throw new ObjectDisposedException("The ThreadLocal instance has been disposed."); } LinkedSlotVolatile[] slotArray = ts_slotArray; return slotArray != null && id < slotArray.Length && slotArray[id].Value != null; } } /// Gets the value of the ThreadLocal<T> for debugging display purposes. It takes care of getting /// the value for the current thread in the ThreadLocal mode. internal T ValueForDebugDisplay { get { LinkedSlotVolatile[] slotArray = ts_slotArray; int id = ~m_idComplement; LinkedSlot slot; if (slotArray == null || id >= slotArray.Length || (slot = slotArray[id].Value) == null || !m_initialized) return default(T); return slot.Value; } } /// Gets the values of all threads that accessed the ThreadLocal<T>. internal List ValuesForDebugDisplay // same as Values property, but doesn't throw if disposed { get { return GetValuesAsList(); } } /// /// Resizes a table to a certain length (or larger). /// private void GrowTable(ref LinkedSlotVolatile[] table, int minLength) { // Determine the size of the new table and allocate it. int newLen = GetNewTableSize(minLength); LinkedSlotVolatile[] newTable = new LinkedSlotVolatile[newLen]; // // The lock is necessary to avoid a race with ThreadLocal.Dispose. GrowTable has to point all // LinkedSlot instances referenced in the old table to reference the new table. Without locking, // Dispose could use a stale SlotArray reference and clear out a slot in the old array only, while // the value continues to be referenced from the new (larger) array. // lock (s_idManager) { for (int i = 0; i < table.Length; i++) { LinkedSlot linkedSlot = table[i].Value; if (linkedSlot != null && linkedSlot.SlotArray != null) { linkedSlot.SlotArray = newTable; newTable[i] = table[i]; } } } table = newTable; } /// /// Chooses the next larger table size /// private static int GetNewTableSize(int minSize) { if ((uint)minSize > MaxArrayLength) { // Intentionally return a value that will result in an OutOfMemoryException return int.MaxValue; } // // Round up the size to the next power of 2 // // The algorithm takes three steps: // input -> subtract one -> propagate 1-bits to the right -> add one // // Let's take a look at the 3 steps in both interesting cases: where the input // is (Example 1) and isn't (Example 2) a power of 2. // // Example 1: 100000 -> 011111 -> 011111 -> 100000 // Example 2: 011010 -> 011001 -> 011111 -> 100000 // int newSize = minSize; // Step 1: Decrement newSize--; // Step 2: Propagate 1-bits to the right. newSize |= newSize >> 1; newSize |= newSize >> 2; newSize |= newSize >> 4; newSize |= newSize >> 8; newSize |= newSize >> 16; // Step 3: Increment newSize++; // Don't set newSize to more than Array.MaxArrayLength if ((uint)newSize > MaxArrayLength) { newSize = MaxArrayLength; } return newSize; } /// /// A wrapper struct used as LinkedSlotVolatile[] - an array of LinkedSlot instances, but with volatile semantics /// on array accesses. /// private struct LinkedSlotVolatile { internal volatile LinkedSlot Value; } /// /// A node in the doubly-linked list stored in the ThreadLocal instance. /// /// The value is stored in one of two places: /// /// 1. If SlotArray is not null, the value is in SlotArray.Table[id] /// 2. If SlotArray is null, the value is in FinalValue. /// private sealed class LinkedSlot { internal LinkedSlot(LinkedSlotVolatile[] slotArray) { SlotArray = slotArray; } // The next LinkedSlot for this ThreadLocal<> instance internal volatile LinkedSlot Next; // The previous LinkedSlot for this ThreadLocal<> instance internal volatile LinkedSlot Previous; // The SlotArray that stores this LinkedSlot at SlotArray.Table[id]. internal volatile LinkedSlotVolatile[] SlotArray; // The value for this slot. internal T Value; } /// /// A manager class that assigns IDs to ThreadLocal instances /// private class IdManager { // The next ID to try private int m_nextIdToTry = 0; // Stores whether each ID is free or not. Additionally, the object is also used as a lock for the IdManager. private List m_freeIds = new List(); internal int GetId() { lock (m_freeIds) { int availableId = m_nextIdToTry; while (availableId < m_freeIds.Count) { if (m_freeIds[availableId]) { break; } availableId++; } if (availableId == m_freeIds.Count) { m_freeIds.Add(false); } else { m_freeIds[availableId] = false; } m_nextIdToTry = availableId + 1; return availableId; } } // Return an ID to the pool internal void ReturnId(int id) { lock (m_freeIds) { m_freeIds[id] = true; if (id < m_nextIdToTry) m_nextIdToTry = id; } } } /// /// A class that facilitates ThreadLocal cleanup after a thread exits. /// /// After a thread with an associated thread-local table has exited, the FinalizationHelper /// is responsible for removing back-references to the table. Since an instance of FinalizationHelper /// is only referenced from a single thread-local slot, the FinalizationHelper will be GC'd once /// the thread has exited. /// /// The FinalizationHelper then locates all LinkedSlot instances with back-references to the table /// (all those LinkedSlot instances can be found by following references from the table slots) and /// releases the table so that it can get GC'd. /// private class FinalizationHelper { internal LinkedSlotVolatile[] SlotArray; private bool m_trackAllValues; internal FinalizationHelper(LinkedSlotVolatile[] slotArray, bool trackAllValues) { SlotArray = slotArray; m_trackAllValues = trackAllValues; } ~FinalizationHelper() { LinkedSlotVolatile[] slotArray = SlotArray; for (int i = 0; i < slotArray.Length; i++) { LinkedSlot linkedSlot = slotArray[i].Value; if (linkedSlot == null) { // This slot in the table is empty continue; } if (m_trackAllValues) { // Set the SlotArray field to null to release the slot array. linkedSlot.SlotArray = null; } else { // Remove the LinkedSlot from the linked list. Once the FinalizationHelper is done, all back-references to // the table will be have been removed, and so the table can get GC'd. lock (s_idManager) { if (linkedSlot.Next != null) { linkedSlot.Next.Previous = linkedSlot.Previous; } // Since the list uses a dummy head node, the Previous reference should never be null. linkedSlot.Previous.Next = linkedSlot.Next; } } } } } } /// A debugger view of the ThreadLocal<T> to surface additional debugging properties and /// to ensure that the ThreadLocal<T> does not become initialized if it was not already. internal sealed class SystemThreading_ThreadLocalDebugView { //The ThreadLocal object being viewed. private readonly ThreadLocal m_tlocal; /// Constructs a new debugger view object for the provided ThreadLocal object. /// A ThreadLocal object to browse in the debugger. public SystemThreading_ThreadLocalDebugView(ThreadLocal tlocal) { m_tlocal = tlocal; } /// Returns whether the ThreadLocal object is initialized or not. public bool IsValueCreated { get { return m_tlocal.IsValueCreated; } } /// Returns the value of the ThreadLocal object. public T Value { get { return m_tlocal.ValueForDebugDisplay; } } /// Return all values for all threads that have accessed this instance. public List Values { get { return m_tlocal.ValuesForDebugDisplay; } } } } #endif