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

fix: adjust async_hooks cls behavior #734

Merged
merged 6 commits into from
May 4, 2018
Merged

Conversation

kjin
Copy link
Contributor

@kjin kjin commented May 2, 2018

This PR introduces the following changes to async_hooks-based tracing:

  • We now always use the triggerId when determining the parent AsyncResource unless the current AsyncResource is of type PROMISE.
  • We clear this.contexts upon disabling CLS (in practice, this happens when the Trace Agent is disabled).
  • Instead of sharing the value of the current context between different async scopes, we share a reference to it instead. This way, if we set context to a different value, the change is reflected in other scopes. (This makes things more consistent but I don't think it has behavioral consequences from the perspective of the user.)
    • Async scopes refer to either (1) scope in the sense of AsyncResource#runInAsyncScope, or (2) the function passed to either AsyncHooksCLS#runInNewContext or AsyncHooksCLS#bindToCurrentContext.
  • Instead of using this.currentContext to get the current context, we now directly look up the current context in this.contexts based on the current execution ID.
    • As a result, the before hook can now be removed.

@googlebot googlebot added the cla: yes This human has signed the Contributor License Agreement. label May 2, 2018
this.hook = (require('async_hooks') as AsyncHooksModule).createHook({
// Store a reference to the async_hooks module, since we will need to query
// the current AsyncResource ID often.
this.ah = require('async_hooks') as AsyncHooksModule;

This comment was marked as spam.

This comment was marked as spam.

}

setContext(value: Context): void {
this.currentContext.value = value;
this.contexts[this.ah.executionAsyncId()].value = value;

This comment was marked as spam.

This comment was marked as spam.

This comment was marked as spam.

const boundContext = this.currentContext.value;
const that = this;
// Capture the context of the current AsyncResource.
const boundContext = this.contexts[outerId];

This comment was marked as spam.

@codecov
Copy link

codecov bot commented May 2, 2018

Codecov Report

Merging #734 into master will increase coverage by 0.05%.
The diff coverage is 92.85%.

Impacted file tree graph

@@            Coverage Diff             @@
##           master     #734      +/-   ##
==========================================
+ Coverage   90.88%   90.93%   +0.05%     
==========================================
  Files          30       30              
  Lines        1568     1577       +9     
  Branches      304      307       +3     
==========================================
+ Hits         1425     1434       +9     
  Misses         59       59              
  Partials       84       84
Impacted Files Coverage Δ
src/cls/async-hooks.ts 96.36% <92.85%> (+0.71%) ⬆️

Continue to review full report at Codecov.

Legend - Click here to learn more
Δ = absolute <relative> (impact), ø = not affected, ? = missing data
Powered by Codecov. Last update d0009ff...1082eb1. Read the comment docs.

// constructor, rather than upon module load.
import * as asyncHooksModule from 'async_hooks';
// This file requires async_hooks in the AsyncHooksCLS constructor, rather than
// upon module load.

This comment was marked as spam.

This comment was marked as spam.

This comment was marked as spam.

This comment was marked as spam.

This comment was marked as spam.

This comment was marked as spam.

Copy link
Contributor

@ofrobots ofrobots left a comment

Choose a reason for hiding this comment

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

LGTM. left some nits.

Intuitively, it didn't make sense how you could implement CLS without the before/after hooks, but after looking at the PR and pondering, I agree with you that this should be correct. As long as we can trust executionAsyncId to be correct, this code is correct. AsyncWrap does the before/after hooks to ensure that the executionAsyncId is correct. We don't need to. Nice work!

// constructor, rather than upon module load.
import * as asyncHooksModule from 'async_hooks';
// This file requires async_hooks in the AsyncHooksCLS constructor, rather than
// upon module load.

This comment was marked as spam.

// that runs within the scope of this new AsyncResource to see the same
// context as its "parent" AsyncResource. The criteria for the parent
// depends on the type of the AsyncResource.
if (type === 'PROMISE') {

This comment was marked as spam.

This comment was marked as spam.

// Getting undefined when looking up this.contexts means that it wasn't
// set, so return the default context.
const current = this.contexts[this.ah.executionAsyncId()];
return current ? current.value : this.defaultContext;
}

setContext(value: Context): void {

This comment was marked as spam.

This comment was marked as spam.

current.value = oldContext;
// Revert the current context to what it was before it was set to the
// captured context.
that.contexts[id] = oldContext;

This comment was marked as spam.

This comment was marked as spam.

const boundContext = this.contexts[this.ah.executionAsyncId()];
// Return if we have already wrapped the function, or there is no current
// context to bind.
if ((fn as ContextWrapped<Func<T>>)[WRAPPED] || !boundContext) {

This comment was marked as spam.

This comment was marked as spam.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
cla: yes This human has signed the Contributor License Agreement.
Projects
None yet
Development

Successfully merging this pull request may close these issues.

4 participants