Skip to content
This repository has been archived by the owner on Jan 23, 2023. It is now read-only.

Commit

Permalink
Improve non-default context
Browse files Browse the repository at this point in the history
  • Loading branch information
benaadams committed Jan 5, 2018
1 parent 776b63a commit 016ed58
Show file tree
Hide file tree
Showing 3 changed files with 100 additions and 33 deletions.
2 changes: 1 addition & 1 deletion src/mscorlib/shared/System/Threading/AsyncLocal.cs
Original file line number Diff line number Diff line change
Expand Up @@ -144,7 +144,7 @@ public bool TryGetValue(IAsyncLocal key, out object value)
}

// Instance with one key/value pair.
private sealed class OneElementAsyncLocalValueMap : IAsyncLocalValueMap
internal sealed class OneElementAsyncLocalValueMap : IAsyncLocalValueMap
{
private readonly IAsyncLocal _key1;
private readonly object _value1;
Expand Down
120 changes: 90 additions & 30 deletions src/mscorlib/shared/System/Threading/ExecutionContext.cs
Original file line number Diff line number Diff line change
Expand Up @@ -106,12 +106,14 @@ public static bool IsFlowSuppressed()
return executionContext != null && executionContext.m_isFlowSuppressed;
}

internal bool HasChangeNotifications => m_localChangeNotifications != null;

public static void Run(ExecutionContext executionContext, ContextCallback callback, Object state)
{
// Note: ExecutionContext.Run is an extremely hot function and used by every await, ThreadPool execution, etc.
if (executionContext == null)
{
throw new InvalidOperationException(SR.InvalidOperation_NullContext);
ThrowNullContext();
}

// Capture references to Thread Contexts
Expand All @@ -134,7 +136,13 @@ public static void Run(ExecutionContext executionContext, ContextCallback callba
if (previousExecutionCtx != executionContext)
{
// Restore changed ExecutionContext
SetCurrentContext(ref currentExecutionCtxRef, executionContext);
currentExecutionCtxRef = executionContext;
if ((executionContext != null && executionContext.HasChangeNotifications) ||
(previousExecutionCtx != null && previousExecutionCtx.HasChangeNotifications))
{
// Are change notifications; trigger any effected
OnValuesChanged(previousExecutionCtx, executionContext);
}
}

ExceptionDispatchInfo edi = null;
Expand All @@ -157,60 +165,100 @@ public static void Run(ExecutionContext executionContext, ContextCallback callba
currentSyncCtxRef = previousSyncCtx;
}

if (currentExecutionCtxRef != previousExecutionCtx)
ExecutionContext currentExecutionCtx = currentExecutionCtxRef;
if (currentExecutionCtx != previousExecutionCtx)
{
// Restore changed ExecutionContext back to previous
SetCurrentContext(ref currentExecutionCtxRef, previousExecutionCtx);
currentExecutionCtxRef = previousExecutionCtx;
if ((currentExecutionCtx != null && currentExecutionCtx.HasChangeNotifications) ||
(previousExecutionCtx != null && previousExecutionCtx.HasChangeNotifications))
{
// Are change notifications; trigger any effected
OnValuesChanged(currentExecutionCtx, previousExecutionCtx);
}
}

// If exception was thrown by callback, rethrow it now original contexts are restored
edi?.Throw();
}

internal static void SetCurrentContext(ref ExecutionContext currentRef, ExecutionContext next)
internal static void OnValuesChanged(ExecutionContext previousExecutionCtx, ExecutionContext nextExecutionCtx)
{
Debug.Assert(currentRef != next);
// Capture current for change notification comparisions
ExecutionContext previous = currentRef;
// Set current to next
currentRef = next;
Debug.Assert(previousExecutionCtx != nextExecutionCtx);

// Collect Change Notifications
IAsyncLocal[] previousChangeNotifications = previousExecutionCtx?.m_localChangeNotifications;
IAsyncLocal[] nextChangeNotifications = nextExecutionCtx?.m_localChangeNotifications;

// At least one side must have notifications
Debug.Assert(previousChangeNotifications != null || nextChangeNotifications != null);

// Fire Change Notifications if any
// Fire Change Notifications
try
{
if (previous != null)
if (previousChangeNotifications != null && nextChangeNotifications != null)
{
foreach (IAsyncLocal local in previous.m_localChangeNotifications)
// Notifications can't exist without values
Debug.Assert(previousExecutionCtx.m_localValues != null);
Debug.Assert(nextExecutionCtx.m_localValues != null);
// Both contexts have change notifications, check previousExecutionCtx first
foreach (IAsyncLocal local in previousChangeNotifications)
{
previous.m_localValues.TryGetValue(local, out object previousValue);
object currentValue = null;
next?.m_localValues.TryGetValue(local, out currentValue);
previousExecutionCtx.m_localValues.TryGetValue(local, out object previousValue);
nextExecutionCtx.m_localValues.TryGetValue(local, out object currentValue);

if (previousValue != currentValue)
{
local.OnValueChanged(previousValue, currentValue, true);
}
}
}

if (next != null && next.m_localChangeNotifications != previous?.m_localChangeNotifications)
{
foreach (IAsyncLocal local in next.m_localChangeNotifications)
if (nextChangeNotifications != previousChangeNotifications)
{
// 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.m_localValues.TryGetValue(local, out previousValue))
// Check for additional notifications in nextExecutionCtx
foreach (IAsyncLocal local in nextChangeNotifications)
{
next.m_localValues.TryGetValue(local, out object currentValue);

if (previousValue != currentValue)
// If the local has a value in the previous context, we already fired the event
// for that local in the code above.
if (!previousExecutionCtx.m_localValues.TryGetValue(local, out object previousValue))
{
local.OnValueChanged(previousValue, currentValue, true);
nextExecutionCtx.m_localValues.TryGetValue(local, out object currentValue);
if (previousValue != currentValue)
{
local.OnValueChanged(previousValue, currentValue, true);
}
}
}
}
}
else if (previousChangeNotifications != null)
{
// Notifications can't exist without values
Debug.Assert(previousExecutionCtx.m_localValues != null);
// No current values, so just check previous against null
foreach (IAsyncLocal local in previousChangeNotifications)
{
previousExecutionCtx.m_localValues.TryGetValue(local, out object previousValue);
if (previousValue != null)
{
local.OnValueChanged(previousValue, null, true);
}
}
}
else // Implied: nextChangeNotifications != null
{
// Notifications can't exist without values
Debug.Assert(nextExecutionCtx.m_localValues != null);
// No previous values, so just check current against null
foreach (IAsyncLocal local in nextChangeNotifications)
{
nextExecutionCtx.m_localValues.TryGetValue(local, out object currentValue);
if (currentValue != null)
{
local.OnValueChanged(null, currentValue, true);
}
}
}
}
catch (Exception ex)
{
Expand All @@ -220,6 +268,12 @@ internal static void SetCurrentContext(ref ExecutionContext currentRef, Executio
}
}

[StackTraceHidden]
private static void ThrowNullContext()
{
throw new InvalidOperationException(SR.InvalidOperation_NullContext);
}

internal static object GetLocalValue(IAsyncLocal local)
{
ExecutionContext current = Thread.CurrentThread.ExecutionContext;
Expand Down Expand Up @@ -259,8 +313,8 @@ internal static void SetLocalValue(IAsyncLocal local, object newValue, bool need
}
else
{
newValues = AsyncLocalValueMap.Empty.Set(local, newValue);;
newChangeNotifications = Array.Empty<IAsyncLocal>();
// First AsyncLocal
newValues = new AsyncLocalValueMap.OneElementAsyncLocalValueMap(local, newValue);
}

//
Expand All @@ -270,8 +324,14 @@ internal static void SetLocalValue(IAsyncLocal local, object newValue, bool need
{
if (hadPreviousValue)
{
Debug.Assert(newChangeNotifications != null);
Debug.Assert(Array.IndexOf(newChangeNotifications, local) >= 0);
}
else if (newChangeNotifications == null)
{
newChangeNotifications = new IAsyncLocal[1];
newChangeNotifications[0] = local;
}
else
{
int newNotificationIndex = newChangeNotifications.Length;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -343,10 +343,17 @@ public void Start<TStateMachine>(ref TStateMachine stateMachine) where TStateMac
currentSyncCtxRef = previousSyncCtx;
}

if (previousExecutionCtx != currentExecutionCtxRef)
ExecutionContext currentExecutionCtx = currentExecutionCtxRef;
if (previousExecutionCtx != currentExecutionCtx)
{
// Restore changed ExecutionContext back to previous
ExecutionContext.SetCurrentContext(ref currentExecutionCtxRef, previousExecutionCtx);
currentExecutionCtxRef = previousExecutionCtx;
if ((currentExecutionCtx != null && currentExecutionCtx.HasChangeNotifications) ||
(previousExecutionCtx != null && previousExecutionCtx.HasChangeNotifications))
{
// Are change notifications; trigger any effected
ExecutionContext.OnValuesChanged(currentExecutionCtx, previousExecutionCtx);
}
}
}
}
Expand Down

0 comments on commit 016ed58

Please sign in to comment.