fix: adjust async_hooks cls behavior#734
Conversation
| 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.
Sorry, something went wrong.
This comment was marked as spam.
This comment was marked as spam.
Sorry, something went wrong.
|
|
||
| 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.
Sorry, something went wrong.
This comment was marked as spam.
This comment was marked as spam.
Sorry, something went wrong.
This comment was marked as spam.
This comment was marked as spam.
Sorry, something went wrong.
| 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.
This comment was marked as spam.
Sorry, something went wrong.
Codecov Report
@@ 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
Continue to review full report at Codecov.
|
| // 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.
Sorry, something went wrong.
This comment was marked as spam.
This comment was marked as spam.
Sorry, something went wrong.
This comment was marked as spam.
This comment was marked as spam.
Sorry, something went wrong.
This comment was marked as spam.
This comment was marked as spam.
Sorry, something went wrong.
This comment was marked as spam.
This comment was marked as spam.
Sorry, something went wrong.
This comment was marked as spam.
This comment was marked as spam.
Sorry, something went wrong.
ofrobots
left a comment
There was a problem hiding this comment.
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.
This comment was marked as spam.
Sorry, something went wrong.
| // 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.
Sorry, something went wrong.
This comment was marked as spam.
This comment was marked as spam.
Sorry, something went wrong.
| return current ? current.value : this.defaultContext; | ||
| } | ||
|
|
||
| setContext(value: Context): void { |
This comment was marked as spam.
This comment was marked as spam.
Sorry, something went wrong.
This comment was marked as spam.
This comment was marked as spam.
Sorry, something went wrong.
| 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.
Sorry, something went wrong.
This comment was marked as spam.
This comment was marked as spam.
Sorry, something went wrong.
| 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.
Sorry, something went wrong.
This comment was marked as spam.
This comment was marked as spam.
Sorry, something went wrong.
This PR introduces the following changes to async_hooks-based tracing:
triggerIdwhen determining the parentAsyncResourceunless the currentAsyncResourceis of typePROMISE.this.contextsupon disabling CLS (in practice, this happens when the Trace Agent is disabled).AsyncResource#runInAsyncScope, or (2) the function passed to eitherAsyncHooksCLS#runInNewContextorAsyncHooksCLS#bindToCurrentContext.this.currentContextto get the current context, we now directly look up the current context inthis.contextsbased on the current execution ID.beforehook can now be removed.