v8/test/mjsunit/promise-hooks.js

Ignoring revisions in .git-blame-ignore-revs. Click here to bypass and see the normal blame view.

295 lines
7.7 KiB
JavaScript
Raw Normal View History

Reland "[api] JSFunction PromiseHook for v8::Context" This is a reland of d5457f5fb7ea05ca05a697599ffa50d35c1ae3c7 after a speculative revert. Additionally it fixes an issue with throwing promise hooks. Original change's description: > [api] JSFunction PromiseHook for v8::Context > > This will enable Node.js to get much better performance from async_hooks > as currently PromiseHook delegates to C++ for the hook function and then > Node.js delegates it right back to JavaScript, introducing several > unnecessary barrier hops in code that gets called very, very frequently > in modern, promise-heavy applications. > > This API mirrors the form of the original C++ function based PromiseHook > API, however it is intentionally separate to allow it to use JSFunctions > triggered within generated code to, as much as possible, avoid entering > runtime functions entirely. > > Because PromiseHook has internal use also, beyond just the Node.js use, > I have opted to leave the existing API intact and keep this separate to > avoid conflicting with any possible behaviour expectations of other API > users. > > The design ideas for this new API stemmed from discussion with some V8 > team members at a previous Node.js Diagnostics Summit hosted by Google > in Munich, and the relevant documentation of the discussion can be found > here: https://docs.google.com/document/d/1g8OrG5lMIUhRn1zbkutgY83MiTSMx-0NHDs8Bf-nXxM/edit#heading=h.w1bavzz80l1e > > A summary of the reasons for why this new design is important can be > found here: https://docs.google.com/document/d/1vtgoT4_kjgOr-Bl605HR2T6_SC-C8uWzYaOPDK5pmRo/edit?usp=sharing > > Bug: v8:11025 > Change-Id: I0b403b00c37d3020b5af07b654b860659d3a7697 > Reviewed-on: https://chromium-review.googlesource.com/c/v8/v8/+/2759188 > Reviewed-by: Marja Hölttä <marja@chromium.org> > Reviewed-by: Camillo Bruni <cbruni@chromium.org> > Reviewed-by: Anton Bikineev <bikineev@chromium.org> > Reviewed-by: Igor Sheludko <ishell@chromium.org> > Commit-Queue: Camillo Bruni <cbruni@chromium.org> > Cr-Commit-Position: refs/heads/master@{#73858} Bug: v8:11025 Bug: chromium:1197475 Change-Id: I73a71e97d9c3dff89a2b092c3fe4adff81ede8ef Reviewed-on: https://chromium-review.googlesource.com/c/v8/v8/+/2823917 Reviewed-by: Marja Hölttä <marja@chromium.org> Reviewed-by: Igor Sheludko <ishell@chromium.org> Reviewed-by: Anton Bikineev <bikineev@chromium.org> Reviewed-by: Camillo Bruni <cbruni@chromium.org> Commit-Queue: Camillo Bruni <cbruni@chromium.org> Cr-Commit-Position: refs/heads/master@{#74071}
2021-04-14 22:44:39 +00:00
// Copyright 2020 the V8 project authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
// Flags: --allow-natives-syntax --turbofan --no-always-turbofan --no-stress-opt --deopt-every-n-times=0 --ignore-unhandled-promises
Reland "[api] JSFunction PromiseHook for v8::Context" This is a reland of d5457f5fb7ea05ca05a697599ffa50d35c1ae3c7 after a speculative revert. Additionally it fixes an issue with throwing promise hooks. Original change's description: > [api] JSFunction PromiseHook for v8::Context > > This will enable Node.js to get much better performance from async_hooks > as currently PromiseHook delegates to C++ for the hook function and then > Node.js delegates it right back to JavaScript, introducing several > unnecessary barrier hops in code that gets called very, very frequently > in modern, promise-heavy applications. > > This API mirrors the form of the original C++ function based PromiseHook > API, however it is intentionally separate to allow it to use JSFunctions > triggered within generated code to, as much as possible, avoid entering > runtime functions entirely. > > Because PromiseHook has internal use also, beyond just the Node.js use, > I have opted to leave the existing API intact and keep this separate to > avoid conflicting with any possible behaviour expectations of other API > users. > > The design ideas for this new API stemmed from discussion with some V8 > team members at a previous Node.js Diagnostics Summit hosted by Google > in Munich, and the relevant documentation of the discussion can be found > here: https://docs.google.com/document/d/1g8OrG5lMIUhRn1zbkutgY83MiTSMx-0NHDs8Bf-nXxM/edit#heading=h.w1bavzz80l1e > > A summary of the reasons for why this new design is important can be > found here: https://docs.google.com/document/d/1vtgoT4_kjgOr-Bl605HR2T6_SC-C8uWzYaOPDK5pmRo/edit?usp=sharing > > Bug: v8:11025 > Change-Id: I0b403b00c37d3020b5af07b654b860659d3a7697 > Reviewed-on: https://chromium-review.googlesource.com/c/v8/v8/+/2759188 > Reviewed-by: Marja Hölttä <marja@chromium.org> > Reviewed-by: Camillo Bruni <cbruni@chromium.org> > Reviewed-by: Anton Bikineev <bikineev@chromium.org> > Reviewed-by: Igor Sheludko <ishell@chromium.org> > Commit-Queue: Camillo Bruni <cbruni@chromium.org> > Cr-Commit-Position: refs/heads/master@{#73858} Bug: v8:11025 Bug: chromium:1197475 Change-Id: I73a71e97d9c3dff89a2b092c3fe4adff81ede8ef Reviewed-on: https://chromium-review.googlesource.com/c/v8/v8/+/2823917 Reviewed-by: Marja Hölttä <marja@chromium.org> Reviewed-by: Igor Sheludko <ishell@chromium.org> Reviewed-by: Anton Bikineev <bikineev@chromium.org> Reviewed-by: Camillo Bruni <cbruni@chromium.org> Commit-Queue: Camillo Bruni <cbruni@chromium.org> Cr-Commit-Position: refs/heads/master@{#74071}
2021-04-14 22:44:39 +00:00
let log = [];
let asyncId = 0;
function logEvent (type, args) {
const promise = args[0];
promise.asyncId = promise.asyncId || ++asyncId;
log.push({
type,
promise,
parent: args[1],
argsLength: args.length
})
}
function initHook(...args) {
logEvent('init', args);
}
function resolveHook(...args) {
logEvent('resolve', args);
}
function beforeHook(...args) {
logEvent('before', args);
}
function afterHook(...args) {
logEvent('after', args);
}
function printLog(message) {
console.log(` --- ${message} --- `)
for (const event of log) {
console.log(JSON.stringify(event))
}
}
let has_promise_hooks = false;
try {
d8.promise.setHooks();
has_promise_hooks = true;
} catch {
has_promise_hooks = false;
}
Reland "[api] JSFunction PromiseHook for v8::Context" This is a reland of d5457f5fb7ea05ca05a697599ffa50d35c1ae3c7 after a speculative revert. Additionally it fixes an issue with throwing promise hooks. Original change's description: > [api] JSFunction PromiseHook for v8::Context > > This will enable Node.js to get much better performance from async_hooks > as currently PromiseHook delegates to C++ for the hook function and then > Node.js delegates it right back to JavaScript, introducing several > unnecessary barrier hops in code that gets called very, very frequently > in modern, promise-heavy applications. > > This API mirrors the form of the original C++ function based PromiseHook > API, however it is intentionally separate to allow it to use JSFunctions > triggered within generated code to, as much as possible, avoid entering > runtime functions entirely. > > Because PromiseHook has internal use also, beyond just the Node.js use, > I have opted to leave the existing API intact and keep this separate to > avoid conflicting with any possible behaviour expectations of other API > users. > > The design ideas for this new API stemmed from discussion with some V8 > team members at a previous Node.js Diagnostics Summit hosted by Google > in Munich, and the relevant documentation of the discussion can be found > here: https://docs.google.com/document/d/1g8OrG5lMIUhRn1zbkutgY83MiTSMx-0NHDs8Bf-nXxM/edit#heading=h.w1bavzz80l1e > > A summary of the reasons for why this new design is important can be > found here: https://docs.google.com/document/d/1vtgoT4_kjgOr-Bl605HR2T6_SC-C8uWzYaOPDK5pmRo/edit?usp=sharing > > Bug: v8:11025 > Change-Id: I0b403b00c37d3020b5af07b654b860659d3a7697 > Reviewed-on: https://chromium-review.googlesource.com/c/v8/v8/+/2759188 > Reviewed-by: Marja Hölttä <marja@chromium.org> > Reviewed-by: Camillo Bruni <cbruni@chromium.org> > Reviewed-by: Anton Bikineev <bikineev@chromium.org> > Reviewed-by: Igor Sheludko <ishell@chromium.org> > Commit-Queue: Camillo Bruni <cbruni@chromium.org> > Cr-Commit-Position: refs/heads/master@{#73858} Bug: v8:11025 Bug: chromium:1197475 Change-Id: I73a71e97d9c3dff89a2b092c3fe4adff81ede8ef Reviewed-on: https://chromium-review.googlesource.com/c/v8/v8/+/2823917 Reviewed-by: Marja Hölttä <marja@chromium.org> Reviewed-by: Igor Sheludko <ishell@chromium.org> Reviewed-by: Anton Bikineev <bikineev@chromium.org> Reviewed-by: Camillo Bruni <cbruni@chromium.org> Commit-Queue: Camillo Bruni <cbruni@chromium.org> Cr-Commit-Position: refs/heads/master@{#74071}
2021-04-14 22:44:39 +00:00
function assertNextEvent(type, args) {
const [ promiseOrId, parentOrId ] = args;
const nextEvent = log.shift();
assertEquals(type, nextEvent.type);
assertEquals(type === 'init' ? 2 : 1, nextEvent.argsLength);
assertTrue(nextEvent.promise instanceof Promise);
if (promiseOrId instanceof Promise) {
assertEquals(promiseOrId, nextEvent.promise);
} else {
assertTrue(typeof promiseOrId === 'number');
assertEquals(promiseOrId, nextEvent.promise?.asyncId);
}
if (parentOrId instanceof Promise) {
assertEquals(parentOrId, nextEvent.parent);
assertTrue(nextEvent.parent instanceof Promise);
} else if (typeof parentOrId === 'number') {
assertEquals(parentOrId, nextEvent.parent?.asyncId);
assertTrue(nextEvent.parent instanceof Promise);
} else {
assertEquals(undefined, parentOrId);
assertEquals(undefined, nextEvent.parent);
}
}
function assertEmptyLog() {
assertEquals(0, log.length);
asyncId = 0;
log = [];
}
// Verify basic log structure of different promise behaviours
function basicTest() {
d8.promise.setHooks(initHook, beforeHook, afterHook, resolveHook);
// `new Promise(...)` triggers init event with correct promise
var done, p1 = new Promise(r => done = r);
%PerformMicrotaskCheckpoint();
assertNextEvent('init', [ p1 ]);
assertEmptyLog();
// `promise.then(...)` triggers init event with correct promise and parent
var p2 = p1.then(() => { });
%PerformMicrotaskCheckpoint();
assertNextEvent('init', [ p2, p1 ]);
assertEmptyLog();
// `resolve(...)` triggers resolve event and any already attached continuations
done();
%PerformMicrotaskCheckpoint();
assertNextEvent('resolve', [ p1 ]);
assertNextEvent('before', [ p2 ]);
assertNextEvent('resolve', [ p2 ]);
assertNextEvent('after', [ p2 ]);
assertEmptyLog();
// `reject(...)` triggers the resolve event
var done, p3 = new Promise((_, r) => done = r);
done();
%PerformMicrotaskCheckpoint();
assertNextEvent('init', [ p3 ]);
assertNextEvent('resolve', [ p3 ]);
assertEmptyLog();
// `promise.catch(...)` triggers init event with correct promise and parent
// When the promise is already completed, the continuation should also run
// immediately at the next checkpoint.
var p4 = p3.catch(() => { });
%PerformMicrotaskCheckpoint();
assertNextEvent('init', [ p4, p3 ]);
assertNextEvent('before', [ p4 ]);
assertNextEvent('resolve', [ p4 ]);
assertNextEvent('after', [ p4 ]);
assertEmptyLog();
// Detach hooks
d8.promise.setHooks();
}
// Exceptions thrown in hook handlers should not raise or reject
function exceptions() {
function thrower() {
throw new Error('unexpected!');
}
// Init hook
d8.promise.setHooks(thrower);
assertDoesNotThrow(() => {
Promise.resolve()
.catch(assertUnreachable);
});
%PerformMicrotaskCheckpoint();
d8.promise.setHooks();
// Before hook
d8.promise.setHooks(undefined, thrower);
assertDoesNotThrow(() => {
Promise.resolve()
.then(() => {})
.catch(assertUnreachable);
});
%PerformMicrotaskCheckpoint();
d8.promise.setHooks();
// After hook
d8.promise.setHooks(undefined, undefined, thrower);
assertDoesNotThrow(() => {
Promise.resolve()
.then(() => {})
.catch(assertUnreachable);
});
%PerformMicrotaskCheckpoint();
d8.promise.setHooks();
// Resolve hook
d8.promise.setHooks(undefined, undefined, undefined, thrower);
assertDoesNotThrow(() => {
Promise.resolve()
.catch(assertUnreachable);
});
%PerformMicrotaskCheckpoint();
d8.promise.setHooks();
// Resolve hook for a reject
d8.promise.setHooks(undefined, undefined, undefined, thrower);
assertDoesNotThrow(() => {
Promise.reject()
.then(assertUnreachable)
.catch();
});
%PerformMicrotaskCheckpoint();
d8.promise.setHooks();
}
// For now, expect the optimizer to bail out on async functions
// when context promise hooks are attached.
function optimizerBailout(test, verify) {
// Warm up test method
%PrepareFunctionForOptimization(test);
assertUnoptimized(test);
test();
test();
test();
%PerformMicrotaskCheckpoint();
// Prove transition to optimized code when no hooks are present
assertUnoptimized(test);
%OptimizeFunctionOnNextCall(test);
test();
assertOptimized(test);
%PerformMicrotaskCheckpoint();
// Verify that attaching hooks deopts the async function
d8.promise.setHooks(initHook, beforeHook, afterHook, resolveHook);
// assertUnoptimized(test);
// Verify log structure of deoptimized call
%PrepareFunctionForOptimization(test);
test();
%PerformMicrotaskCheckpoint();
verify();
// Optimize and verify log structure again
%OptimizeFunctionOnNextCall(test);
test();
assertOptimized(test);
%PerformMicrotaskCheckpoint();
verify();
d8.promise.setHooks();
}
Revert "Fix Context PromiseHook behaviour with debugger enabled" This reverts commit 872b7faa32d837f9b166d750328357f856168e72. Reason for revert: Somewhat speculative revert because of https://ci.chromium.org/ui/p/v8/builders/ci/V8%20Linux%20-%20gc%20stress/39673/overview (reverting locally resolved the issue for me) Original change's description: > Fix Context PromiseHook behaviour with debugger enabled > > This is a solution for https://github.com/nodejs/node/issues/43148. > > Due to differences in behaviour between code with and without the debugger enabled, some promise lifecycle events were being missed and some extra ones were being added. This change resolves this and verifies the event sequence is consistent between code with and without the debugger. > > Change-Id: I3dabf1dceb14233226b1752083d659f1c2f97966 > Reviewed-on: https://chromium-review.googlesource.com/c/v8/v8/+/3779922 > Reviewed-by: Victor Gomes <victorgomes@chromium.org> > Commit-Queue: Camillo Bruni <cbruni@chromium.org> > Reviewed-by: Camillo Bruni <cbruni@chromium.org> > Cr-Commit-Position: refs/heads/main@{#82132} Change-Id: I3e05adead5d8033906055e0741854da68aade2ac No-Presubmit: true No-Tree-Checks: true No-Try: true Reviewed-on: https://chromium-review.googlesource.com/c/v8/v8/+/3804859 Commit-Queue: Nico Hartmann <nicohartmann@chromium.org> Owners-Override: Nico Hartmann <nicohartmann@chromium.org> Bot-Commit: Rubber Stamper <rubber-stamper@appspot.gserviceaccount.com> Commit-Queue: Rubber Stamper <rubber-stamper@appspot.gserviceaccount.com> Auto-Submit: Nico Hartmann <nicohartmann@chromium.org> Cr-Commit-Position: refs/heads/main@{#82136}
2022-08-02 11:59:40 +00:00
if (has_promise_hooks) {
optimizerBailout(async () => {
await Promise.resolve();
}, () => {
assertNextEvent('init', [ 1 ]);
assertNextEvent('init', [ 2 ]);
assertNextEvent('resolve', [ 2 ]);
assertNextEvent('init', [ 3, 2 ]);
assertNextEvent('before', [ 3 ]);
assertNextEvent('resolve', [ 1 ]);
assertNextEvent('resolve', [ 3 ]);
assertNextEvent('after', [ 3 ]);
assertEmptyLog();
});
optimizerBailout(async () => {
await { then (cb) { cb() } };
}, () => {
assertNextEvent('init', [ 1 ]);
assertNextEvent('init', [ 2, 1 ]);
assertNextEvent('init', [ 3, 2 ]);
assertNextEvent('before', [ 2 ]);
assertNextEvent('resolve', [ 2 ]);
assertNextEvent('after', [ 2 ]);
assertNextEvent('before', [ 3 ]);
assertNextEvent('resolve', [ 1 ]);
assertNextEvent('resolve', [ 3 ]);
assertNextEvent('after', [ 3 ]);
assertEmptyLog();
});
basicTest();
exceptions();
(function regress1126309() {
function __f_16(test) {
test();
d8.promise.setHooks(undefined, () => {});
%PerformMicrotaskCheckpoint();
d8.promise.setHooks();
}
__f_16(async () => { await Promise.resolve()});
})();
(function boundFunction() {
function hook() {};
const bound = hook.bind(this);
d8.promise.setHooks(bound, bound, bound, bound);
Promise.resolve();
Promise.reject();
%PerformMicrotaskCheckpoint();
d8.promise.setHooks();
})();
(function promiseAll() {
let initCount = 0;
d8.promise.setHooks(() => { initCount++});
Promise.all([Promise.resolve(1)]);
%PerformMicrotaskCheckpoint();
assertEquals(initCount, 3);
d8.promise.setHooks();
})();
(function overflow(){
d8.promise.setHooks(() => { new Promise(()=>{}) });
// Trigger overflow from JS code:
Promise.all([Promise.resolve(1)]);
%PerformMicrotaskCheckpoint();
d8.promise.setHooks();
});
}