Skip to content

Commit 994f446

Browse files
committed
[GR-72857] Top-level rejection should not clear the promise job queue.
PullRequest: js/3670
2 parents 4f4c263 + 23562e7 commit 994f446

File tree

2 files changed

+156
-2
lines changed
  • graal-js/src
    • com.oracle.truffle.js.test/src/com/oracle/truffle/js/test/regress
    • com.oracle.truffle.js/src/com/oracle/truffle/js/runtime

2 files changed

+156
-2
lines changed
Lines changed: 145 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,145 @@
1+
/*
2+
* Copyright (c) 2026, 2026, Oracle and/or its affiliates. All rights reserved.
3+
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
4+
*
5+
* The Universal Permissive License (UPL), Version 1.0
6+
*
7+
* Subject to the condition set forth below, permission is hereby granted to any
8+
* person obtaining a copy of this software, associated documentation and/or
9+
* data (collectively the "Software"), free of charge and under any and all
10+
* copyright rights in the Software, and any and all patent rights owned or
11+
* freely licensable by each licensor hereunder covering either (i) the
12+
* unmodified Software as contributed to or provided by such licensor, or (ii)
13+
* the Larger Works (as defined below), to deal in both
14+
*
15+
* (a) the Software, and
16+
*
17+
* (b) any piece of software and/or hardware listed in the lrgrwrks.txt file if
18+
* one is included with the Software each a "Larger Work" to which the Software
19+
* is contributed by such licensors),
20+
*
21+
* without restriction, including without limitation the rights to copy, create
22+
* derivative works of, display, perform, and distribute the Software and make,
23+
* use, sell, offer for sale, import, export, have made, and have sold the
24+
* Software and the Larger Work(s), and to sublicense the foregoing rights on
25+
* either these or other terms.
26+
*
27+
* This license is subject to the following condition:
28+
*
29+
* The above copyright notice and either this complete permission notice or at a
30+
* minimum a reference to the UPL must be included in all copies or substantial
31+
* portions of the Software.
32+
*
33+
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
34+
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
35+
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
36+
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
37+
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
38+
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
39+
* SOFTWARE.
40+
*/
41+
package com.oracle.truffle.js.test.regress;
42+
43+
import static org.junit.Assert.assertEquals;
44+
import static org.junit.Assert.assertTrue;
45+
import static org.junit.Assert.fail;
46+
47+
import com.oracle.truffle.js.lang.JavaScriptLanguage;
48+
import com.oracle.truffle.js.test.JSTest;
49+
50+
import org.graalvm.polyglot.Context;
51+
import org.graalvm.polyglot.PolyglotException;
52+
import org.graalvm.polyglot.Source;
53+
import org.graalvm.polyglot.Value;
54+
import org.graalvm.polyglot.proxy.ProxyExecutable;
55+
56+
import org.junit.Test;
57+
58+
public class GR72857 {
59+
60+
private static Source resolverInitSource() {
61+
return Source.newBuilder("js", """
62+
var resolver = Promise.withResolvers();
63+
globalThis.resolve = resolver.resolve;
64+
globalThis.reject = resolver.reject;
65+
await resolver.promise
66+
""", "source.mjs").buildLiteral();
67+
}
68+
69+
@Test
70+
public void testResolve() {
71+
try (Context context = JSTest.newContextBuilder().build()) {
72+
Value modulePromise = context.eval(resolverInitSource());
73+
boolean[] resolved = new boolean[1];
74+
modulePromise.invokeMember("then", (ProxyExecutable) (Value... arguments) -> {
75+
resolved[0] = true;
76+
return null;
77+
});
78+
Value resolve = context.getBindings("js").getMember("resolve");
79+
resolve.execute();
80+
assertTrue(resolved[0]);
81+
}
82+
}
83+
84+
@Test
85+
public void testReject() {
86+
try (Context context = JSTest.newContextBuilder().build()) {
87+
Value modulePromise = context.eval(resolverInitSource());
88+
boolean[] rejected = new boolean[1];
89+
modulePromise.invokeMember("catch", (ProxyExecutable) (Value... arguments) -> {
90+
rejected[0] = true;
91+
return null;
92+
});
93+
Value reject = context.getBindings("js").getMember("reject");
94+
String error = "myError";
95+
try {
96+
reject.execute(error);
97+
fail("should have thrown");
98+
} catch (PolyglotException ex) {
99+
assertTrue(ex.getMessage().contains(error));
100+
}
101+
assertTrue(rejected[0]);
102+
}
103+
}
104+
105+
private static final String CODE = """
106+
globalThis.log = '';
107+
108+
function appendToLog(s) {
109+
globalThis.log += s;
110+
};
111+
112+
Promise.resolve().then(x => x).then(x => x).then(x => appendToLog('D'));
113+
Promise.resolve().then(x => x).then(x => appendToLog('C'));
114+
Promise.resolve().then(x => appendToLog('B'));
115+
appendToLog('A');
116+
117+
throw new Error("someError");
118+
""";
119+
120+
@Test
121+
public void testScript() {
122+
testScriptOrModule(true);
123+
}
124+
125+
@Test
126+
public void testModule() {
127+
testScriptOrModule(false);
128+
}
129+
130+
private static void testScriptOrModule(boolean script) {
131+
try (Context context = JSTest.newContextBuilder().build()) {
132+
String mimeType = script ? JavaScriptLanguage.APPLICATION_MIME_TYPE : JavaScriptLanguage.MODULE_MIME_TYPE;
133+
Source source = Source.newBuilder("js", CODE, "source.js").mimeType(mimeType).buildLiteral();
134+
try {
135+
context.eval(source);
136+
fail("should have thrown");
137+
} catch (PolyglotException ex) {
138+
assertTrue(ex.getMessage().contains("someError"));
139+
}
140+
String log = context.getBindings("js").getMember("log").asString();
141+
assertEquals("ABCD", log);
142+
}
143+
}
144+
145+
}

graal-js/src/com.oracle.truffle.js/src/com/oracle/truffle/js/runtime/JSAgent.java

Lines changed: 11 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright (c) 2018, 2024, Oracle and/or its affiliates. All rights reserved.
2+
* Copyright (c) 2018, 2026, Oracle and/or its affiliates. All rights reserved.
33
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
44
*
55
* The Universal Permissive License (UPL), Version 1.0
@@ -53,6 +53,7 @@
5353
import org.graalvm.collections.Equivalence;
5454

5555
import com.oracle.truffle.api.CompilerDirectives.TruffleBoundary;
56+
import com.oracle.truffle.api.exception.AbstractTruffleException;
5657
import com.oracle.truffle.api.nodes.Node;
5758
import com.oracle.truffle.js.runtime.JSAgentWaiterList.JSAgentWaiterListEntry;
5859
import com.oracle.truffle.js.runtime.JSAgentWaiterList.WaiterRecord;
@@ -145,6 +146,7 @@ public void enqueueWaitAsyncPromiseJob(WaiterRecord waiter) {
145146

146147
@TruffleBoundary
147148
public final void processAllPromises(boolean processWeakRefs) {
149+
AbstractTruffleException topLevelRejection = null;
148150
try {
149151
interopBoundaryEnter();
150152
boolean checkWaiterRecords = !waitAsyncJobsQueue.isEmpty();
@@ -154,7 +156,11 @@ public final void processAllPromises(boolean processWeakRefs) {
154156
}
155157
if (!promiseJobsQueue.isEmpty()) {
156158
JSFunctionObject nextJob = promiseJobsQueue.pollLast();
157-
JSFunction.call(nextJob, Undefined.instance, JSArguments.EMPTY_ARGUMENTS_ARRAY);
159+
try {
160+
JSFunction.call(nextJob, Undefined.instance, JSArguments.EMPTY_ARGUMENTS_ARRAY);
161+
} catch (AbstractTruffleException ex) {
162+
topLevelRejection = ex;
163+
}
158164
checkWaiterRecords = true;
159165
}
160166
}
@@ -176,6 +182,9 @@ public final void processAllPromises(boolean processWeakRefs) {
176182
promiseRejectionTracker.promiseReactionJobsProcessed();
177183
}
178184
}
185+
if (topLevelRejection != null) {
186+
throw topLevelRejection;
187+
}
179188
}
180189

181190
private boolean processWaitAsyncJobs() {

0 commit comments

Comments
 (0)