Skip to content
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

Merged
merged 15 commits into from
Apr 27, 2017

Conversation

dVakulen
Copy link
Contributor

@dVakulen dVakulen commented Apr 20, 2017

As target framework version was upgraded to .Net 4.6.1 now it's possible to remove NETSTANDARD ifdefs from RequestContext.

@dVakulen dVakulen changed the title Use async local for request context [WIP] Use async local for request context Apr 21, 2017
@jdom
Copy link
Member

jdom commented Apr 21, 2017

I think this is a good idea. Nevertheless, I'm not so sure about no longer honoring Trace.CorrelationManager.ActivityId since that one is not just an implementation detail, and people set that directly.
What are your thoughts about it?

@dVakulen
Copy link
Contributor Author

As AsyncLocal activity id is the only way of storing it while running on .Net core - it should become the default, and on full .Net we can use Trace.CorrelationManager.ActivityId in case of empty AsyncLocal one.

@dVakulen
Copy link
Contributor Author

dVakulen commented Apr 24, 2017

@jdom Added commit that does fallback to Trace.CorrelationManager.ActivityId in case of empty AsyncLocal one : c4b726b.
Will add test if this is acceptable approach.

Copy link
Member

@jdom jdom left a 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))
Copy link
Member

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

Copy link
Contributor Author

@dVakulen dVakulen Apr 24, 2017

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Addressed.

@jdom
Copy link
Member

jdom commented Apr 24, 2017

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))
Copy link
Member

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

Copy link
Contributor Author

@dVakulen dVakulen Apr 24, 2017

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
Copy link
Member

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)

Copy link
Contributor Author

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;
Copy link
Member

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

Copy link
Contributor Author

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.

@jdom jdom changed the title [WIP] Use async local for request context Use AsyncLocal for RequestContext Apr 24, 2017
#else
Trace.CorrelationManager.ActivityId = (Guid)activityIdObj;
object legacyActivityIdObj = null;
#if !NETSTANDARD
Copy link
Member

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

Copy link
Contributor Author

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;
Copy link
Member

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.

Copy link
Contributor Author

@dVakulen dVakulen Apr 25, 2017

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.

Copy link
Member

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).

@jdom
Copy link
Member

jdom commented Apr 25, 2017

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";
Copy link
Member

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

@@ -114,20 +114,12 @@ public static class RequestContextTestUtils
{
public static void SetActivityId(Guid id)
{
#if NETSTANDARD
RequestContext.ActivityId.Value = id;
Copy link
Member

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.

Copy link
Member

@jdom jdom Apr 26, 2017

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); }
        }

Copy link
Member

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.

Copy link
Contributor Author

@dVakulen dVakulen Apr 26, 2017

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.

jdom and others added 2 commits April 25, 2017 18:22
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
@jdom jdom merged commit 647ae06 into dotnet:master Apr 27, 2017
@jdom
Copy link
Member

jdom commented Apr 27, 2017

Thanks a lot @dVakulen!

@dVakulen
Copy link
Contributor Author

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).

@dVakulen dVakulen deleted the request-context-async-local branch April 27, 2017 18:50
@ReubenBond
Copy link
Member

@dVakulen the results from, eg #2060 look amazing (up to 81% perf improvement!), I think the thumbs up here are because we've each personally dealt with differences between .NET Standard & full .NET - there were some hard times there... so we're just so happy to see convergence.

@jdom
Copy link
Member

jdom commented Apr 27, 2017

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.
All PRs are useful. Some are not in our main priority, so while good, they still add work for us in the form of reviews and testing, that we have to prioritize high to keep the contributions coming. This one is good, but also aligns with our priority, so it removes work from us. Sure, we have to review it, but you did take the time to do most of the heavy lifting and testing.

@github-actions github-actions bot locked and limited conversation to collaborators Dec 9, 2023
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

4 participants