From 181410ce863ccc60b8fc36f189824adb01b62813 Mon Sep 17 00:00:00 2001 From: Jan Stola Date: Sun, 8 Oct 2023 13:55:31 +0200 Subject: [PATCH 1/9] Adding resolve (= settle) promise hook into the reject promise function. --- .../truffle/js/nodes/promise/CreateResolvingFunctionNode.java | 1 + 1 file changed, 1 insertion(+) diff --git a/graal-js/src/com.oracle.truffle.js/src/com/oracle/truffle/js/nodes/promise/CreateResolvingFunctionNode.java b/graal-js/src/com.oracle.truffle.js/src/com/oracle/truffle/js/nodes/promise/CreateResolvingFunctionNode.java index 2fe848b55a7..6d3293ded26 100644 --- a/graal-js/src/com.oracle.truffle.js/src/com/oracle/truffle/js/nodes/promise/CreateResolvingFunctionNode.java +++ b/graal-js/src/com.oracle.truffle.js/src/com/oracle/truffle/js/nodes/promise/CreateResolvingFunctionNode.java @@ -274,6 +274,7 @@ public Object execute(VirtualFrame frame) { return Undefined.instance; } alreadyResolved.value = true; + context.notifyPromiseHook(PromiseHook.TYPE_RESOLVE, promise); return rejectPromiseNode.execute(promise, reason); } From c46bb840e828b471f91fd6f226750b0544545136 Mon Sep 17 00:00:00 2001 From: Jan Stola Date: Sun, 8 Oct 2023 13:57:50 +0200 Subject: [PATCH 2/9] Adding "before" and "after" promise hooks into PromiseResolveThenableNode. --- .../truffle/js/nodes/promise/PromiseResolveThenableNode.java | 3 +++ 1 file changed, 3 insertions(+) diff --git a/graal-js/src/com.oracle.truffle.js/src/com/oracle/truffle/js/nodes/promise/PromiseResolveThenableNode.java b/graal-js/src/com.oracle.truffle.js/src/com/oracle/truffle/js/nodes/promise/PromiseResolveThenableNode.java index 7b58688e6da..f1aa837c6cf 100644 --- a/graal-js/src/com.oracle.truffle.js/src/com/oracle/truffle/js/nodes/promise/PromiseResolveThenableNode.java +++ b/graal-js/src/com.oracle.truffle.js/src/com/oracle/truffle/js/nodes/promise/PromiseResolveThenableNode.java @@ -49,6 +49,7 @@ import com.oracle.truffle.js.runtime.JSArguments; import com.oracle.truffle.js.runtime.JSContext; import com.oracle.truffle.js.runtime.JobCallback; +import com.oracle.truffle.js.runtime.PromiseHook; import com.oracle.truffle.js.runtime.builtins.JSPromiseObject; import com.oracle.truffle.js.runtime.objects.JSDynamicObject; import com.oracle.truffle.js.runtime.objects.Undefined; @@ -92,9 +93,11 @@ public Object execute(JSPromiseObject promiseToResolve, Object thenable, JobCall JSAgent agent = getRealm().getAgent(); try { var previousContextMapping = agent.asyncContextSwap(then.asyncContextSnapshot()); + context.notifyPromiseHook(PromiseHook.TYPE_BEFORE, promiseToResolve); try { return callResolveNode.executeCall(JSArguments.create(thenable, then.callback(), resolve, reject)); } finally { + context.notifyPromiseHook(PromiseHook.TYPE_AFTER, promiseToResolve); agent.asyncContextSwap(previousContextMapping); } } catch (AbstractTruffleException ex) { From 32a33ec1bfcbd92365fce777fbd892322c51ccf2 Mon Sep 17 00:00:00 2001 From: Jan Stola Date: Sun, 8 Oct 2023 14:01:25 +0200 Subject: [PATCH 3/9] Do not eliminate the throwaway capability when a promise hook is registered. --- .../oracle/truffle/js/nodes/control/AbstractAwaitNode.java | 2 +- .../src/com/oracle/truffle/js/runtime/JSContext.java | 6 +++++- 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/graal-js/src/com.oracle.truffle.js/src/com/oracle/truffle/js/nodes/control/AbstractAwaitNode.java b/graal-js/src/com.oracle.truffle.js/src/com/oracle/truffle/js/nodes/control/AbstractAwaitNode.java index 9488fea9ecf..a4076aed7ea 100644 --- a/graal-js/src/com.oracle.truffle.js/src/com/oracle/truffle/js/nodes/control/AbstractAwaitNode.java +++ b/graal-js/src/com.oracle.truffle.js/src/com/oracle/truffle/js/nodes/control/AbstractAwaitNode.java @@ -221,7 +221,7 @@ private JSPromiseObject promiseResolve(Object value) { } private PromiseCapabilityRecord newThrowawayCapability() { - if (context.getEcmaScriptVersion() >= JSConfig.ECMAScript2019) { + if (context.getEcmaScriptVersion() >= JSConfig.ECMAScript2019 && !context.hasPromiseHook()) { return null; } PromiseCapabilityRecord throwawayCapability = newPromiseCapability(); diff --git a/graal-js/src/com.oracle.truffle.js/src/com/oracle/truffle/js/runtime/JSContext.java b/graal-js/src/com.oracle.truffle.js/src/com/oracle/truffle/js/runtime/JSContext.java index 470a67bb5e1..7fe699cebf0 100644 --- a/graal-js/src/com.oracle.truffle.js/src/com/oracle/truffle/js/runtime/JSContext.java +++ b/graal-js/src/com.oracle.truffle.js/src/com/oracle/truffle/js/runtime/JSContext.java @@ -1646,8 +1646,12 @@ private void invalidatePromiseHookNotUsedAssumption() { } } + public final boolean hasPromiseHook() { + return !promiseHookNotUsedAssumption.isValid() && (promiseHook != null); + } + public final void notifyPromiseHook(int changeType, JSDynamicObject promise) { - if (!promiseHookNotUsedAssumption.isValid() && promiseHook != null) { + if (hasPromiseHook()) { JSRealm realm = JSRealm.getMain(null); if (changeType == -1) { // Information about parent for the incoming INIT event From 03c7479e3df22a66c284c37cb6c3ca18cafa4195 Mon Sep 17 00:00:00 2001 From: Jan Stola Date: Sun, 8 Oct 2023 14:04:00 +0200 Subject: [PATCH 4/9] Correction of the parent of the throwaway capability's promise. --- .../com/oracle/truffle/js/nodes/control/AbstractAwaitNode.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/graal-js/src/com.oracle.truffle.js/src/com/oracle/truffle/js/nodes/control/AbstractAwaitNode.java b/graal-js/src/com.oracle.truffle.js/src/com/oracle/truffle/js/nodes/control/AbstractAwaitNode.java index a4076aed7ea..e7d22c75f17 100644 --- a/graal-js/src/com.oracle.truffle.js/src/com/oracle/truffle/js/nodes/control/AbstractAwaitNode.java +++ b/graal-js/src/com.oracle.truffle.js/src/com/oracle/truffle/js/nodes/control/AbstractAwaitNode.java @@ -161,10 +161,10 @@ protected final Object suspendAwait(VirtualFrame frame, Object value) { JSPromiseObject promise = promiseResolve(value); JSFunctionObject onFulfilled = createAwaitFulfilledFunction(resumeTarget, asyncContext, generatorOrCapability); JSFunctionObject onRejected = createAwaitRejectedFunction(resumeTarget, asyncContext, generatorOrCapability); + context.notifyPromiseHook(-1 /* parent info */, promise); PromiseCapabilityRecord throwawayCapability = newThrowawayCapability(); fillAsyncStackTrace(frame, onFulfilled, onRejected); - context.notifyPromiseHook(-1 /* parent info */, promise); echoInput(frame, promise); performPromiseThenNode.execute(promise, onFulfilled, onRejected, throwawayCapability); From e722d4ac94bf702f2c675ddc989f669214fe033b Mon Sep 17 00:00:00 2001 From: Jan Stola Date: Sun, 8 Oct 2023 14:06:49 +0200 Subject: [PATCH 5/9] Throwaway capability should not break the async stack trace. --- .../truffle/js/nodes/control/AbstractAwaitNode.java | 1 + .../js/nodes/promise/PromiseReactionJobNode.java | 2 +- .../js/runtime/objects/PromiseCapabilityRecord.java | 12 +++++++++++- 3 files changed, 13 insertions(+), 2 deletions(-) diff --git a/graal-js/src/com.oracle.truffle.js/src/com/oracle/truffle/js/nodes/control/AbstractAwaitNode.java b/graal-js/src/com.oracle.truffle.js/src/com/oracle/truffle/js/nodes/control/AbstractAwaitNode.java index e7d22c75f17..de8fbcca1d1 100644 --- a/graal-js/src/com.oracle.truffle.js/src/com/oracle/truffle/js/nodes/control/AbstractAwaitNode.java +++ b/graal-js/src/com.oracle.truffle.js/src/com/oracle/truffle/js/nodes/control/AbstractAwaitNode.java @@ -225,6 +225,7 @@ private PromiseCapabilityRecord newThrowawayCapability() { return null; } PromiseCapabilityRecord throwawayCapability = newPromiseCapability(); + throwawayCapability.markAsThrowaway(); ((JSPromiseObject) throwawayCapability.getPromise()).setIsHandled(true); return throwawayCapability; } diff --git a/graal-js/src/com.oracle.truffle.js/src/com/oracle/truffle/js/nodes/promise/PromiseReactionJobNode.java b/graal-js/src/com.oracle.truffle.js/src/com/oracle/truffle/js/nodes/promise/PromiseReactionJobNode.java index 6ee5bd90caf..068efc3b9f8 100644 --- a/graal-js/src/com.oracle.truffle.js/src/com/oracle/truffle/js/nodes/promise/PromiseReactionJobNode.java +++ b/graal-js/src/com.oracle.truffle.js/src/com/oracle/truffle/js/nodes/promise/PromiseReactionJobNode.java @@ -225,7 +225,7 @@ protected List findAsynchronousFrames(Frame frame) { JSDynamicObject functionObject = JSFrameUtil.getFunctionObject(frame); PromiseReactionRecord reaction = (PromiseReactionRecord) getReaction.getValue(functionObject); PromiseCapabilityRecord promiseCapability = reaction.getCapability(); - if (promiseCapability != null) { + if (promiseCapability != null && !promiseCapability.isThrowaway()) { return AwaitNode.findAsyncStackFramesFromPromise(promiseCapability.getPromise()); } else if (reaction.getHandler() != null && reaction.getHandler().callback() instanceof JSFunctionObject callbackFunction) { return AwaitNode.findAsyncStackFramesFromHandler(callbackFunction); diff --git a/graal-js/src/com.oracle.truffle.js/src/com/oracle/truffle/js/runtime/objects/PromiseCapabilityRecord.java b/graal-js/src/com.oracle.truffle.js/src/com/oracle/truffle/js/runtime/objects/PromiseCapabilityRecord.java index 92435aa398f..db62371b392 100644 --- a/graal-js/src/com.oracle.truffle.js/src/com/oracle/truffle/js/runtime/objects/PromiseCapabilityRecord.java +++ b/graal-js/src/com.oracle.truffle.js/src/com/oracle/truffle/js/runtime/objects/PromiseCapabilityRecord.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2018, 2022, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2018, 2023, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * The Universal Permissive License (UPL), Version 1.0 @@ -44,6 +44,7 @@ public final class PromiseCapabilityRecord { private JSDynamicObject promise; private Object resolve; private Object reject; + private boolean throwaway; private PromiseCapabilityRecord(JSDynamicObject promise, JSDynamicObject resolve, JSDynamicObject reject) { this.promise = promise; @@ -78,4 +79,13 @@ public void setResolve(Object resolve) { public void setReject(Object reject) { this.reject = reject; } + + public void markAsThrowaway() { + this.throwaway = true; + } + + public boolean isThrowaway() { + return this.throwaway; + } + } From 357fdbe4b3d72150a855517c7dcd5fb629d29787 Mon Sep 17 00:00:00 2001 From: Jan Stola Date: Sun, 8 Oct 2023 14:07:52 +0200 Subject: [PATCH 6/9] Updating the status of async-hooks test-suite. --- graal-nodejs/test/async-hooks/async-hooks.status | 6 ------ 1 file changed, 6 deletions(-) diff --git a/graal-nodejs/test/async-hooks/async-hooks.status b/graal-nodejs/test/async-hooks/async-hooks.status index 6f69b69daf0..cebf2a48ef7 100644 --- a/graal-nodejs/test/async-hooks/async-hooks.status +++ b/graal-nodejs/test/async-hooks/async-hooks.status @@ -6,16 +6,10 @@ prefix async-hooks [true] # This section applies to all platforms -# PromiseHook events in graal-nodejs (for await) do not match the events generated by V8 -test-async-local-storage-async-await: FAIL -test-async-local-storage-async-functions: FAIL - # Randomly failing GC-based test test-async-local-storage-gcable: SKIP # Unclassified -test-async-local-storage-errors: FAIL -test-async-local-storage-thenable: FAIL test-destroy-not-blocked: FAIL [$system==win32] From b8fb1ee1604368a8a8b7a8c62bdbaf4683418f3f Mon Sep 17 00:00:00 2001 From: Jan Stola Date: Mon, 9 Oct 2023 09:29:58 +0200 Subject: [PATCH 7/9] Promise hooks are allowed to throw. --- .../src/com/oracle/truffle/js/runtime/JSContext.java | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/graal-js/src/com.oracle.truffle.js/src/com/oracle/truffle/js/runtime/JSContext.java b/graal-js/src/com.oracle.truffle.js/src/com/oracle/truffle/js/runtime/JSContext.java index 7fe699cebf0..4d03f9fb703 100644 --- a/graal-js/src/com.oracle.truffle.js/src/com/oracle/truffle/js/runtime/JSContext.java +++ b/graal-js/src/com.oracle.truffle.js/src/com/oracle/truffle/js/runtime/JSContext.java @@ -1658,7 +1658,12 @@ public final void notifyPromiseHook(int changeType, JSDynamicObject promise) { realm.storeParentPromise(promise); } else { JSDynamicObject parent = (changeType == PromiseHook.TYPE_INIT) ? realm.fetchParentPromise() : Undefined.instance; - notifyPromiseHookImpl(changeType, promise, parent); + try { + notifyPromiseHookImpl(changeType, promise, parent); + } catch (GraalJSException ex) { + // Resembles ReportMessageFromMicrotask() + notifyPromiseRejectionTracker(JSPromise.create(this, getRealm()), JSPromise.REJECTION_TRACKER_OPERATION_REJECT, ex.getErrorObject()); + } } } } From f5cf69dc8bc2b44d4ab3bdc10255317d4417bc85 Mon Sep 17 00:00:00 2001 From: Jan Stola Date: Sun, 8 Oct 2023 14:08:36 +0200 Subject: [PATCH 8/9] Updating the status of parallel test-suite. --- graal-nodejs/test/parallel/parallel.status | 8 -------- 1 file changed, 8 deletions(-) diff --git a/graal-nodejs/test/parallel/parallel.status b/graal-nodejs/test/parallel/parallel.status index 7a9b3977712..968987ab338 100644 --- a/graal-nodejs/test/parallel/parallel.status +++ b/graal-nodejs/test/parallel/parallel.status @@ -8,10 +8,6 @@ prefix parallel ### Graal.js-specific ### -# PromiseHook events in graal-nodejs (for await) do not match the events generated by V8 -test-async-hooks-correctly-switch-promise-hook: FAIL -test-async-hooks-execution-async-resource-await: FAIL - # Triggers stack-overflow that may not be handled well test-async-wrap-pop-id-during-load: SKIP test-promise-reject-callback-exception: SKIP @@ -183,9 +179,7 @@ test-trace-events-worker-metadata: SKIP test-trace-events-worker-metadata-with-name: SKIP # Unclassified -test-async-hooks-async-await: FAIL test-async-hooks-http-parser-destroy: SKIP -test-async-local-storage-contexts: FAIL test-crypto-op-during-process-exit: SKIP test-crypto-sign-verify: FAIL test-domain-no-error-handler-abort-on-uncaught-0: FAIL @@ -206,8 +200,6 @@ test-gc-tls-external-memory: SKIP test-http-server-keepalive-req-gc: SKIP test-http2-response-splitting: SKIP test-net-connect-memleak: FAIL -test-promise-hook-exceptions: FAIL -test-promise-hook-on-resolve: FAIL test-repl: FAIL test-runner-output: FAIL test-runner-watch-mode: SKIP From 4d8a0bf498eae8fc8e74022fca36ee3d8dae0b7d Mon Sep 17 00:00:00 2001 From: Jan Stola Date: Sun, 8 Oct 2023 23:03:52 +0200 Subject: [PATCH 9/9] Regression test of the retrieval of AsyncLocalStorage after await. --- graal-nodejs/test/graal/unit/other.js | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/graal-nodejs/test/graal/unit/other.js b/graal-nodejs/test/graal/unit/other.js index 6fdd40236b6..a84e1db8c52 100644 --- a/graal-nodejs/test/graal/unit/other.js +++ b/graal-nodejs/test/graal/unit/other.js @@ -39,6 +39,7 @@ * SOFTWARE. */ +var async_hooks = require('async_hooks'); var assert = require('assert'); var fs = require('fs'); var module = require('./_unit'); @@ -110,4 +111,19 @@ describe('Other', function () { it('should not define FinalizationGroup', function () { assert.strictEqual(global.FinalizationGroup, undefined); }); + it('should keep local storage after await', function(done) { + const { AsyncLocalStorage } = async_hooks; + const asyncLocalStorage = new AsyncLocalStorage(); + + asyncLocalStorage.run({id: 42}, async () => { + try { + assert.strictEqual(asyncLocalStorage.getStore().id, 42); + await 211; + assert.strictEqual(asyncLocalStorage.getStore().id, 42); + done(); + } catch (err) { + done(err); + } + }); + }); });