From 9c0a2a989a0e6f0f27eeac1d41887427f385a461 Mon Sep 17 00:00:00 2001
From: Paul Westcott
Date: Sat, 4 Mar 2017 14:14:02 +1100
Subject: [PATCH] Alternative System.Lazy implementation (dotnet/coreclr#8963)
* Remove use of lock for PublicationOnly
This involves a little bit of trickery. In avoiding the lock we need to
go from m_implementation as PublicationOnly to null in a single step. We
can't do this though without another object, so we reuse out Lazy
object, which basically just spins its wheels until the value is ready.
This shouldn't be a very common scenerio (i.e. hitting that code path)
but it is possible.
* Minimize additional object creation
Storing factory in Lazy object, and passing Lazy as a parameter to
ILazyItem functions. This means that None & PublicationOnly now need no
creation of secondary object.
* Remove Func for default constructor invoking
Fixing startup performance concerns
* Moved non-generic functionality out of Lazy
...and into a helper class
* Expression-bodied functions
Commit migrated from https://github.com/dotnet/coreclr/commit/2453adf1afba45dec79c6c643623b656702a17de
---
src/coreclr/src/mscorlib/src/System/Lazy.cs | 546 ++++++++++++++++------------
1 file changed, 306 insertions(+), 240 deletions(-)
diff --git a/src/coreclr/src/mscorlib/src/System/Lazy.cs b/src/coreclr/src/mscorlib/src/System/Lazy.cs
index 446ec30..58fdd4a 100644
--- a/src/coreclr/src/mscorlib/src/System/Lazy.cs
+++ b/src/coreclr/src/mscorlib/src/System/Lazy.cs
@@ -26,84 +26,190 @@ using System.Runtime.ExceptionServices;
namespace System
{
- // Lazy is generic, but not all of its state needs to be generic. Avoid creating duplicate
- // objects per instantiation by putting them here.
- internal static class LazyHelpers
+ internal enum LazyState
{
- // Dummy object used as the value of m_threadSafeObj if in PublicationOnly mode.
- internal static readonly object PUBLICATION_ONLY_SENTINEL = new object();
+ NoneViaConstructor = 0,
+ NoneViaFactory = 1,
+ NoneException = 2,
+
+ PublicationOnlyViaConstructor = 3,
+ PublicationOnlyViaFactory = 4,
+ PublicationOnlyWait = 5,
+ PublicationOnlyException = 6,
+
+ ExecutionAndPublicationViaConstructor = 7,
+ ExecutionAndPublicationViaFactory = 8,
+ ExecutionAndPublicationException = 9,
}
///
- /// Provides support for lazy initialization.
+ /// LazyHelper serves multiples purposes
+ /// - minimizing code size of Lazy<T> by implementing as much of the code that is not generic
+ /// this reduces generic code bloat, making faster class initialization
+ /// - contains singleton objects that are used to handle threading primitives for PublicationOnly mode
+ /// - allows for instantiation for ExecutionAndPublication so as to create an object for locking on
+ /// - holds exception information.
///
- /// Specifies the type of element being lazily initialized.
- ///
- ///
- /// By default, all public and protected members of are thread-safe and may be used
- /// concurrently from multiple threads. These thread-safety guarantees may be removed optionally and per instance
- /// using parameters to the type's constructors.
- ///
- ///
- [Serializable]
- [DebuggerTypeProxy(typeof(System_LazyDebugView<>))]
- [DebuggerDisplay("ThreadSafetyMode={Mode}, IsValueCreated={IsValueCreated}, IsValueFaulted={IsValueFaulted}, Value={ValueForDebugDisplay}")]
- public class Lazy
+ internal class LazyHelper
{
- #region Inner classes
+ internal readonly static LazyHelper NoneViaConstructor = new LazyHelper(LazyState.NoneViaConstructor);
+ internal readonly static LazyHelper NoneViaFactory = new LazyHelper(LazyState.NoneViaFactory);
+ internal readonly static LazyHelper PublicationOnlyViaConstructor = new LazyHelper(LazyState.PublicationOnlyViaConstructor);
+ internal readonly static LazyHelper PublicationOnlyViaFactory = new LazyHelper(LazyState.PublicationOnlyViaFactory);
+ internal readonly static LazyHelper PublicationOnlyWaitForOtherThreadToPublish = new LazyHelper(LazyState.PublicationOnlyWait);
+
+ internal LazyState State { get; }
+
+ private readonly ExceptionDispatchInfo _exceptionDispatch;
+
///
- /// wrapper class to box the initialized value, this is mainly created to avoid boxing/unboxing the value each time the value is called in case T is
- /// a value type
+ /// Constructor that defines the state
///
- [Serializable]
- private class Boxed
+ internal LazyHelper(LazyState state)
{
- internal Boxed(T value)
+ State = state;
+ }
+
+ ///
+ /// Constructor used for exceptions
+ ///
+ internal LazyHelper(LazyThreadSafetyMode mode, Exception exception)
+ {
+ switch(mode)
{
- m_value = value;
+ case LazyThreadSafetyMode.ExecutionAndPublication:
+ State = LazyState.ExecutionAndPublicationException;
+ break;
+
+ case LazyThreadSafetyMode.None:
+ State = LazyState.NoneException;
+ break;
+
+ case LazyThreadSafetyMode.PublicationOnly:
+ State = LazyState.PublicationOnlyException;
+ break;
+
+ default:
+ Debug.Assert(false, "internal constructor, this should never occur");
+ break;
}
- internal T m_value;
+
+ _exceptionDispatch = ExceptionDispatchInfo.Capture(exception);
}
+ internal void ThrowException()
+ {
+ Debug.Assert(_exceptionDispatch != null, "execution path is invalid");
- ///
- /// Wrapper class to wrap the excpetion thrown by the value factory
- ///
- private class LazyInternalExceptionHolder
+ _exceptionDispatch.Throw();
+ }
+
+ private LazyThreadSafetyMode GetMode()
{
- internal ExceptionDispatchInfo m_edi;
- internal LazyInternalExceptionHolder(Exception ex)
+ switch (State)
{
- m_edi = ExceptionDispatchInfo.Capture(ex);
+ case LazyState.NoneViaConstructor:
+ case LazyState.NoneViaFactory:
+ case LazyState.NoneException:
+ return LazyThreadSafetyMode.None;
+
+ case LazyState.PublicationOnlyViaConstructor:
+ case LazyState.PublicationOnlyViaFactory:
+ case LazyState.PublicationOnlyWait:
+ case LazyState.PublicationOnlyException:
+ return LazyThreadSafetyMode.PublicationOnly;
+
+ case LazyState.ExecutionAndPublicationViaConstructor:
+ case LazyState.ExecutionAndPublicationViaFactory:
+ case LazyState.ExecutionAndPublicationException:
+ return LazyThreadSafetyMode.ExecutionAndPublication;
+
+ default:
+ Debug.Assert(false, "Invalid logic; State should always have a valid value");
+ return default(LazyThreadSafetyMode);
}
}
- #endregion
- // A dummy delegate used as a :
- // 1- Flag to avoid recursive call to Value in None and ExecutionAndPublication modes in m_valueFactory
- // 2- Flag to m_threadSafeObj if ExecutionAndPublication mode and the value is known to be initialized
- private static readonly Func ALREADY_INVOKED_SENTINEL = delegate
+ internal static LazyThreadSafetyMode? GetMode(LazyHelper state)
{
- Debug.Assert(false, "ALREADY_INVOKED_SENTINEL should never be invoked.");
- return default(T);
- };
+ if (state == null)
+ return null; // we don't know the mode anymore
+ return state.GetMode();
+ }
+
+ internal static bool GetIsValueFaulted(LazyHelper state) => state?._exceptionDispatch != null;
- //null --> value is not created
- //m_value is Boxed --> the value is created, and m_value holds the value
- //m_value is LazyExceptionHolder --> it holds an exception
- private object m_boxed;
+ internal static LazyHelper Create(LazyThreadSafetyMode mode, bool useDefaultConstructor)
+ {
+ switch (mode)
+ {
+ case LazyThreadSafetyMode.None:
+ return useDefaultConstructor ? NoneViaConstructor : NoneViaFactory;
+
+ case LazyThreadSafetyMode.PublicationOnly:
+ return useDefaultConstructor ? PublicationOnlyViaConstructor : PublicationOnlyViaFactory;
+
+ case LazyThreadSafetyMode.ExecutionAndPublication:
+ // we need to create an object for ExecutionAndPublication because we use Monitor-based locking
+ var state = useDefaultConstructor ? LazyState.ExecutionAndPublicationViaConstructor : LazyState.ExecutionAndPublicationViaFactory;
+ return new LazyHelper(state);
+
+ default:
+ throw new ArgumentOutOfRangeException(nameof(mode), Environment.GetResourceString("Lazy_ctor_ModeInvalid"));
+ }
+ }
- // The factory delegate that returns the value.
- // In None and ExecutionAndPublication modes, this will be set to ALREADY_INVOKED_SENTINEL as a flag to avoid recursive calls
+ internal static object CreateViaDefaultConstructor(Type type)
+ {
+ try
+ {
+ return Activator.CreateInstance(type);
+ }
+ catch (MissingMethodException)
+ {
+ throw new MissingMemberException(Environment.GetResourceString("Lazy_CreateValue_NoParameterlessCtorForT"));
+ }
+ }
+
+ internal static LazyThreadSafetyMode GetModeFromIsThreadSafe(bool isThreadSafe)
+ {
+ return isThreadSafe ? LazyThreadSafetyMode.ExecutionAndPublication : LazyThreadSafetyMode.None;
+ }
+ }
+
+ ///
+ /// Provides support for lazy initialization.
+ ///
+ /// Specifies the type of element being lazily initialized.
+ ///
+ ///
+ /// By default, all public and protected members of are thread-safe and may be used
+ /// concurrently from multiple threads. These thread-safety guarantees may be removed optionally and per instance
+ /// using parameters to the type's constructors.
+ ///
+ ///
+ [Serializable]
+ [ComVisible(false)]
+ [DebuggerTypeProxy(typeof(System_LazyDebugView<>))]
+ [DebuggerDisplay("ThreadSafetyMode={Mode}, IsValueCreated={IsValueCreated}, IsValueFaulted={IsValueFaulted}, Value={ValueForDebugDisplay}")]
+ public class Lazy
+ {
+ private static T CreateViaDefaultConstructor()
+ {
+ return (T)LazyHelper.CreateViaDefaultConstructor(typeof(T));
+ }
+
+ // _state, a volatile reference, is set to null after m_value has been set
[NonSerialized]
- private Func m_valueFactory;
+ private volatile LazyHelper _state;
- // null if it is not thread safe mode
- // LazyHelpers.PUBLICATION_ONLY_SENTINEL if PublicationOnly mode
- // object if ExecutionAndPublication mode (may be ALREADY_INVOKED_SENTINEL if the value is already initialized)
+ // we ensure that _factory when finished is set to null to allow garbage collector to clean up
+ // any referenced items
[NonSerialized]
- private object m_threadSafeObj;
+ private Func _factory;
+ // m_value eventually stores the lazily created value. It is valid when _state = null.
+ private T _value;
///
/// Initializes a new instance of the class that
@@ -113,7 +219,7 @@ namespace System
/// An instance created with this constructor may be used concurrently from multiple threads.
///
public Lazy()
- : this(LazyThreadSafetyMode.ExecutionAndPublication)
+ : this(null, LazyThreadSafetyMode.ExecutionAndPublication, useDefaultConstructor:true)
{
}
@@ -127,7 +233,7 @@ namespace System
///
public Lazy(T value)
{
- m_boxed = new Boxed(value);
+ _value = value;
}
///
@@ -144,7 +250,7 @@ namespace System
/// An instance created with this constructor may be used concurrently from multiple threads.
///
public Lazy(Func valueFactory)
- : this(valueFactory, LazyThreadSafetyMode.ExecutionAndPublication)
+ : this(valueFactory, LazyThreadSafetyMode.ExecutionAndPublication, useDefaultConstructor:false)
{
}
@@ -155,7 +261,7 @@ namespace System
/// true if this instance should be usable by multiple threads concurrently; false if the instance will only be used by one thread at a time.
///
public Lazy(bool isThreadSafe) :
- this(isThreadSafe ? LazyThreadSafetyMode.ExecutionAndPublication : LazyThreadSafetyMode.None)
+ this(null, LazyHelper.GetModeFromIsThreadSafe(isThreadSafe), useDefaultConstructor:true)
{
}
@@ -165,12 +271,11 @@ namespace System
///
/// The lazy thread-safety mode mode
/// mode contains an invalid valuee
- public Lazy(LazyThreadSafetyMode mode)
+ public Lazy(LazyThreadSafetyMode mode) :
+ this(null, mode, useDefaultConstructor:true)
{
- m_threadSafeObj = GetObjectFromMode(mode);
}
-
///
/// Initializes a new instance of the class
/// that uses a specified initialization function and a specified thread-safety mode.
@@ -182,8 +287,8 @@ namespace System
///
/// is
/// a null reference (Nothing in Visual Basic).
- public Lazy(Func valueFactory, bool isThreadSafe)
- : this(valueFactory, isThreadSafe ? LazyThreadSafetyMode.ExecutionAndPublication : LazyThreadSafetyMode.None)
+ public Lazy(Func valueFactory, bool isThreadSafe) :
+ this(valueFactory, LazyHelper.GetModeFromIsThreadSafe(isThreadSafe), useDefaultConstructor:false)
{
}
@@ -199,27 +304,147 @@ namespace System
/// a null reference (Nothing in Visual Basic).
/// mode contains an invalid value.
public Lazy(Func valueFactory, LazyThreadSafetyMode mode)
+ : this(valueFactory, mode, useDefaultConstructor:false)
{
- if (valueFactory == null)
+ }
+
+ private Lazy(Func valueFactory, LazyThreadSafetyMode mode, bool useDefaultConstructor)
+ {
+ if (valueFactory == null && !useDefaultConstructor)
throw new ArgumentNullException(nameof(valueFactory));
- m_threadSafeObj = GetObjectFromMode(mode);
- m_valueFactory = valueFactory;
+ _factory = valueFactory;
+ _state = LazyHelper.Create(mode, useDefaultConstructor);
}
- ///
- /// Static helper function that returns an object based on the given mode. it also throws an exception if the mode is invalid
- ///
- private static object GetObjectFromMode(LazyThreadSafetyMode mode)
+ private void ViaConstructor()
+ {
+ _value = CreateViaDefaultConstructor();
+ _state = null; // volatile write, must occur after setting _value
+ }
+
+ private void ViaFactory(LazyThreadSafetyMode mode)
+ {
+ try
+ {
+ Func factory = _factory;
+ if (factory == null)
+ throw new InvalidOperationException(Environment.GetResourceString("Lazy_Value_RecursiveCallsToValue"));
+ _factory = null;
+
+ _value = factory();
+ _state = null; // volatile write, must occur after setting _value
+ }
+ catch (Exception exception)
+ {
+ _state = new LazyHelper(mode, exception);
+ throw;
+ }
+ }
+
+ private void ExecutionAndPublication(LazyHelper executionAndPublication, bool useDefaultConstructor)
+ {
+ lock (executionAndPublication)
+ {
+ // it's possible for multiple calls to have piled up behind the lock, so we need to check
+ // to see if the ExecutionAndPublication object is still the current implementation.
+ if (ReferenceEquals(_state, executionAndPublication))
+ {
+ if (useDefaultConstructor)
+ {
+ ViaConstructor();
+ }
+ else
+ {
+ ViaFactory(LazyThreadSafetyMode.ExecutionAndPublication);
+ }
+ }
+ }
+ }
+
+ private void PublicationOnly(LazyHelper publicationOnly, T possibleValue)
+ {
+ LazyHelper previous = Interlocked.CompareExchange(ref _state, LazyHelper.PublicationOnlyWaitForOtherThreadToPublish, publicationOnly);
+ if (previous == publicationOnly)
+ {
+ _factory = null;
+ _value = possibleValue;
+ _state = null; // volatile write, must occur after setting _value
+ }
+ }
+
+ private void PublicationOnlyViaConstructor(LazyHelper initializer)
{
- if (mode == LazyThreadSafetyMode.ExecutionAndPublication)
- return new object();
- else if (mode == LazyThreadSafetyMode.PublicationOnly)
- return LazyHelpers.PUBLICATION_ONLY_SENTINEL;
- else if (mode != LazyThreadSafetyMode.None)
- throw new ArgumentOutOfRangeException(nameof(mode), Environment.GetResourceString("Lazy_ctor_ModeInvalid"));
+ PublicationOnly(initializer, CreateViaDefaultConstructor());
+ }
- return null; // None mode
+ private void PublicationOnlyViaFactory(LazyHelper initializer)
+ {
+ Func factory = _factory;
+ if (factory == null)
+ {
+ PublicationOnlyWaitForOtherThreadToPublish();
+ }
+ else
+ {
+ PublicationOnly(initializer, factory());
+ }
+ }
+
+ private void PublicationOnlyWaitForOtherThreadToPublish()
+ {
+ var spinWait = new SpinWait();
+ while (!ReferenceEquals(_state, null))
+ {
+ // We get here when PublicationOnly temporarily sets _state to LazyHelper.PublicationOnlyWaitForOtherThreadToPublish.
+ // This temporary state should be quickly followed by _state being set to null.
+ spinWait.SpinOnce();
+ }
+ }
+
+ private T CreateValue()
+ {
+ // we have to create a copy of state here, and use the copy exclusively from here on in
+ // so as to ensure thread safety.
+ var state = _state;
+ if (state != null)
+ {
+ switch (state.State)
+ {
+ case LazyState.NoneViaConstructor:
+ ViaConstructor();
+ break;
+
+ case LazyState.NoneViaFactory:
+ ViaFactory(LazyThreadSafetyMode.None);
+ break;
+
+ case LazyState.PublicationOnlyViaConstructor:
+ PublicationOnlyViaConstructor(state);
+ break;
+
+ case LazyState.PublicationOnlyViaFactory:
+ PublicationOnlyViaFactory(state);
+ break;
+
+ case LazyState.PublicationOnlyWait:
+ PublicationOnlyWaitForOtherThreadToPublish();
+ break;
+
+ case LazyState.ExecutionAndPublicationViaConstructor:
+ ExecutionAndPublication(state, useDefaultConstructor:true);
+ break;
+
+ case LazyState.ExecutionAndPublicationViaFactory:
+ ExecutionAndPublication(state, useDefaultConstructor:false);
+ break;
+
+ default:
+ state.ThrowException();
+ break;
+ }
+ }
+ return Value;
}
/// Forces initialization during serialization.
@@ -251,30 +476,19 @@ namespace System
{
return default(T);
}
- return ((Boxed)m_boxed).m_value;
+ return _value;
}
}
///
/// Gets a value indicating whether this instance may be used concurrently from multiple threads.
///
- internal LazyThreadSafetyMode Mode
- {
- get
- {
- if (m_threadSafeObj == null) return LazyThreadSafetyMode.None;
- if (m_threadSafeObj == (object)LazyHelpers.PUBLICATION_ONLY_SENTINEL) return LazyThreadSafetyMode.PublicationOnly;
- return LazyThreadSafetyMode.ExecutionAndPublication;
- }
- }
+ internal LazyThreadSafetyMode? Mode => LazyHelper.GetMode(_state);
///
/// Gets whether the value creation is faulted or not
///
- internal bool IsValueFaulted
- {
- get { return m_boxed is LazyInternalExceptionHolder; }
- }
+ internal bool IsValueFaulted => LazyHelper.GetIsValueFaulted(_state);
/// Gets a value indicating whether the has been initialized.
///
@@ -285,13 +499,7 @@ namespace System
/// a value being produced or an exception being thrown. If an exception goes unhandled during initialization,
/// will return false.
///
- public bool IsValueCreated
- {
- get
- {
- return m_boxed != null && m_boxed is Boxed;
- }
- }
+ public bool IsValueCreated => _state == null;
/// Gets the lazily initialized value of the current .
@@ -315,149 +523,7 @@ namespace System
/// from initialization delegate.
///
[DebuggerBrowsable(DebuggerBrowsableState.Never)]
- public T Value
- {
- get
- {
- Boxed boxed = null;
- if (m_boxed != null)
- {
- // Do a quick check up front for the fast path.
- boxed = m_boxed as Boxed;
- if (boxed != null)
- {
- return boxed.m_value;
- }
-
- LazyInternalExceptionHolder exc = m_boxed as LazyInternalExceptionHolder;
- Debug.Assert(exc != null);
- exc.m_edi.Throw();
- }
-
- // Fall through to the slow path.
- return LazyInitValue();
- }
- }
-
- ///
- /// local helper method to initialize the value
- ///
- /// The inititialized T value
- private T LazyInitValue()
- {
- Boxed boxed = null;
- LazyThreadSafetyMode mode = Mode;
- if (mode == LazyThreadSafetyMode.None)
- {
- boxed = CreateValue();
- m_boxed = boxed;
- }
- else if (mode == LazyThreadSafetyMode.PublicationOnly)
- {
- boxed = CreateValue();
- if (boxed == null ||
- Interlocked.CompareExchange(ref m_boxed, boxed, null) != null)
- {
- // If CreateValue returns null, it means another thread successfully invoked the value factory
- // and stored the result, so we should just take what was stored. If CreateValue returns non-null
- // but another thread set the value we should just take what was stored.
- boxed = (Boxed)m_boxed;
- }
- else
- {
- // We successfully created and stored the value. At this point, the value factory delegate is
- // no longer needed, and we don't want to hold onto its resources.
- m_valueFactory = ALREADY_INVOKED_SENTINEL;
- }
- }
- else
- {
- object threadSafeObj = Volatile.Read(ref m_threadSafeObj);
- bool lockTaken = false;
- try
- {
- if (threadSafeObj != (object)ALREADY_INVOKED_SENTINEL)
- Monitor.Enter(threadSafeObj, ref lockTaken);
- else
- Debug.Assert(m_boxed != null);
-
- if (m_boxed == null)
- {
- boxed = CreateValue();
- m_boxed = boxed;
- Volatile.Write(ref m_threadSafeObj, ALREADY_INVOKED_SENTINEL);
- }
- else // got the lock but the value is not null anymore, check if it is created by another thread or faulted and throw if so
- {
- boxed = m_boxed as Boxed;
- if (boxed == null) // it is not Boxed, so it is a LazyInternalExceptionHolder
- {
- LazyInternalExceptionHolder exHolder = m_boxed as LazyInternalExceptionHolder;
- Debug.Assert(exHolder != null);
- exHolder.m_edi.Throw();
- }
- }
- }
- finally
- {
- if (lockTaken)
- Monitor.Exit(threadSafeObj);
- }
- }
- Debug.Assert(boxed != null);
- return boxed.m_value;
- }
-
- /// Creates an instance of T using m_valueFactory in case its not null or use reflection to create a new T()
- /// An instance of Boxed.
- private Boxed CreateValue()
- {
- Boxed boxed = null;
- LazyThreadSafetyMode mode = Mode;
- if (m_valueFactory != null)
- {
- try
- {
- // check for recursion
- if (mode != LazyThreadSafetyMode.PublicationOnly && m_valueFactory == ALREADY_INVOKED_SENTINEL)
- throw new InvalidOperationException(Environment.GetResourceString("Lazy_Value_RecursiveCallsToValue"));
-
- Func factory = m_valueFactory;
- if (mode != LazyThreadSafetyMode.PublicationOnly) // only detect recursion on None and ExecutionAndPublication modes
- {
- m_valueFactory = ALREADY_INVOKED_SENTINEL;
- }
- else if (factory == ALREADY_INVOKED_SENTINEL)
- {
- // Another thread raced to successfully invoke the factory.
- return null;
- }
- boxed = new Boxed(factory());
- }
- catch (Exception ex)
- {
- if (mode != LazyThreadSafetyMode.PublicationOnly) // don't cache the exception for PublicationOnly mode
- m_boxed = new LazyInternalExceptionHolder(ex);
- throw;
- }
- }
- else
- {
- try
- {
- boxed = new Boxed((T)Activator.CreateInstance(typeof(T)));
- }
- catch (System.MissingMethodException)
- {
- Exception ex = new System.MissingMemberException(Environment.GetResourceString("Lazy_CreateValue_NoParameterlessCtorForT"));
- if (mode != LazyThreadSafetyMode.PublicationOnly) // don't cache the exception for PublicationOnly mode
- m_boxed = new LazyInternalExceptionHolder(ex);
- throw ex;
- }
- }
-
- return boxed;
- }
+ public T Value => _state == null ? _value : CreateValue();
}
/// A debugger view of the Lazy<T> to surface additional debugging properties and
@@ -488,7 +554,7 @@ namespace System
}
/// Returns the execution mode of the Lazy object
- public LazyThreadSafetyMode Mode
+ public LazyThreadSafetyMode? Mode
{
get { return m_lazy.Mode; }
}
--
2.7.4