Uh oh!
There was an error while loading. Please reload this page.
Retain async context when notifying PerformanceObservers - #36343
Retain async context when notifying PerformanceObservers#36343ZauberNerd wants to merge 4 commits into
Conversation
Uh oh!
There was an error while loading. Please reload this page.
addaleax
left a comment
There was a problem hiding this comment.
I’ll mark this as request changes only because it already has an approval but still needs tests in order to land :) But I’m fine with the approach here
Uh oh!
There was an error while loading. Please reload this page.
Uh oh!
There was an error while loading. Please reload this page.
d5353bf to
f24f3faComparef24f3fa to
88d44a7CompareZauberNerd
commented
Dec 3, 2020
Hi @addaleax could you please take another look here? |
88d44a7 to
64e45a9CompareUh oh!
There was an error while loading. Please reload this page.
Uh oh!
There was an error while loading. Please reload this page.
Uh oh!
There was an error while loading. Please reload this page.
64e45a9 to
dbe0e87Comparenodejs-github-bot
commented
Dec 7, 2020
There are situations where one wants to invoke a JS callback's ->Call()
from C++ and in particular retain any existing async_context state, but
where it's not obvious that a plain ->Call() would be safe at the point
in question.
Such callsites usually resort to
node::MakeCallback(..., async_context{0, 0}), which unconditionally
pushes the async_context{0, 0} and takes the required provisions for the
->Call() itself such as triggering the tick after its return, if needed.
An example would be the PerformanceObserver invocation from
PerformanceEntry::Notify(): this can get called when coming from JS
through e.g. perf_hooks.performance.mark() and alike, but perhaps also
from nghttp2 (c.f. EmitStatistics() in node_http2.cc).
In the former case, a plain ->Call() would be safe and it would be
desirable to retain the current async_context so that
PerformanceObservers can access it resp. the associated
AsyncLocalStorage. However, in the second case the additional provisions
taken by node::MakeCallback() might potentially be strictly required.
So PerformanceEntry::Notify() bites the bullet and invokes the
PerformanceObservers through node::MakeCallback() unconditionally,
thereby always rendering any possibly preexisting async_context
inaccessible.
Introduce the convenience node::MakeSyncCallback() for such usecases,
which would basically forward to ->Call() if safe and to
node::MakeCallback(..., async_context{0, 0}) otherwise.
Co-Authored-By: ZauberNerd <zaubernerd@zaubernerd.de>
PR-URL: nodejs#36343
Reviewed-By: James M Snell <jasnell@gmail.com>
Reviewed-By: Anna Henningsen <anna@addaleax.net>
Reviewed-By: Rich Trott <rtrott@gmail.com>It's desirable to retain async_contexts active at callsites of
perf_hooks.performance.mark() and alike in the subsequent
PerformanceObserver invocations such that the latter can access e.g.
associated AsyncLocalStorage instances.
In working towards this goal replace the node::MakeCallback(...,
async_context{0, 0}) in PerformanceEntry::doNotify() by the new
node::MakeSyncCallback() introduced specifically for this purpose.
This change will retain the original async_context, if any, in
perf_hook's observersCallback() and thus, for the subsequent doNotify()
on unbuffered PerformanceObservers.
Co-Authored-By: ZauberNerd <zaubernerd@zaubernerd.de>
PR-URL: nodejs#36343
Reviewed-By: James M Snell <jasnell@gmail.com>
Reviewed-By: Anna Henningsen <anna@addaleax.net>
Reviewed-By: Rich Trott <rtrott@gmail.com>This reverts commit 009e418. AFAIU the discussion at [1], PerformanceObserver had been made to inherit from AsyncResource more or less as a band-aid in lack of a better async_context candidate to invoke it in. In order to enable access to AsyncLocalStores from PerformanceObservers invoked synchronously through e.g. measure() or mark(), the current async_context, if any, should be retained. Note that this is a breaking change, but - as has been commented at [1], PerformanceObserver being derived from AsyncResource is a "minor divergence from the spec" anyway, - to my knowledge this is an internal implementation detail which has never been documented and - I can't think of a good reason why existing PerformanceObserver implementations would possibly rely on it. OTOH, it's probably worthwhile to not potentially invoke before() and after() async_hooks for each and every PerformanceObserver notification. [1] nodejs#18789 Co-Authored-By: ZauberNerd <zaubernerd@zaubernerd.de> PR-URL: nodejs#36343 Reviewed-By: James M Snell <jasnell@gmail.com> Reviewed-By: Anna Henningsen <anna@addaleax.net> Reviewed-By: Rich Trott <rtrott@gmail.com>
This test proves that the PerformanceObserver callback gets called with the async context of the callsite of performance.mark()/measure() and therefore AsyncLocalStorage can be used inside a PerformanceObserver. PR: nodejs#36343 PR-URL: nodejs#36343 Reviewed-By: James M Snell <jasnell@gmail.com> Reviewed-By: Anna Henningsen <anna@addaleax.net> Reviewed-By: Rich Trott <rtrott@gmail.com>
dbe0e87 to
ef0f5b1CompareTrott
commented
Dec 11, 2020
Landed in f49cef5...ef0f5b1 |
It's desirable to retain async_contexts active at callsites of
perf_hooks.performance.mark() and alike in the subsequent
PerformanceObserver invocations such that the latter can access e.g.
associated AsyncLocalStorage instances.
In working towards this goal replace the node::MakeCallback(...,
async_context{0, 0}) in PerformanceEntry::doNotify() by the new
node::MakeSyncCallback() introduced specifically for this purpose.
This change will retain the original async_context, if any, in
perf_hook's observersCallback() and thus, for the subsequent doNotify()
on unbuffered PerformanceObservers.
Co-Authored-By: ZauberNerd <zaubernerd@zaubernerd.de>
PR-URL: #36343
Reviewed-By: James M Snell <jasnell@gmail.com>
Reviewed-By: Anna Henningsen <anna@addaleax.net>
Reviewed-By: Rich Trott <rtrott@gmail.com>This reverts commit 009e418. AFAIU the discussion at [1], PerformanceObserver had been made to inherit from AsyncResource more or less as a band-aid in lack of a better async_context candidate to invoke it in. In order to enable access to AsyncLocalStores from PerformanceObservers invoked synchronously through e.g. measure() or mark(), the current async_context, if any, should be retained. Note that this is a breaking change, but - as has been commented at [1], PerformanceObserver being derived from AsyncResource is a "minor divergence from the spec" anyway, - to my knowledge this is an internal implementation detail which has never been documented and - I can't think of a good reason why existing PerformanceObserver implementations would possibly rely on it. OTOH, it's probably worthwhile to not potentially invoke before() and after() async_hooks for each and every PerformanceObserver notification. [1] #18789 Co-Authored-By: ZauberNerd <zaubernerd@zaubernerd.de> PR-URL: #36343 Reviewed-By: James M Snell <jasnell@gmail.com> Reviewed-By: Anna Henningsen <anna@addaleax.net> Reviewed-By: Rich Trott <rtrott@gmail.com>
This test proves that the PerformanceObserver callback gets called with the async context of the callsite of performance.mark()/measure() and therefore AsyncLocalStorage can be used inside a PerformanceObserver. PR: #36343 PR-URL: #36343 Reviewed-By: James M Snell <jasnell@gmail.com> Reviewed-By: Anna Henningsen <anna@addaleax.net> Reviewed-By: Rich Trott <rtrott@gmail.com>
Problem description:
Using the
perf_hooksmodule (specifically the User Timing API), to collect timings in an async context (i.e. to collect metrics during the lifetime of an HTTP request), it is not possible to correlate thePerformanceEntrys with a single request (except for using workarounds, such as prefixing all mark names with a unique id).This is required, when implementing e.g. tracing solutions, where one wants to have timings per requests.
Solution:
With the changes in this PR we are able to retain async contexts when calling the
PerformanceObserver, which enables us to, for example, access anAsyncLocalStoragein the observer callback to store the collected metrics per request.Additional notes:
PerformanceObserveris buffered.Questions:
Checklist
make -j4 test(UNIX), orvcbuild test(Windows) passes