Skip to content

Commit 9d2d145

Browse files
fix(turbopack-ecmascript-runtime): prevent hanging when top level await is skipped (vercel/turborepo#8409)
### Description A "race condition" was causing the queue to never be resolved. ### Testing Instructions Added a new test for it Fixes #65278
1 parent ea505a4 commit 9d2d145

File tree

65 files changed

+457
-41
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

65 files changed

+457
-41
lines changed

crates/turbopack-ecmascript-runtime/js/src/shared/runtime-utils.ts

Lines changed: 20 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -333,12 +333,20 @@ const turbopackQueues = Symbol("turbopack queues");
333333
const turbopackExports = Symbol("turbopack exports");
334334
const turbopackError = Symbol("turbopack error");
335335

336+
const enum QueueStatus {
337+
Unknown = -1,
338+
Unresolved = 0,
339+
Resolved = 1,
340+
}
341+
336342
type AsyncQueueFn = (() => void) & { queueCount: number };
337-
type AsyncQueue = AsyncQueueFn[] & { resolved: boolean };
343+
type AsyncQueue = AsyncQueueFn[] & {
344+
status: QueueStatus;
345+
};
338346

339347
function resolveQueue(queue?: AsyncQueue) {
340-
if (queue && !queue.resolved) {
341-
queue.resolved = true;
348+
if (queue && queue.status !== QueueStatus.Resolved) {
349+
queue.status = QueueStatus.Resolved;
342350
queue.forEach((fn) => fn.queueCount--);
343351
queue.forEach((fn) => (fn.queueCount-- ? fn.queueCount++ : fn()));
344352
}
@@ -355,11 +363,13 @@ type AsyncModuleExt = {
355363
type AsyncModulePromise<T = Exports> = Promise<T> & AsyncModuleExt;
356364

357365
function wrapDeps(deps: Dep[]): AsyncModuleExt[] {
358-
return deps.map((dep) => {
366+
return deps.map((dep): AsyncModuleExt => {
359367
if (dep !== null && typeof dep === "object") {
360368
if (isAsyncModuleExt(dep)) return dep;
361369
if (isPromise(dep)) {
362-
const queue: AsyncQueue = Object.assign([], { resolved: false });
370+
const queue: AsyncQueue = Object.assign([], {
371+
status: QueueStatus.Unresolved,
372+
});
363373

364374
const obj: AsyncModuleExt = {
365375
[turbopackExports]: {},
@@ -381,12 +391,10 @@ function wrapDeps(deps: Dep[]): AsyncModuleExt[] {
381391
}
382392
}
383393

384-
const ret: AsyncModuleExt = {
394+
return {
385395
[turbopackExports]: dep,
386396
[turbopackQueues]: () => {},
387397
};
388-
389-
return ret;
390398
});
391399
}
392400

@@ -401,7 +409,7 @@ function asyncModule(
401409
hasAwait: boolean
402410
) {
403411
const queue: AsyncQueue | undefined = hasAwait
404-
? Object.assign([], { resolved: true })
412+
? Object.assign([], { status: QueueStatus.Unknown })
405413
: undefined;
406414

407415
const depQueues: Set<AsyncQueue> = new Set();
@@ -450,7 +458,7 @@ function asyncModule(
450458
function fnQueue(q: AsyncQueue) {
451459
if (q !== queue && !depQueues.has(q)) {
452460
depQueues.add(q);
453-
if (q && !q.resolved) {
461+
if (q && q.status === QueueStatus.Unresolved) {
454462
fn.queueCount++;
455463
q.push(fn);
456464
}
@@ -474,8 +482,8 @@ function asyncModule(
474482

475483
body(handleAsyncDependencies, asyncResult);
476484

477-
if (queue) {
478-
queue.resolved = false;
485+
if (queue && queue.status === QueueStatus.Unknown) {
486+
queue.status = QueueStatus.Unresolved;
479487
}
480488
}
481489

crates/turbopack-tests/index.d.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
1-
import * as expectMod from "expect";
2-
import * as jest from "jest-circus";
1+
import * as expectMod from "./tests/execution/node_modules/expect";
2+
import * as jest from "./tests/execution/node_modules/jest-circus";
33

44
export {};
55

Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
it("should not hang", async () => {
2+
debugger;
3+
const { test } = await import("./wrapper");
4+
5+
expect(test()).toBe(5);
6+
}, 1000);
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
export const x = 5;
2+
3+
const a = 10;
4+
if (a !== 10) {
5+
// intentionally nothing, the skipped await point causes the problem
6+
await 0;
7+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
import { x } from "./repro.js";
2+
3+
export function test() {
4+
return x;
5+
}

crates/turbopack-tests/tests/execution/turbopack/wasm/complex/input/index.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
const magicAsyncModule = require("./magic")
1+
const magicAsyncModule = require("./magic");
22

33
describe("complex wasm", () => {
44
it("should be possible to use imported memory", async () => {

crates/turbopack-tests/tests/execution/turbopack/wasm/simple/input/math.js

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -5,11 +5,11 @@ import { fibonacci } from "./fibonacci.wasm";
55
export { add, factorial, fibonacci };
66

77
export function factorialJavascript(i) {
8-
if (i < 1) return 1;
9-
return i * factorialJavascript(i - 1);
8+
if (i < 1) return 1;
9+
return i * factorialJavascript(i - 1);
1010
}
1111

1212
export function fibonacciJavascript(i) {
13-
if (i < 2) return 1;
14-
return fibonacciJavascript(i - 1) + fibonacciJavascript(i - 2);
13+
if (i < 2) return 1;
14+
return fibonacciJavascript(i - 1) + fibonacciJavascript(i - 2);
1515
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
export * from "./unknown.js";
2+
3+
await 1;
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
it("should handle re-export from async modules correctly", async () => {
2+
await import("./test.js");
3+
});
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
export * from "./async-unknown.js";
2+
export { a } from "./async-unknown.js";
3+
export default "default";

0 commit comments

Comments
 (0)