// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
+#nullable enable
using System.Collections.Generic;
using System.Diagnostics;
public class ThreadLocal<T> : IDisposable
{
// a delegate that returns the created value, if null the created value will be default(T)
- private Func<T> _valueFactory;
+ private Func<T>? _valueFactory;
// ts_slotArray is a table of thread-local values for all ThreadLocal<T> instances
//
// The slot relevant to this particular ThreadLocal<T> instance is determined by the _idComplement instance field stored in
// the ThreadLocal<T> instance.
[ThreadStatic]
- private static LinkedSlotVolatile[] ts_slotArray;
+ private static LinkedSlotVolatile[]? ts_slotArray;
[ThreadStatic]
- private static FinalizationHelper ts_finalizationHelper;
+ private 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
private volatile bool _initialized;
// IdManager assigns and reuses slot IDs. Additionally, the object is also used as a global lock.
- private static IdManager s_idManager = new IdManager();
+ private static readonly IdManager s_idManager = new IdManager();
// A linked list of all values associated with this ThreadLocal<T> 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 _linkedSlot = new LinkedSlot(null);
+ private LinkedSlot? _linkedSlot = new LinkedSlot(null);
// Whether the Values property is supported
private bool _trackAllValues;
Initialize(valueFactory, trackAllValues);
}
- private void Initialize(Func<T> valueFactory, bool trackAllValues)
+ private void Initialize(Func<T>? valueFactory, bool trackAllValues)
{
_valueFactory = valueFactory;
_trackAllValues = trackAllValues;
}
_initialized = false;
- for (LinkedSlot linkedSlot = _linkedSlot._next; linkedSlot != null; linkedSlot = linkedSlot._next)
+ Debug.Assert(_linkedSlot != null, "Should be non-null if not yet disposed");
+ for (LinkedSlot? linkedSlot = _linkedSlot._next; linkedSlot != null; linkedSlot = linkedSlot._next)
{
- LinkedSlotVolatile[] slotArray = linkedSlot._slotArray;
+ LinkedSlotVolatile[]? slotArray = linkedSlot._slotArray;
if (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;
+ slotArray[id].Value!._value = default!; // TODO-NULLABILITY: need a way to annotate Ts
slotArray[id].Value = null;
}
}
/// Calling this method forces initialization for the current thread, as is the
/// case with accessing <see cref="Value"/> directly.
/// </remarks>
- public override string ToString()
+ public override string? ToString()
{
- return Value.ToString();
+ return Value!.ToString(); // TODO-NULLABILITY: This is actually documented to null ref if Value is null!
}
/// <summary>
{
get
{
- LinkedSlotVolatile[] slotArray = ts_slotArray;
- LinkedSlot slot;
+ LinkedSlotVolatile[]? slotArray = ts_slotArray;
+ LinkedSlot? slot;
int id = ~_idComplement;
//
}
set
{
- LinkedSlotVolatile[] slotArray = ts_slotArray;
- LinkedSlot slot;
+ LinkedSlotVolatile[]? slotArray = ts_slotArray;
+ LinkedSlot? slot;
int id = ~_idComplement;
// Attempt to set the value using the fast path
T value;
if (_valueFactory == null)
{
- value = default;
+ value = default!; // TODO-NULLABILITY: Need a way to annotate T as nullable
}
else
{
return value;
}
- private void SetValueSlow(T value, LinkedSlotVolatile[] slotArray)
+ private void SetValueSlow(T value, LinkedSlotVolatile[]? slotArray)
{
int id = ~_idComplement;
// 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);
+ GrowTable(ref slotArray!, id + 1);
+ Debug.Assert(ts_finalizationHelper != null, "Should have been initialized when this thread's slot array was created.");
ts_finalizationHelper.SlotArray = slotArray;
ts_slotArray = slotArray;
}
{
// 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;
+ 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
throw new ObjectDisposedException(SR.ThreadLocal_Disposed);
}
- slot._value = value;
+ slot!._value = value;
}
}
throw new ObjectDisposedException(SR.ThreadLocal_Disposed);
}
- LinkedSlot firstRealNode = _linkedSlot._next;
+ Debug.Assert(_linkedSlot != null, "Should only be null if disposed");
+ LinkedSlot? firstRealNode = _linkedSlot._next;
// Insert linkedSlot between nodes m_linkedSlot and firstRealNode.
// (_linkedSlot is the dummy head node that should always be in the front.)
}
/// <summary>Gets all of the threads' values in a list.</summary>
- private List<T> GetValuesAsList()
+ private List<T>? GetValuesAsList()
{
List<T> valueList = new List<T>();
int id = ~_idComplement;
}
// Walk over the linked list of slots and gather the values associated with this ThreadLocal instance.
- for (LinkedSlot linkedSlot = _linkedSlot._next; linkedSlot != null; linkedSlot = linkedSlot._next)
+ Debug.Assert(_linkedSlot != null, "Should only be null if the instance was disposed.");
+ for (LinkedSlot? linkedSlot = _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.
get
{
int count = 0;
- for (LinkedSlot linkedSlot = _linkedSlot._next; linkedSlot != null; linkedSlot = linkedSlot._next)
+ for (LinkedSlot? linkedSlot = _linkedSlot?._next; linkedSlot != null; linkedSlot = linkedSlot._next)
{
count++;
}
throw new ObjectDisposedException(SR.ThreadLocal_Disposed);
}
- LinkedSlotVolatile[] slotArray = ts_slotArray;
+ LinkedSlotVolatile[]? slotArray = ts_slotArray;
return slotArray != null && id < slotArray.Length && slotArray[id].Value != null;
}
}
{
get
{
- LinkedSlotVolatile[] slotArray = ts_slotArray;
+ LinkedSlotVolatile[]? slotArray = ts_slotArray;
int id = ~_idComplement;
- LinkedSlot slot;
+ LinkedSlot? slot;
if (slotArray == null || id >= slotArray.Length || (slot = slotArray[id].Value) == null || !_initialized)
- return default;
+ return default!; // TODO-NULLABILITY: Need a way to annotate T
return slot._value;
}
}
/// <summary>Gets the values of all threads that accessed the ThreadLocal<T>.</summary>
- internal List<T> ValuesForDebugDisplay // same as Values property, but doesn't throw if disposed
+ internal List<T>? ValuesForDebugDisplay // same as Values property, but doesn't throw if disposed
{
get { return GetValuesAsList(); }
}
{
for (int i = 0; i < table.Length; i++)
{
- LinkedSlot linkedSlot = table[i].Value;
+ LinkedSlot? linkedSlot = table[i].Value;
if (linkedSlot != null && linkedSlot._slotArray != null)
{
linkedSlot._slotArray = newTable;
/// </summary>
private struct LinkedSlotVolatile
{
- internal volatile LinkedSlot Value;
+ internal volatile LinkedSlot? Value;
}
/// <summary>
/// </summary>
private sealed class LinkedSlot
{
- internal LinkedSlot(LinkedSlotVolatile[] slotArray)
+ internal LinkedSlot(LinkedSlotVolatile[]? slotArray)
{
_slotArray = slotArray;
}
// The next LinkedSlot for this ThreadLocal<> instance
- internal volatile LinkedSlot _next;
+ internal volatile LinkedSlot? _next;
// The previous LinkedSlot for this ThreadLocal<> instance
- internal volatile LinkedSlot _previous;
+ internal volatile LinkedSlot? _previous;
// The SlotArray that stores this LinkedSlot at SlotArray.Table[id].
- internal volatile LinkedSlotVolatile[] _slotArray;
+ internal volatile LinkedSlotVolatile[]? _slotArray;
// The value for this slot.
- internal T _value;
+ internal T _value = default!; // TODO-NULLABILITY: should not need to initialize this to default!
}
/// <summary>
for (int i = 0; i < slotArray.Length; i++)
{
- LinkedSlot linkedSlot = slotArray[i].Value;
+ LinkedSlot? linkedSlot = slotArray[i].Value;
if (linkedSlot == null)
{
// This slot in the table is empty
public T Value => _tlocal.ValueForDebugDisplay;
/// <summary>Return all values for all threads that have accessed this instance.</summary>
- public List<T> Values => _tlocal.ValuesForDebugDisplay;
+ public List<T>? Values => _tlocal.ValuesForDebugDisplay;
}
}