-
Notifications
You must be signed in to change notification settings - Fork 2k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Use AsyncLocal for RequestContext #2961
Conversation
I think this is a good idea. Nevertheless, I'm not so sure about no longer honoring |
As |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yup, I think this one is a safer approach.
Once we convert the vNext tree to the main tree (post 1.5.0 I think), we can move this fallback logic into PlatformServices, so when running Orleans 2.0 in full .NET, this behavior is preserved, even if Orleans itself is NETSTANDARD.
|
||
object legacyActivityIdObj; | ||
#if !NETSTANDARD | ||
if ((Guid)activityIdObj == Guid.Empty && contextData.TryGetValue(E2_E_TRACING_LEGACY_ACTIVITY_ID_HEADER, out legacyActivityIdObj)) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I would do this inside the previous if
, before assigning it to Guid.Empty
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Addressed.
Awesome, thanks! BTW, please add a test for it, as you suggested |
Trace.CorrelationManager.ActivityId = (Guid)activityIdObj; | ||
object legacyActivityIdObj; | ||
#if !NETSTANDARD | ||
if (contextData.TryGetValue(E2_E_TRACING_LEGACY_ACTIVITY_ID_HEADER, out legacyActivityIdObj)) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The reason for the test failures is that contextData
can be null, so this throws
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Fixed.
@@ -2,6 +2,7 @@ | |||
using System.Collections.Generic; | |||
using System.Diagnostics; | |||
using System.Linq; | |||
using System.Threading; | |||
#if !NETSTANDARD | |||
using System.Runtime.Remoting.Messaging; | |||
#else |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
check this ifdefing (you are adding System.Threading two times, one outside the if
, and one inside the else
)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Fixed
#endif | ||
} | ||
|
||
ActivityId.Value = (Guid)activityIdObj; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Most likely this is a non-issue, but could this cause unnecessary perf impact if the user is never setting the ActivityId in his code (but we set it always to Guid.Empty)? Probably not a concern in terms of memory pressure, but not sure about how expensive is to lift this value when scheduling tasks.
/cc @ReubenBond
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It's not expensive from memory standpoint, but ExecutionContext
is optimized for no values case, so flowing of that value will indeed take some CPU time, but I doubt that it could be seen on performance tests. Anyway, fixed.
#else | ||
Trace.CorrelationManager.ActivityId = (Guid)activityIdObj; | ||
object legacyActivityIdObj = null; | ||
#if !NETSTANDARD |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
super-nit: I assume this generates a warning in NETSTANDARD, since you are initializing legacyActivityIdObj
but never using it. Just move it inside the #if
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Moved
} | ||
else | ||
{ | ||
ActivityId.Value = (Guid)activityIdObj; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I suppose this is enough not to set ActivityId if unused. Do we want to set it to Guid.Empty if ActivityId.Value
is already set to something else though?
I believe the answer is no, and we should keep the code as-is, but wanted to ask you in case you did not consider that case and you actually have a different opinion about it.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I strongly suspect that it will be empty in that cases, as local values ExecutionContext
does not flow up to parent task as can be seen at https://github.com/dotnet/corefx/blob/b04757c307eee37dcc2b887f0b5ba08d7e866cc1/src/System.Threading/tests/AsyncLocalTests.cs#L255 and RequestContext
is being imported only in tasks that is created by Orleans per request, thus ensuring that non-empty value can not end up in that place. (+ in some places in streaming, which I didn't reviewed thoroughly, but they also seems to be safe). But to be on completely safe side, as retrieving of empty value does not incurs much cost (value retrieving code, values container class that is optimized for
common case of no values) I've just added check for non-empty async local activity id value to set it to default value.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Hehe, we think alike. I did the same analysis as you and draw the same conclusion: it's probably empty in most cases since it doesn't flow up, but still it wouldn't harm to do the check (although I would have been fine if we didn't do the check too, since it's a very rare edge condition).
LGTM, I'll merge when the tests complete |
@@ -36,13 +32,13 @@ public static class RequestContext | |||
|
|||
internal const string CALL_CHAIN_REQUEST_CONTEXT_HEADER = "#RC_CCH"; | |||
internal const string E2_E_TRACING_ACTIVITY_ID_HEADER = "#RC_AI"; | |||
internal const string E2_E_TRACING_LEGACY_ACTIVITY_ID_HEADER = "#L_RC_AI"; | |||
internal const string ORLEANS_REQUEST_CONTEXT_KEY = "#ORL_RC"; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is no longer used. But no issue, I'll delete it afterwards, merging for now
test/Tester/TestUtils.cs
Outdated
@@ -114,20 +114,12 @@ public static class RequestContextTestUtils | |||
{ | |||
public static void SetActivityId(Guid id) | |||
{ | |||
#if NETSTANDARD | |||
RequestContext.ActivityId.Value = id; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Since we are now officializing this approach (previously it was just a temp workaround for .NET Standard, so I didn't nit pick on it), it would be nice to not expose the AsyncLocal field, but instead have a wrapper property, so this would look like:
RequestContext.ActivityId = id
Same with the CallContextData field.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
BTW, looking closer, should we just dump the ActivityId as a separate AsyncLocal field? Looks like we are putting it in the same dictionary as CallContextData when flowing it, and there is no inherent reason to have it separate. The reason some of it was separate, is that we were also augmenting and flowing the Trace.CorrelationManager.ActivityId
, which is an external property that people might have already been using.
We can of course have a property in RequestContext so that people don't have to use a well-known header for it, such as:
/// <summary>Gets or sets an activity ID that can be used for correlation</summary>
public static Guid ActivityId
{
get { return (Guid)(Get(E2_E_TRACING_ACTIVITY_ID_HEADER) ?? Guid.Empty); }
set { Set(E2_E_TRACING_ACTIVITY_ID_HEADER, value); }
}
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Created a PR against your branch (dVakulen#8), let me know what you think. I believe it greatly simplifies the ActivityID API (and impl), especially for end users of both .NET and .NET Standard.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yeah, the reason it was extracted into separate variable was to save us from more conditional compilation statements + no dictionary allocations if ActivityId is the only value to be stored there.
Simplify handling and fallbacks of ActivityIDs to something similar to how it was before the move to AsyncLocal. Add RequestContext.ActivityId property for ease of flowing an end-to-end activity ID even in .NET Standard, where Trace.CorrelationManager.ActivityId does not exist. Avoid setting an explicit null value if the AsyncLocal is already null when clearing.
Remove separate AsyncLocal for ActivityId
Thanks a lot @dVakulen! |
Thank you too, @jdom. As side note - while it's indeed cool to receive thumb ups, I don't quite get why this one has got so much of them (compared to other PR's). |
yup, thumbs up are because we are starting to converge to a single source tree for 2.0. The 3 of us that gave thumbs up are the ones that have being dealing with the differences in the codebases for several months. So after the 4.6.1 merge we were already planning to do this work (and other NETSTANDARD_TODO ones), so you coming and fixing it for us is all goodness. |
As target framework version was upgraded to .Net 4.6.1 now it's possible to remove NETSTANDARD ifdefs from
RequestContext
.