From 63cd880f45c7bf294e721cae43561e19b1166d97 Mon Sep 17 00:00:00 2001 From: Stephen Toub Date: Mon, 21 Nov 2016 07:36:15 -0500 Subject: [PATCH] Address PR feedback - Delete !FEATURE_CORECLR code from ExecutionContext - Add support for downgrading between map types when values are set to null - Change ManyElementAsyncLocalValueMap.Set to size the dictionary based on whether the key is in the existing map Commit migrated from https://github.com/dotnet/coreclr/commit/bc8d6117d76d6e359aaaecad99415c8d96576e3d --- .../mscorlib/src/System/Threading/AsyncLocal.cs | 196 +++- .../src/System/Threading/ExecutionContext.cs | 1086 -------------------- 2 files changed, 168 insertions(+), 1114 deletions(-) diff --git a/src/coreclr/src/mscorlib/src/System/Threading/AsyncLocal.cs b/src/coreclr/src/mscorlib/src/System/Threading/AsyncLocal.cs index e079e7b..bbfb203 100644 --- a/src/coreclr/src/mscorlib/src/System/Threading/AsyncLocal.cs +++ b/src/coreclr/src/mscorlib/src/System/Threading/AsyncLocal.cs @@ -137,7 +137,14 @@ namespace System.Threading // Instance without any key/value pairs. Used as a singleton/ private sealed class EmptyAsyncLocalValueMap : IAsyncLocalValueMap { - public IAsyncLocalValueMap Set(IAsyncLocal key, object value) => new OneElementAsyncLocalValueMap(key, value); + public IAsyncLocalValueMap Set(IAsyncLocal key, object value) + { + // If the value isn't null, then create a new one-element map to store + // the key/value pair. If it is null, then we're still empty. + return value != null ? + new OneElementAsyncLocalValueMap(key, value) : + (IAsyncLocalValueMap)this; + } public bool TryGetValue(IAsyncLocal key, out object value) { @@ -159,9 +166,23 @@ namespace System.Threading public IAsyncLocalValueMap Set(IAsyncLocal key, object value) { - return ReferenceEquals(key, _key1) ? - new OneElementAsyncLocalValueMap(key, value) : - (IAsyncLocalValueMap)new TwoElementAsyncLocalValueMap(_key1, _value1, key, value); + if (value != null) + { + // The value is non-null. If the key matches one already contained in this map, + // then create a new one-element map with the updated value, otherwise create + // a two-element map with the additional key/value. + return ReferenceEquals(key, _key1) ? + new OneElementAsyncLocalValueMap(key, value) : + (IAsyncLocalValueMap)new TwoElementAsyncLocalValueMap(_key1, _value1, key, value); + } + else + { + // The value is null. If the key exists in this map, remove it by downgrading to an empty map. + // Otherwise, there's nothing to add or remove, so just return this map. + return ReferenceEquals(key, _key1) ? + Empty : + (IAsyncLocalValueMap)this; + } } public bool TryGetValue(IAsyncLocal key, out object value) @@ -193,10 +214,25 @@ namespace System.Threading public IAsyncLocalValueMap Set(IAsyncLocal key, object value) { - return - ReferenceEquals(key, _key1) ? new TwoElementAsyncLocalValueMap(key, value, _key2, _value2) : - ReferenceEquals(key, _key2) ? new TwoElementAsyncLocalValueMap(_key1, _value1, key, value) : - (IAsyncLocalValueMap)new ThreeElementAsyncLocalValueMap(_key1, _value1, _key2, _value2, key, value); + if (value != null) + { + // The value is non-null. If the key matches one already contained in this map, + // then create a new two-element map with the updated value, otherwise create + // a three-element map with the additional key/value. + return + ReferenceEquals(key, _key1) ? new TwoElementAsyncLocalValueMap(key, value, _key2, _value2) : + ReferenceEquals(key, _key2) ? new TwoElementAsyncLocalValueMap(_key1, _value1, key, value) : + (IAsyncLocalValueMap)new ThreeElementAsyncLocalValueMap(_key1, _value1, _key2, _value2, key, value); + } + else + { + // The value is null. If the key exists in this map, remove it by downgrading to a one-element map + // without the key. Otherwise, there's nothing to add or remove, so just return this map. + return + ReferenceEquals(key, _key1) ? new OneElementAsyncLocalValueMap(_key2, _value2) : + ReferenceEquals(key, _key2) ? new OneElementAsyncLocalValueMap(_key1, _value1) : + (IAsyncLocalValueMap)this; + } } public bool TryGetValue(IAsyncLocal key, out object value) @@ -234,16 +270,33 @@ namespace System.Threading public IAsyncLocalValueMap Set(IAsyncLocal key, object value) { - if (ReferenceEquals(key, _key1)) return new ThreeElementAsyncLocalValueMap(key, value, _key2, _value2, _key3, _value3); - if (ReferenceEquals(key, _key2)) return new ThreeElementAsyncLocalValueMap(_key1, _value1, key, value, _key3, _value3); - if (ReferenceEquals(key, _key3)) return new ThreeElementAsyncLocalValueMap(_key1, _value1, _key2, _value2, key, value); - - var multi = new MultiElementAsyncLocalValueMap(4); - multi.UnsafeStore(0, _key1, _value1); - multi.UnsafeStore(1, _key2, _value2); - multi.UnsafeStore(2, _key3, _value3); - multi.UnsafeStore(3, key, value); - return multi; + if (value != null) + { + // The value is non-null. If the key matches one already contained in this map, + // then create a new three-element map with the updated value. + if (ReferenceEquals(key, _key1)) return new ThreeElementAsyncLocalValueMap(key, value, _key2, _value2, _key3, _value3); + if (ReferenceEquals(key, _key2)) return new ThreeElementAsyncLocalValueMap(_key1, _value1, key, value, _key3, _value3); + if (ReferenceEquals(key, _key3)) return new ThreeElementAsyncLocalValueMap(_key1, _value1, _key2, _value2, key, value); + + // The key doesn't exist in this map, so upgrade to a multi map that contains + // the additional key/value pair. + var multi = new MultiElementAsyncLocalValueMap(4); + multi.UnsafeStore(0, _key1, _value1); + multi.UnsafeStore(1, _key2, _value2); + multi.UnsafeStore(2, _key3, _value3); + multi.UnsafeStore(3, key, value); + return multi; + } + else + { + // The value is null. If the key exists in this map, remove it by downgrading to a two-element map + // without the key. Otherwise, there's nothing to add or remove, so just return this map. + return + ReferenceEquals(key, _key1) ? new TwoElementAsyncLocalValueMap(_key2, _value2, _key3, _value3) : + ReferenceEquals(key, _key2) ? new TwoElementAsyncLocalValueMap(_key1, _value1, _key3, _value3) : + ReferenceEquals(key, _key3) ? new TwoElementAsyncLocalValueMap(_key1, _value1, _key2, _value2) : + (IAsyncLocalValueMap)this; + } } public bool TryGetValue(IAsyncLocal key, out object value) @@ -274,7 +327,7 @@ namespace System.Threading // Instance with up to 16 key/value pairs. private sealed class MultiElementAsyncLocalValueMap : IAsyncLocalValueMap { - private const int MaxMultiElements = 16; + internal const int MaxMultiElements = 16; private readonly KeyValuePair[] _keyValues; internal MultiElementAsyncLocalValueMap(int count) @@ -291,17 +344,52 @@ namespace System.Threading public IAsyncLocalValueMap Set(IAsyncLocal key, object value) { + // Find the key in this map. for (int i = 0; i < _keyValues.Length; i++) { if (ReferenceEquals(key, _keyValues[i].Key)) { - var multi = new MultiElementAsyncLocalValueMap(_keyValues.Length); - Array.Copy(_keyValues, 0, multi._keyValues, 0, _keyValues.Length); - multi._keyValues[i] = new KeyValuePair(key, value); - return multi; + // The key is in the map. If the value isn't null, then create a new map of the same + // size that has all of the same pairs, with this new key/value pair overwriting the old. + if (value != null) + { + var multi = new MultiElementAsyncLocalValueMap(_keyValues.Length); + Array.Copy(_keyValues, 0, multi._keyValues, 0, _keyValues.Length); + multi._keyValues[i] = new KeyValuePair(key, value); + return multi; + } + else if (_keyValues.Length == 4) + { + // The value is null, and we only have four elements, one of which we're removing, + // so downgrade to a three-element map, without the matching element. + return + i == 0 ? new ThreeElementAsyncLocalValueMap(_keyValues[1].Key, _keyValues[1].Value, _keyValues[2].Key, _keyValues[2].Value, _keyValues[3].Key, _keyValues[3].Value) : + i == 1 ? new ThreeElementAsyncLocalValueMap(_keyValues[0].Key, _keyValues[0].Value, _keyValues[2].Key, _keyValues[2].Value, _keyValues[3].Key, _keyValues[3].Value) : + i == 2 ? new ThreeElementAsyncLocalValueMap(_keyValues[0].Key, _keyValues[0].Value, _keyValues[1].Key, _keyValues[1].Value, _keyValues[3].Key, _keyValues[3].Value) : + (IAsyncLocalValueMap)new ThreeElementAsyncLocalValueMap(_keyValues[0].Key, _keyValues[0].Value, _keyValues[1].Key, _keyValues[1].Value, _keyValues[2].Key, _keyValues[2].Value); + } + else + { + // The value is null, and we have enough elements remaining to warrant a multi map. + // Create a new one and copy all of the elements from this one, except the one to be removed. + var multi = new MultiElementAsyncLocalValueMap(_keyValues.Length - 1); + if (i != 0) Array.Copy(_keyValues, 0, multi._keyValues, 0, i); + if (i != _keyValues.Length - 1) Array.Copy(_keyValues, i + 1, multi._keyValues, i, _keyValues.Length - i - 1); + return multi; + } } } + // The key does not already exist in this map. + + // If the value is null, then we can simply return this same map, as there's nothing to add or remove. + if (value == null) + { + return this; + } + + // We need to create a new map that has the additional key/value pair. + // If with the addition we can still fit in a multi map, create one. if (_keyValues.Length < MaxMultiElements) { var multi = new MultiElementAsyncLocalValueMap(_keyValues.Length + 1); @@ -310,6 +398,7 @@ namespace System.Threading return multi; } + // Otherwise, upgrade to a many map. var many = new ManyElementAsyncLocalValueMap(MaxMultiElements + 1); foreach (KeyValuePair pair in _keyValues) { @@ -341,13 +430,64 @@ namespace System.Threading public IAsyncLocalValueMap Set(IAsyncLocal key, object value) { - var map = new ManyElementAsyncLocalValueMap(Count); - foreach (KeyValuePair pair in this) + int count = Count; + bool containsKey = ContainsKey(key); + + // If the value being set exists, create a new many map, copy all of the elements from this one, + // and then store the new key/value pair into it. This is the most common case. + if (value != null) { - map[pair.Key] = pair.Value; + var map = new ManyElementAsyncLocalValueMap(count + (containsKey ? 0 : 1)); + foreach (KeyValuePair pair in this) + { + map[pair.Key] = pair.Value; + } + map[key] = value; + return map; } - map[key] = value; - return map; + + // Otherwise, the value is null, which means null is being stored into an AsyncLocal.Value. + // Since there's no observable difference at the API level between storing null and the key + // not existing at all, we can downgrade to a smaller map rather than storing null. + + // If the key is contained in this map, we're going to create a new map that's one pair smaller. + if (containsKey) + { + // If the new count would be within range of a multi map instead of a many map, + // downgrade to the many map, which uses less memory and is faster to access. + // Otherwise, just create a new many map that's missing this key. + if (count == MultiElementAsyncLocalValueMap.MaxMultiElements + 1) + { + var multi = new MultiElementAsyncLocalValueMap(MultiElementAsyncLocalValueMap.MaxMultiElements); + int index = 0; + foreach (KeyValuePair pair in this) + { + if (!ReferenceEquals(key, pair.Key)) + { + multi.UnsafeStore(index++, pair.Key, pair.Value); + } + } + Contract.Assert(index == MultiElementAsyncLocalValueMap.MaxMultiElements); + return multi; + } + else + { + var map = new ManyElementAsyncLocalValueMap(count - 1); + foreach (KeyValuePair pair in this) + { + if (!ReferenceEquals(key, pair.Key)) + { + map[pair.Key] = pair.Value; + } + } + Contract.Assert(map.Count == count - 1); + return map; + } + } + + // We were storing null, but the key wasn't in the map, so there's nothing to change. + // Just return this instance. + return this; } } } diff --git a/src/coreclr/src/mscorlib/src/System/Threading/ExecutionContext.cs b/src/coreclr/src/mscorlib/src/System/Threading/ExecutionContext.cs index b7c0e47..7adfca8 100644 --- a/src/coreclr/src/mscorlib/src/System/Threading/ExecutionContext.cs +++ b/src/coreclr/src/mscorlib/src/System/Threading/ExecutionContext.cs @@ -14,32 +14,22 @@ namespace System.Threading using System; using System.Security; using System.Runtime.Remoting; -#if FEATURE_IMPERSONATION - using System.Security.Principal; -#endif using System.Collections; using System.Collections.Generic; using System.Reflection; using System.Runtime.ExceptionServices; using System.Runtime.Serialization; using System.Security.Permissions; -#if FEATURE_REMOTING - using System.Runtime.Remoting.Messaging; -#endif // FEATURE_REMOTING using System.Runtime.InteropServices; using System.Runtime.CompilerServices; using System.Runtime.ConstrainedExecution; using System.Diagnostics.Contracts; using System.Diagnostics.CodeAnalysis; -#if FEATURE_CORECLR [System.Security.SecurityCritical] // auto-generated -#endif [System.Runtime.InteropServices.ComVisible(true)] public delegate void ContextCallback(Object state); -#if FEATURE_CORECLR - [SecurityCritical] internal struct ExecutionContextSwitcher { @@ -451,1082 +441,6 @@ namespace System.Threading return !(a == b); } } - -#else // FEATURE_CORECLR - - // Legacy desktop ExecutionContext implementation - - internal struct ExecutionContextSwitcher - { - internal ExecutionContext.Reader outerEC; // previous EC we need to restore on Undo - internal bool outerECBelongsToScope; -#if FEATURE_IMPERSONATION || FEATURE_COMPRESSEDSTACK - internal SecurityContextSwitcher scsw; -#endif // #if FEATURE_IMPERSONATION || FEATURE_COMPRESSEDSTACK - internal Object hecsw; -#if FEATURE_IMPERSONATION - internal WindowsIdentity wi; - internal bool cachedAlwaysFlowImpersonationPolicy; - internal bool wiIsValid; -#endif - internal Thread thread; - - [System.Security.SecurityCritical] // auto-generated - [ReliabilityContract(Consistency.WillNotCorruptState, Cer.MayFail)] -#if FEATURE_CORRUPTING_EXCEPTIONS - [HandleProcessCorruptedStateExceptions] -#endif // FEATURE_CORRUPTING_EXCEPTIONS - internal bool UndoNoThrow(Thread currentThread) - { - try - { - Undo(currentThread); - } - catch - { - return false; - } - return true; - } - - [System.Security.SecurityCritical] // auto-generated - [ReliabilityContract(Consistency.WillNotCorruptState, Cer.MayFail)] - internal void Undo(Thread currentThread) - { - // - // Don't use an uninitialized switcher, or one that's already been used. - // - if (thread == null) - return; // Don't do anything - - Contract.Assert(Thread.CurrentThread == this.thread); - - // - // Restore the HostExecutionContext before restoring the ExecutionContext. - // -#if FEATURE_CAS_POLICY - if (hecsw != null) - HostExecutionContextSwitcher.Undo(hecsw); -#endif // FEATURE_CAS_POLICY - - // - // restore the saved Execution Context. Note that this will also restore the - // SynchronizationContext, Logical/IllogicalCallContext, etc. - // - ExecutionContext.Reader innerEC = currentThread.GetExecutionContextReader(); - currentThread.SetExecutionContext(outerEC, outerECBelongsToScope); - -#if DEBUG - try - { - currentThread.ForbidExecutionContextMutation = true; -#endif - - // - // Tell the SecurityContext to do the side-effects of restoration. - // -#if FEATURE_IMPERSONATION || FEATURE_COMPRESSEDSTACK - if (scsw.currSC != null) - { - // Any critical failure inside scsw will cause FailFast - scsw.Undo(); - } -#endif // #if FEATURE_IMPERSONATION || FEATURE_COMPRESSEDSTACK - -#if FEATURE_IMPERSONATION - if (wiIsValid) - SecurityContext.RestoreCurrentWI(outerEC, innerEC, wi, cachedAlwaysFlowImpersonationPolicy); -#endif - - thread = null; // this will prevent the switcher object being used again -#if DEBUG - } - finally - { - currentThread.ForbidExecutionContextMutation = false; - } -#endif - ExecutionContext.OnAsyncLocalContextChanged(innerEC.DangerousGetRawExecutionContext(), outerEC.DangerousGetRawExecutionContext()); - } - } - - - public struct AsyncFlowControl: IDisposable - { - private bool useEC; - private ExecutionContext _ec; -#if FEATURE_IMPERSONATION || FEATURE_COMPRESSEDSTACK - private SecurityContext _sc; -#endif // #if FEATURE_IMPERSONATION || FEATURE_COMPRESSEDSTACK - private Thread _thread; -#if FEATURE_IMPERSONATION || FEATURE_COMPRESSEDSTACK - [SecurityCritical] - internal void Setup(SecurityContextDisableFlow flags) - { - useEC = false; - Thread currentThread = Thread.CurrentThread; - _sc = currentThread.GetMutableExecutionContext().SecurityContext; - _sc._disableFlow = flags; - _thread = currentThread; - } -#endif // #if FEATURE_IMPERSONATION || FEATURE_COMPRESSEDSTACK - [SecurityCritical] - internal void Setup() - { - useEC = true; - Thread currentThread = Thread.CurrentThread; - _ec = currentThread.GetMutableExecutionContext(); - _ec.isFlowSuppressed = true; - _thread = currentThread; - } - - public void Dispose() - { - Undo(); - } - - [SecuritySafeCritical] - public void Undo() - { - if (_thread == null) - { - throw new InvalidOperationException(Environment.GetResourceString("InvalidOperation_CannotUseAFCMultiple")); - } - if (_thread != Thread.CurrentThread) - { - throw new InvalidOperationException(Environment.GetResourceString("InvalidOperation_CannotUseAFCOtherThread")); - } - if (useEC) - { - if (Thread.CurrentThread.GetMutableExecutionContext() != _ec) - { - throw new InvalidOperationException(Environment.GetResourceString("InvalidOperation_AsyncFlowCtrlCtxMismatch")); - } - ExecutionContext.RestoreFlow(); - } -#if FEATURE_IMPERSONATION || FEATURE_COMPRESSEDSTACK - else - { - if (!Thread.CurrentThread.GetExecutionContextReader().SecurityContext.IsSame(_sc)) - { - throw new InvalidOperationException(Environment.GetResourceString("InvalidOperation_AsyncFlowCtrlCtxMismatch")); - } - SecurityContext.RestoreFlow(); - } -#endif // #if FEATURE_IMPERSONATION || FEATURE_COMPRESSEDSTACK - _thread = null; - } - - public override int GetHashCode() - { - return _thread == null ? ToString().GetHashCode() : _thread.GetHashCode(); - } - - public override bool Equals(Object obj) - { - if (obj is AsyncFlowControl) - return Equals((AsyncFlowControl)obj); - else - return false; - } - - public bool Equals(AsyncFlowControl obj) - { - return obj.useEC == useEC && obj._ec == _ec && -#if FEATURE_IMPERSONATION || FEATURE_COMPRESSEDSTACK - obj._sc == _sc && -#endif // #if FEATURE_IMPERSONATION || FEATURE_COMPRESSEDSTACK - obj._thread == _thread; - } - - public static bool operator ==(AsyncFlowControl a, AsyncFlowControl b) - { - return a.Equals(b); - } - - public static bool operator !=(AsyncFlowControl a, AsyncFlowControl b) - { - return !(a == b); - } - - } - - -#if FEATURE_SERIALIZATION - [Serializable] -#endif - public sealed class ExecutionContext : IDisposable, ISerializable - { - /*========================================================================= - ** Data accessed from managed code that needs to be defined in - ** ExecutionContextObject to maintain alignment between the two classes. - ** DON'T CHANGE THESE UNLESS YOU MODIFY ExecutionContextObject in vm\object.h - =========================================================================*/ -#if FEATURE_CAS_POLICY - private HostExecutionContext _hostExecutionContext; -#endif // FEATURE_CAS_POLICY - private SynchronizationContext _syncContext; - private SynchronizationContext _syncContextNoFlow; -#if FEATURE_IMPERSONATION || FEATURE_COMPRESSEDSTACK - private SecurityContext _securityContext; -#endif // #if FEATURE_IMPERSONATION || FEATURE_COMPRESSEDSTACK -#if FEATURE_REMOTING - [System.Security.SecurityCritical] // auto-generated - private LogicalCallContext _logicalCallContext; - private IllogicalCallContext _illogicalCallContext; // this call context follows the physical thread -#endif // #if FEATURE_REMOTING - - enum Flags - { - None = 0x0, - IsNewCapture = 0x1, - IsFlowSuppressed = 0x2, - IsPreAllocatedDefault = 0x4 - } - private Flags _flags; - - private IAsyncLocalValueMap _localValues; - private List _localChangeNotifications; - - internal bool isNewCapture - { - get - { - return (_flags & (Flags.IsNewCapture | Flags.IsPreAllocatedDefault)) != Flags.None; - } - set - { - Contract.Assert(!IsPreAllocatedDefault); - if (value) - _flags |= Flags.IsNewCapture; - else - _flags &= ~Flags.IsNewCapture; - } - } - internal bool isFlowSuppressed - { - get - { - return (_flags & Flags.IsFlowSuppressed) != Flags.None; - } - set - { - Contract.Assert(!IsPreAllocatedDefault); - if (value) - _flags |= Flags.IsFlowSuppressed; - else - _flags &= ~Flags.IsFlowSuppressed; - } - } - - - private static readonly ExecutionContext s_dummyDefaultEC = new ExecutionContext(isPreAllocatedDefault: true); - - static internal ExecutionContext PreAllocatedDefault - { - [SecuritySafeCritical] - get { return s_dummyDefaultEC; } - } - - internal bool IsPreAllocatedDefault - { - get - { - // we use _flags instead of a direct comparison w/ s_dummyDefaultEC to avoid the static access on - // hot code paths. - if ((_flags & Flags.IsPreAllocatedDefault) != Flags.None) - { - Contract.Assert(this == s_dummyDefaultEC); - return true; - } - else - { - return false; - } - } - } - - [ReliabilityContract(Consistency.WillNotCorruptState, Cer.Success)] - internal ExecutionContext() - { - } - - [ReliabilityContract(Consistency.WillNotCorruptState, Cer.Success)] - internal ExecutionContext(bool isPreAllocatedDefault) - { - if (isPreAllocatedDefault) - _flags = Flags.IsPreAllocatedDefault; - } - - // Read-only wrapper around ExecutionContext. This enables safe reading of an ExecutionContext without accidentally modifying it. - internal struct Reader - { - ExecutionContext m_ec; - - public Reader(ExecutionContext ec) { m_ec = ec; } - - public ExecutionContext DangerousGetRawExecutionContext() { return m_ec; } - - public bool IsNull { get { return m_ec == null; } } - [SecurityCritical] - public bool IsDefaultFTContext(bool ignoreSyncCtx) { return m_ec.IsDefaultFTContext(ignoreSyncCtx); } - public bool IsFlowSuppressed - { - [MethodImpl(MethodImplOptions.AggressiveInlining)] - get { return IsNull ? false : m_ec.isFlowSuppressed; } - } - //public Thread Thread { get { return m_ec._thread; } } - public bool IsSame(ExecutionContext.Reader other) { return m_ec == other.m_ec; } - - public SynchronizationContext SynchronizationContext { get { return IsNull ? null : m_ec.SynchronizationContext; } } - public SynchronizationContext SynchronizationContextNoFlow { get { return IsNull ? null : m_ec.SynchronizationContextNoFlow; } } - -#if FEATURE_IMPERSONATION || FEATURE_COMPRESSEDSTACK - public SecurityContext.Reader SecurityContext - { - [SecurityCritical] - [MethodImpl(MethodImplOptions.AggressiveInlining)] - get { return new SecurityContext.Reader(IsNull ? null : m_ec.SecurityContext); } - } -#endif - -#if FEATURE_REMOTING - public LogicalCallContext.Reader LogicalCallContext - { - [SecurityCritical] - get { return new LogicalCallContext.Reader(IsNull ? null : m_ec.LogicalCallContext); } - } - - public IllogicalCallContext.Reader IllogicalCallContext - { - [SecurityCritical] - get { return new IllogicalCallContext.Reader(IsNull ? null : m_ec.IllogicalCallContext); } - } -#endif - - [SecurityCritical] - public object GetLocalValue(IAsyncLocal local) - { - if (IsNull) - return null; - - if (m_ec._localValues == null) - return null; - - object value; - m_ec._localValues.TryGetValue(local, out value); - return value; - } - - [SecurityCritical] - public bool HasSameLocalValues(ExecutionContext other) - { - var thisLocalValues = IsNull ? null : m_ec._localValues; - var otherLocalValues = other == null ? null : other._localValues; - return thisLocalValues == otherLocalValues; - } - - [SecurityCritical] - public bool HasLocalValues() - { - return !this.IsNull && m_ec._localValues != null; - } - } - - [SecurityCritical] - internal static object GetLocalValue(IAsyncLocal local) - { - return Thread.CurrentThread.GetExecutionContextReader().GetLocalValue(local); - } - - [SecurityCritical] - internal static void SetLocalValue(IAsyncLocal local, object newValue, bool needChangeNotifications) - { - ExecutionContext current = Thread.CurrentThread.GetMutableExecutionContext(); - - object previousValue = null; - bool hadPreviousValue = current._localValues != null && current._localValues.TryGetValue(local, out previousValue); - - if (previousValue == newValue) - return; - - current._localValues = current._localValues == null ? - AsyncLocalValueMap.Create(local, newValue) : - current._localValues.Set(local, newValue); - - if (needChangeNotifications) - { - if (hadPreviousValue) - { - Contract.Assert(current._localChangeNotifications != null); - Contract.Assert(current._localChangeNotifications.Contains(local)); - } - else - { - if (current._localChangeNotifications == null) - current._localChangeNotifications = new List(); - else - current._localChangeNotifications = new List(current._localChangeNotifications); - - current._localChangeNotifications.Add(local); - } - - local.OnValueChanged(previousValue, newValue, false); - } - } - - [SecurityCritical] - [HandleProcessCorruptedStateExceptions] - internal static void OnAsyncLocalContextChanged(ExecutionContext previous, ExecutionContext current) - { - List previousLocalChangeNotifications = (previous == null) ? null : previous._localChangeNotifications; - if (previousLocalChangeNotifications != null) - { - foreach (IAsyncLocal local in previousLocalChangeNotifications) - { - object previousValue = null; - if (previous != null && previous._localValues != null) - previous._localValues.TryGetValue(local, out previousValue); - - object currentValue = null; - if (current != null && current._localValues != null) - current._localValues.TryGetValue(local, out currentValue); - - if (previousValue != currentValue) - local.OnValueChanged(previousValue, currentValue, true); - } - } - - List currentLocalChangeNotifications = (current == null) ? null : current._localChangeNotifications; - if (currentLocalChangeNotifications != null && currentLocalChangeNotifications != previousLocalChangeNotifications) - { - try - { - foreach (IAsyncLocal local in currentLocalChangeNotifications) - { - // If the local has a value in the previous context, we already fired the event for that local - // in the code above. - object previousValue = null; - if (previous == null || - previous._localValues == null || - !previous._localValues.TryGetValue(local, out previousValue)) - { - object currentValue = null; - if (current != null && current._localValues != null) - current._localValues.TryGetValue(local, out currentValue); - - if (previousValue != currentValue) - local.OnValueChanged(previousValue, currentValue, true); - } - } - } - catch (Exception ex) - { - Environment.FailFast( - Environment.GetResourceString("ExecutionContext_ExceptionInAsyncLocalNotification"), - ex); - } - } - } - - -#if FEATURE_REMOTING - internal LogicalCallContext LogicalCallContext - { - [System.Security.SecurityCritical] // auto-generated - get - { - if (_logicalCallContext == null) - { - _logicalCallContext = new LogicalCallContext(); - } - return _logicalCallContext; - } - [System.Security.SecurityCritical] // auto-generated - set - { - Contract.Assert(this != s_dummyDefaultEC); - _logicalCallContext = value; - } - } - - internal IllogicalCallContext IllogicalCallContext - { - get - { - if (_illogicalCallContext == null) - { - _illogicalCallContext = new IllogicalCallContext(); - } - return _illogicalCallContext; - } - set - { - Contract.Assert(this != s_dummyDefaultEC); - _illogicalCallContext = value; - } - } -#endif // #if FEATURE_REMOTING - - internal SynchronizationContext SynchronizationContext - { - [ReliabilityContract(Consistency.WillNotCorruptState, Cer.Success)] - get - { - return _syncContext; - } - [ReliabilityContract(Consistency.WillNotCorruptState, Cer.Success)] - set - { - Contract.Assert(this != s_dummyDefaultEC); - _syncContext = value; - } - } - - internal SynchronizationContext SynchronizationContextNoFlow - { - [ReliabilityContract(Consistency.WillNotCorruptState, Cer.Success)] - get - { - return _syncContextNoFlow; - } - [ReliabilityContract(Consistency.WillNotCorruptState, Cer.Success)] - set - { - Contract.Assert(this != s_dummyDefaultEC); - _syncContextNoFlow = value; - } - } - -#if FEATURE_CAS_POLICY - internal HostExecutionContext HostExecutionContext - { - get - { - return _hostExecutionContext; - } - set - { - Contract.Assert(this != s_dummyDefaultEC); - _hostExecutionContext = value; - } - } -#endif // FEATURE_CAS_POLICY -#if FEATURE_IMPERSONATION || FEATURE_COMPRESSEDSTACK - internal SecurityContext SecurityContext - { - [ReliabilityContract(Consistency.WillNotCorruptState, Cer.Success)] - get - { - return _securityContext; - } - [ReliabilityContract(Consistency.WillNotCorruptState, Cer.Success)] - set - { - Contract.Assert(this != s_dummyDefaultEC); - // store the new security context - _securityContext = value; - // perform the reverse link too - if (value != null) - _securityContext.ExecutionContext = this; - } - } -#endif // #if FEATURE_IMPERSONATION || FEATURE_COMPRESSEDSTACK - - - public void Dispose() - { - if(this.IsPreAllocatedDefault) - return; //Do nothing if this is the default context -#if FEATURE_CAS_POLICY - if (_hostExecutionContext != null) - _hostExecutionContext.Dispose(); -#endif // FEATURE_CAS_POLICY -#if FEATURE_IMPERSONATION || FEATURE_COMPRESSEDSTACK - if (_securityContext != null) - _securityContext.Dispose(); -#endif //FEATURE_IMPERSONATION || FEATURE_COMPRESSEDSTACK - } - - [DynamicSecurityMethod] - [System.Security.SecurityCritical] // auto-generated_required - public static void Run(ExecutionContext executionContext, ContextCallback callback, Object state) - { - if (executionContext == null) - throw new InvalidOperationException(Environment.GetResourceString("InvalidOperation_NullContext")); - if (!executionContext.isNewCapture) - throw new InvalidOperationException(Environment.GetResourceString("InvalidOperation_NotNewCaptureContext")); - - Run(executionContext, callback, state, false); - } - - // This method is special from a security perspective - the VM will not allow a stack walk to - // continue past the call to ExecutionContext.Run. If you change the signature to this method, make - // sure to update SecurityStackWalk::IsSpecialRunFrame in the VM to search for the new signature. - [DynamicSecurityMethod] - [SecurityCritical] - [FriendAccessAllowed] - internal static void Run(ExecutionContext executionContext, ContextCallback callback, Object state, bool preserveSyncCtx) - { - RunInternal(executionContext, callback, state, preserveSyncCtx); - } - - // Actual implementation of Run is here, in a non-DynamicSecurityMethod, because the JIT seems to refuse to inline callees into - // a DynamicSecurityMethod. - [SecurityCritical] - [SuppressMessage("Microsoft.Concurrency", "CA8001", Justification = "Reviewed for thread safety")] - [HandleProcessCorruptedStateExceptions] - internal static void RunInternal(ExecutionContext executionContext, ContextCallback callback, Object state, bool preserveSyncCtx) - { - Contract.Assert(executionContext != null); - if (executionContext.IsPreAllocatedDefault) - { - Contract.Assert(executionContext.IsDefaultFTContext(preserveSyncCtx)); - } - else - { - Contract.Assert(executionContext.isNewCapture); - executionContext.isNewCapture = false; - } - - Thread currentThread = Thread.CurrentThread; - ExecutionContextSwitcher ecsw = default(ExecutionContextSwitcher); - - RuntimeHelpers.PrepareConstrainedRegions(); - try - { - ExecutionContext.Reader ec = currentThread.GetExecutionContextReader(); - if ( (ec.IsNull || ec.IsDefaultFTContext(preserveSyncCtx)) && -#if FEATURE_IMPERSONATION || FEATURE_COMPRESSEDSTACK - SecurityContext.CurrentlyInDefaultFTSecurityContext(ec) && -#endif // #if FEATURE_IMPERSONATION || FEATURE_COMPRESSEDSTACK - executionContext.IsDefaultFTContext(preserveSyncCtx) && - ec.HasSameLocalValues(executionContext) - ) - { - // Neither context is interesting, so we don't need to set the context. - // We do need to reset any changes made by the user's callback, - // so here we establish a "copy-on-write scope". Any changes will - // result in a copy of the context being made, preserving the original - // context. - EstablishCopyOnWriteScope(currentThread, true, ref ecsw); - } - else - { - if (executionContext.IsPreAllocatedDefault) - executionContext = new ExecutionContext(); - ecsw = SetExecutionContext(executionContext, preserveSyncCtx); - } - - // - // Call the user's callback - // - callback(state); - } - finally - { - ecsw.Undo(currentThread); - } - } - - [SecurityCritical] - static internal void EstablishCopyOnWriteScope(Thread currentThread, ref ExecutionContextSwitcher ecsw) - { - EstablishCopyOnWriteScope(currentThread, false, ref ecsw); - } - - [SecurityCritical] - static private void EstablishCopyOnWriteScope(Thread currentThread, bool knownNullWindowsIdentity, ref ExecutionContextSwitcher ecsw) - { - Contract.Assert(currentThread == Thread.CurrentThread); - - ecsw.outerEC = currentThread.GetExecutionContextReader(); - ecsw.outerECBelongsToScope = currentThread.ExecutionContextBelongsToCurrentScope; - -#if FEATURE_IMPERSONATION - ecsw.cachedAlwaysFlowImpersonationPolicy = SecurityContext.AlwaysFlowImpersonationPolicy; - if (knownNullWindowsIdentity) - Contract.Assert(SecurityContext.GetCurrentWI(ecsw.outerEC, ecsw.cachedAlwaysFlowImpersonationPolicy) == null); - else - ecsw.wi = SecurityContext.GetCurrentWI(ecsw.outerEC, ecsw.cachedAlwaysFlowImpersonationPolicy); - ecsw.wiIsValid = true; -#endif - currentThread.ExecutionContextBelongsToCurrentScope = false; - ecsw.thread = currentThread; - } - - - // Sets the given execution context object on the thread. - // Returns the previous one. - [System.Security.SecurityCritical] // auto-generated - [DynamicSecurityMethodAttribute()] - [MethodImplAttribute(MethodImplOptions.NoInlining)] // Methods containing StackCrawlMark local var has to be marked non-inlineable -#if FEATURE_CORRUPTING_EXCEPTIONS - [HandleProcessCorruptedStateExceptions] -#endif // FEATURE_CORRUPTING_EXCEPTIONS - internal static ExecutionContextSwitcher SetExecutionContext(ExecutionContext executionContext, bool preserveSyncCtx) - { -#if FEATURE_IMPERSONATION || FEATURE_COMPRESSEDSTACK - StackCrawlMark stackMark = StackCrawlMark.LookForMyCaller; -#endif // #if FEATURE_IMPERSONATION || FEATURE_COMPRESSEDSTACK - - Contract.Assert(executionContext != null); - Contract.Assert(executionContext != s_dummyDefaultEC); - - // Set up the switcher object to return; - ExecutionContextSwitcher ecsw = new ExecutionContextSwitcher(); - - Thread currentThread = Thread.CurrentThread; - ExecutionContext.Reader outerEC = currentThread.GetExecutionContextReader(); - - ecsw.thread = currentThread; - ecsw.outerEC = outerEC; - ecsw.outerECBelongsToScope = currentThread.ExecutionContextBelongsToCurrentScope; - - if (preserveSyncCtx) - executionContext.SynchronizationContext = outerEC.SynchronizationContext; - executionContext.SynchronizationContextNoFlow = outerEC.SynchronizationContextNoFlow; - - currentThread.SetExecutionContext(executionContext, belongsToCurrentScope: true); - - RuntimeHelpers.PrepareConstrainedRegions(); - try - { - OnAsyncLocalContextChanged(outerEC.DangerousGetRawExecutionContext(), executionContext); - -#if FEATURE_IMPERSONATION || FEATURE_COMPRESSEDSTACK - //set the security context - SecurityContext sc = executionContext.SecurityContext; - if (sc != null) - { - // non-null SC: needs to be set - SecurityContext.Reader prevSeC = outerEC.SecurityContext; - ecsw.scsw = SecurityContext.SetSecurityContext(sc, prevSeC, false, ref stackMark); - } - else if (!SecurityContext.CurrentlyInDefaultFTSecurityContext(ecsw.outerEC)) - { - // null incoming SC, but we're currently not in FT: use static FTSC to set - SecurityContext.Reader prevSeC = outerEC.SecurityContext; - ecsw.scsw = SecurityContext.SetSecurityContext(SecurityContext.FullTrustSecurityContext, prevSeC, false, ref stackMark); - } -#endif // #if FEATURE_IMPERSONATION || FEATURE_COMPRESSEDSTACK -#if FEATURE_CAS_POLICY - // set the Host Context - HostExecutionContext hostContext = executionContext.HostExecutionContext; - if (hostContext != null) - { - ecsw.hecsw = HostExecutionContextManager.SetHostExecutionContextInternal(hostContext); - } -#endif // FEATURE_CAS_POLICY - } - catch - { - ecsw.UndoNoThrow(currentThread); - throw; - } - return ecsw; - } - - // - // Public CreateCopy. Used to copy captured ExecutionContexts so they can be reused multiple times. - // This should only copy the portion of the context that we actually capture. - // - [SecuritySafeCritical] - public ExecutionContext CreateCopy() - { - if (!isNewCapture) - { - throw new InvalidOperationException(Environment.GetResourceString("InvalidOperation_CannotCopyUsedContext")); - } - ExecutionContext ec = new ExecutionContext(); - ec.isNewCapture = true; - ec._syncContext = _syncContext == null ? null : _syncContext.CreateCopy(); - ec._localValues = _localValues; - ec._localChangeNotifications = _localChangeNotifications; -#if FEATURE_CAS_POLICY - // capture the host execution context - ec._hostExecutionContext = _hostExecutionContext == null ? null : _hostExecutionContext.CreateCopy(); -#endif // FEATURE_CAS_POLICY -#if FEATURE_IMPERSONATION || FEATURE_COMPRESSEDSTACK - if (_securityContext != null) - { - ec._securityContext = _securityContext.CreateCopy(); - ec._securityContext.ExecutionContext = ec; - } -#endif // #if FEATURE_IMPERSONATION || FEATURE_COMPRESSEDSTACK - -#if FEATURE_REMOTING - if (this._logicalCallContext != null) - ec.LogicalCallContext = (LogicalCallContext)this.LogicalCallContext.Clone(); - - Contract.Assert(this._illogicalCallContext == null); -#endif // #if FEATURE_REMOTING - - return ec; - } - - // - // Creates a complete copy, used for copy-on-write. - // - [SecuritySafeCritical] - internal ExecutionContext CreateMutableCopy() - { - Contract.Assert(!this.isNewCapture); - - ExecutionContext ec = new ExecutionContext(); - - // We don't deep-copy the SyncCtx, since we're still in the same context after copy-on-write. - ec._syncContext = this._syncContext; - ec._syncContextNoFlow = this._syncContextNoFlow; - -#if FEATURE_CAS_POLICY - // capture the host execution context - ec._hostExecutionContext = this._hostExecutionContext == null ? null : _hostExecutionContext.CreateCopy(); -#endif // FEATURE_CAS_POLICY - -#if FEATURE_IMPERSONATION || FEATURE_COMPRESSEDSTACK - if (_securityContext != null) - { - ec._securityContext = this._securityContext.CreateMutableCopy(); - ec._securityContext.ExecutionContext = ec; - } -#endif // #if FEATURE_IMPERSONATION || FEATURE_COMPRESSEDSTACK - -#if FEATURE_REMOTING - if (this._logicalCallContext != null) - ec.LogicalCallContext = (LogicalCallContext)this.LogicalCallContext.Clone(); - - if (this._illogicalCallContext != null) - ec.IllogicalCallContext = (IllogicalCallContext)this.IllogicalCallContext.CreateCopy(); -#endif // #if FEATURE_REMOTING - - ec._localValues = this._localValues; - ec._localChangeNotifications = this._localChangeNotifications; - ec.isFlowSuppressed = this.isFlowSuppressed; - - return ec; - } - - [System.Security.SecurityCritical] // auto-generated_required - public static AsyncFlowControl SuppressFlow() - { - if (IsFlowSuppressed()) - { - throw new InvalidOperationException(Environment.GetResourceString("InvalidOperation_CannotSupressFlowMultipleTimes")); - } - Contract.EndContractBlock(); - AsyncFlowControl afc = new AsyncFlowControl(); - afc.Setup(); - return afc; - } - - [SecuritySafeCritical] - public static void RestoreFlow() - { - ExecutionContext ec = Thread.CurrentThread.GetMutableExecutionContext(); - if (!ec.isFlowSuppressed) - { - throw new InvalidOperationException(Environment.GetResourceString("InvalidOperation_CannotRestoreUnsupressedFlow")); - } - ec.isFlowSuppressed = false; - } - - [Pure] - public static bool IsFlowSuppressed() - { - return Thread.CurrentThread.GetExecutionContextReader().IsFlowSuppressed; - } - - [System.Security.SecuritySafeCritical] // auto-generated - [MethodImplAttribute(MethodImplOptions.NoInlining)] // Methods containing StackCrawlMark local var has to be marked non-inlineable - public static ExecutionContext Capture() - { - // set up a stack mark for finding the caller - StackCrawlMark stackMark = StackCrawlMark.LookForMyCaller; - return ExecutionContext.Capture(ref stackMark, CaptureOptions.None); - } - - // - // Captures an ExecutionContext with optimization for the "default" case, and captures a "null" synchronization context. - // When calling ExecutionContext.Run on the returned context, specify ignoreSyncCtx = true - // - [System.Security.SecuritySafeCritical] // auto-generated - [MethodImplAttribute(MethodImplOptions.NoInlining)] // Methods containing StackCrawlMark local var has to be marked non-inlineable - [FriendAccessAllowed] - internal static ExecutionContext FastCapture() - { - // set up a stack mark for finding the caller - StackCrawlMark stackMark = StackCrawlMark.LookForMyCaller; - return ExecutionContext.Capture(ref stackMark, CaptureOptions.IgnoreSyncCtx | CaptureOptions.OptimizeDefaultCase); - } - - - [Flags] - internal enum CaptureOptions - { - None = 0x00, - - IgnoreSyncCtx = 0x01, //Don't flow SynchronizationContext - - OptimizeDefaultCase = 0x02, //Faster in the typical case, but can't show the result to users - // because they could modify the shared default EC. - // Use this only if you won't be exposing the captured EC to users. - } - - // internal helper to capture the current execution context using a passed in stack mark - [System.Security.SecurityCritical] // auto-generated - static internal ExecutionContext Capture(ref StackCrawlMark stackMark, CaptureOptions options) - { - ExecutionContext.Reader ecCurrent = Thread.CurrentThread.GetExecutionContextReader(); - - // check to see if Flow is suppressed - if (ecCurrent.IsFlowSuppressed) - return null; - - // - // Attempt to capture context. There may be nothing to capture... - // - -#if FEATURE_IMPERSONATION || FEATURE_COMPRESSEDSTACK - // capture the security context - SecurityContext secCtxNew = SecurityContext.Capture(ecCurrent, ref stackMark); -#endif // #if FEATURE_IMPERSONATION || FEATURE_COMPRESSEDSTACK -#if FEATURE_CAS_POLICY - // capture the host execution context - HostExecutionContext hostCtxNew = HostExecutionContextManager.CaptureHostExecutionContext(); -#endif // FEATURE_CAS_POLICY - - SynchronizationContext syncCtxNew = null; - -#if FEATURE_REMOTING - LogicalCallContext logCtxNew = null; -#endif - - if (!ecCurrent.IsNull) - { - // capture the sync context - if (0 == (options & CaptureOptions.IgnoreSyncCtx)) - syncCtxNew = (ecCurrent.SynchronizationContext == null) ? null : ecCurrent.SynchronizationContext.CreateCopy(); - -#if FEATURE_REMOTING - // copy over the Logical Call Context - if (ecCurrent.LogicalCallContext.HasInfo) - logCtxNew = ecCurrent.LogicalCallContext.Clone(); -#endif // #if FEATURE_REMOTING - } - - IAsyncLocalValueMap localValues = null; - List localChangeNotifications = null; - if (!ecCurrent.IsNull) - { - localValues = ecCurrent.DangerousGetRawExecutionContext()._localValues; - localChangeNotifications = ecCurrent.DangerousGetRawExecutionContext()._localChangeNotifications; - } - - // - // If we didn't get anything but defaults, and we're allowed to return the - // dummy default EC, don't bother allocating a new context. - // - if (0 != (options & CaptureOptions.OptimizeDefaultCase) && -#if FEATURE_IMPERSONATION || FEATURE_COMPRESSEDSTACK - secCtxNew == null && -#endif -#if FEATURE_CAS_POLICY - hostCtxNew == null && -#endif // FEATURE_CAS_POLICY - syncCtxNew == null && -#if FEATURE_REMOTING - (logCtxNew == null || !logCtxNew.HasInfo) && -#endif // #if FEATURE_REMOTING - localValues == null && - localChangeNotifications == null - ) - { - return s_dummyDefaultEC; - } - - // - // Allocate the new context, and fill it in. - // - ExecutionContext ecNew = new ExecutionContext(); -#if FEATURE_IMPERSONATION || FEATURE_COMPRESSEDSTACK - ecNew.SecurityContext = secCtxNew; - if (ecNew.SecurityContext != null) - ecNew.SecurityContext.ExecutionContext = ecNew; -#endif -#if FEATURE_CAS_POLICY - ecNew._hostExecutionContext = hostCtxNew; -#endif // FEATURE_CAS_POLICY - ecNew._syncContext = syncCtxNew; -#if FEATURE_REMOTING - ecNew.LogicalCallContext = logCtxNew; -#endif // #if FEATURE_REMOTING - ecNew._localValues = localValues; - ecNew._localChangeNotifications = localChangeNotifications; - ecNew.isNewCapture = true; - - return ecNew; - } - - // - // Implementation of ISerializable - // - - [System.Security.SecurityCritical] // auto-generated_required - public void GetObjectData(SerializationInfo info, StreamingContext context) - { - if (info==null) - throw new ArgumentNullException(nameof(info)); - Contract.EndContractBlock(); - -#if FEATURE_REMOTING - if (_logicalCallContext != null) - { - info.AddValue("LogicalCallContext", _logicalCallContext, typeof(LogicalCallContext)); - } -#endif // #if FEATURE_REMOTING - } - - [System.Security.SecurityCritical] // auto-generated - private ExecutionContext(SerializationInfo info, StreamingContext context) - { - SerializationInfoEnumerator e = info.GetEnumerator(); - while (e.MoveNext()) - { -#if FEATURE_REMOTING - if (e.Name.Equals("LogicalCallContext")) - { - _logicalCallContext = (LogicalCallContext) e.Value; - } -#endif // #if FEATURE_REMOTING - } - } // ObjRef .ctor - - - [System.Security.SecurityCritical] // auto-generated - internal bool IsDefaultFTContext(bool ignoreSyncCtx) - { -#if FEATURE_CAS_POLICY - if (_hostExecutionContext != null) - return false; -#endif // FEATURE_CAS_POLICY - if (!ignoreSyncCtx && _syncContext != null) - return false; -#if FEATURE_IMPERSONATION || FEATURE_COMPRESSEDSTACK - if (_securityContext != null && !_securityContext.IsDefaultFTSecurityContext()) - return false; -#endif //#if FEATURE_IMPERSONATION || FEATURE_COMPRESSEDSTACK -#if FEATURE_REMOTING - if (_logicalCallContext != null && _logicalCallContext.HasInfo) - return false; - if (_illogicalCallContext != null && _illogicalCallContext.HasUserData) - return false; -#endif //#if FEATURE_REMOTING - return true; - } - } // class ExecutionContext - -#endif //FEATURE_CORECLR } -- 2.7.4