-
Notifications
You must be signed in to change notification settings - Fork 5.4k
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
feat(core): add Deno.core.setPromiseHooks #15475
Conversation
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.
Thanks for looking into this!
@cjihrig please take a look if you find a moment |
Some points for consideration:
|
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.
Left a few comments. Also kicked off a CI run for you.
Is the current API similar to Node's API? |
Dismissing my review. Looking at the test, and comparing it to #8209, it would be useful to comment the test more.
This is intended as an internal low-level API on top of which a proper public API can be later constructed. Node.js has the There is also the This PR provides |
@lbguilherme please rebase and address the question from @cjihrig so we could land it for Deno v1.25 this week |
9b8b210
to
9fe6217
Compare
Done! This is now rebased on top of the current |
The only thing that still isn't obvious to me is why the events in the test are different from the ones in #8209, even though the test code appears to be the same. Did something change in V8, or was the original implementation buggy? In #8209, the first few events are: init |
When a promise first begins to resolve it will also trigger the creation of the promise related to the Previously there was some number of init events. With a more recent version of Deno something in the test utils changed and this number increased. This is why I'm doing a bogus await at the beginning of the test now, to ensure that any unrelated promise that is waiting to run gets a chance to run before the test starts. See this section: https://github.com/denoland/deno/pull/15475/files#diff-4ba8d96f30b74cf0a30189b1b111204e319d27a7785519bfec3941f6c5941190R26-R28 Now every event has an explainable reason to be there. |
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.
Thanks for the explanation. The code LGTM.
@bartlomieju since you've done most of the work on the test runner, I'll let you merge.
The test failure seems unrelated. Web tests about WebSocket. Should I do something? |
I re-ran the failing CI jobs. It looks like it was a flaky test. CI is green now. |
LGTM, thanks for picking this up |
Note that it's probably a good idea to bring this API to APM vendors to make sure that's the API they'd like or implement a basic APM on top of it and see if there are obvious issues. That said: I'd just land it as is (it looks reasonable) and worst case change it based on future APM feedback. |
@bartlomieju are there chances this will be included in deno |
No, we'll ship it in v1.26 |
@hastebrot just wondering, would you be using this for AsyncLocalStorage or for APMs/context tracking? |
I'd use it for an APM solution.
If it could work with an |
I believe this would be an equivalent implementation for Deno: import { Context, ROOT_CONTEXT } from '@opentelemetry/api';
import { AbstractAsyncHooksContextManager } from './AbstractAsyncHooksContextManager';
interface HookCallbacks {
init: (promise: Promise<unknown>) => void
before: (promise: Promise<unknown>) => void
after: (promise: Promise<unknown>) => void
resolve: (promise: Promise<unknown>) => void
}
const enabledCallbacks = new Set<HookCallbacks>();
Deno.core.setPromiseHooks(
(promise: Promise<unknown>) => {
for (const { init } of enabledCallbacks) {
init(promise);
}
},
(promise: Promise<unknown>) => {
for (const { before } of enabledCallbacks) {
before(promise);
}
},
(promise: Promise<unknown>) => {
for (const { after } of enabledCallbacks) {
after(promise);
}
},
(promise: Promise<unknown>) => {
for (const { resolve } of enabledCallbacks) {
resolve(promise);
}
},
);
export class AsyncHooksContextManager extends AbstractAsyncHooksContextManager {
private _contexts: Map<Promise<unknown>, Context> = new Map();
private _stack: Array<Context | undefined> = [];
private _callbacks: HookCallbacks = {
init: (promise) => {
const context = this._stack[this._stack.length - 1];
if (context !== undefined) {
this._contexts.set(promise, context);
}
},
before: (promise) => {
const context = this._contexts.get(promise);
if (context !== undefined) {
this._enterContext(context);
}
},
after: () => {
this._exitContext();
},
resolve: (promise) => {
this._contexts.delete(promise);
}
}
active(): Context {
return this._stack[this._stack.length - 1] ?? ROOT_CONTEXT;
}
with<A extends unknown[], F extends (...args: A) => ReturnType<F>>(
context: Context,
fn: F,
thisArg?: ThisParameterType<F>,
...args: A
): ReturnType<F> {
this._enterContext(context);
try {
return fn.call(thisArg!, ...args);
} finally {
this._exitContext();
}
}
enable(): this {
enabledCallbacks.add(this._callbacks);
return this;
}
disable(): this {
enabledCallbacks.delete(this._callbacks);
this._contexts.clear();
this._stack = [];
return this;
}
private _enterContext(context: Context) {
this._stack.push(context);
}
private _exitContext() {
this._stack.pop();
}
} As pretty much all async operation in Deno is implemented on top of Promises, tracking them should be enough to keep track of all async contexts. It should work with Again, it's fairly low level and the interface doesn't help. It would make sense to introduce a more friendly wrapper on the stdlib later on. |
That's definitely a good idea |
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.
LGTM
Fixes #5638.
Based on the previous work at #8209 by @benjamingr.
Unblocks denoland/std#2306.
This PR adds
Deno.core.setPromiseHooks
that will enable monitoring of async contexts. Please see previous discussion at the linked issues.