Summary
A two-level deep await chain — i.e. an async function whose body contains an await, called via await/.then/bare from another async context — hangs forever and produces zero output. This is a pre-existing bug (reproduces on v0.5.498 too, not v0.5.503-specific) but is currently masked from the parity sweep because test_async.ts is in run_parity_tests.sh::SKIP_TESTS. The same shape causes test-files/test_gap_async_advanced.ts to hang silently.
Minimal repro
async function getValue(): Promise<number> {
return 42;
}
async function run(): Promise<void> {
const x = await getValue(); // <- await inside the body
console.log(x);
}
await run();
console.log(100);
Expected (matches node --experimental-strip-types): prints 42 then 100.
Observed: zero output, infinite spin (still alive after 60+ seconds at 100% CPU on kill -9 only).
Pinning
| Variant |
Result |
Top-level await Promise.resolve(42) |
works |
await getValue() at top-level (one-level) |
works |
await new Promise(r => r(42)) inside async main() |
works |
await Promise.resolve(42) inside async main(), main() called bare |
hangs |
main().then(() => …) instead of bare call |
hangs |
await run() where run itself awaits another async fn |
hangs |
The trigger is an async function whose body contains an await, invoked from a top-level execution path that schedules the resulting promise. One-deep await works; the second await never resumes the outer.
Repro file
test-files/test_async.ts (already in the repo, currently skip-listed in run_parity_tests.sh):
async function getValue(): Promise<number> { return 42; }
async function run(): Promise<void> { let x = await getValue(); console.log(x); }
await run();
console.log(100);
Why this matters
- Almost all real-world TypeScript code follows the
async function main() { … } main().catch(…) pattern.
- Today the hang is masked because:
test_async.ts is in the parity SKIP_TESTS array (commented "Async tests need event loop"), so the 167/167 sweep doesn't catch it.
test_gap_async_advanced.ts is in the gap suite but produces zero output before the runner times out, getting recorded as a generic FAIL without surfacing the hang nature.
- Anything more ambitious than a single top-level
await will silently brick.
Suggested fixes
- Investigate why a nested
await doesn't drive its outer promise to completion — likely the microtask queue isn't being pumped after the inner promise settles, or the outer continuation isn't being enqueued correctly.
- Once fixed, un-skip
test_async/test_async2…5/test_async_chain in run_parity_tests.sh::SKIP_TESTS so the parity sweep covers this surface.
Verified pre-existing
git checkout HEAD~5 -- crates/ && cargo build --release (v0.5.498) — repro hangs identically. So this is not a v0.5.503 / v0.5.502 / v0.5.501 / v0.5.500 / v0.5.499 / v0.5.498 regression; it predates that window.
Summary
A two-level deep
awaitchain — i.e. an async function whose body contains anawait, called viaawait/.then/bare from another async context — hangs forever and produces zero output. This is a pre-existing bug (reproduces on v0.5.498 too, not v0.5.503-specific) but is currently masked from the parity sweep becausetest_async.tsis inrun_parity_tests.sh::SKIP_TESTS. The same shape causestest-files/test_gap_async_advanced.tsto hang silently.Minimal repro
Expected (matches
node --experimental-strip-types): prints42then100.Observed: zero output, infinite spin (still alive after 60+ seconds at 100% CPU on
kill -9only).Pinning
await Promise.resolve(42)await getValue()at top-level (one-level)await new Promise(r => r(42))insideasync main()await Promise.resolve(42)insideasync main(),main()called baremain().then(() => …)instead of bare callawait run()whererunitself awaits another async fnThe trigger is an
asyncfunction whose body contains anawait, invoked from a top-level execution path that schedules the resulting promise. One-deepawaitworks; the secondawaitnever resumes the outer.Repro file
test-files/test_async.ts(already in the repo, currently skip-listed inrun_parity_tests.sh):Why this matters
async function main() { … } main().catch(…)pattern.test_async.tsis in the parity SKIP_TESTS array (commented "Async tests need event loop"), so the 167/167 sweep doesn't catch it.test_gap_async_advanced.tsis in the gap suite but produces zero output before the runner times out, getting recorded as a generic FAIL without surfacing the hang nature.awaitwill silently brick.Suggested fixes
awaitdoesn't drive its outer promise to completion — likely the microtask queue isn't being pumped after the inner promise settles, or the outer continuation isn't being enqueued correctly.test_async/test_async2…5/test_async_chaininrun_parity_tests.sh::SKIP_TESTSso the parity sweep covers this surface.Verified pre-existing
git checkout HEAD~5 -- crates/ && cargo build --release(v0.5.498) — repro hangs identically. So this is not a v0.5.503 / v0.5.502 / v0.5.501 / v0.5.500 / v0.5.499 / v0.5.498 regression; it predates that window.