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