Skip to content

Nested async-await hangs: await asyncFn() where asyncFn itself awaits never resolves #447

@proggeramlug

Description

@proggeramlug

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

  1. 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.
  2. 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.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions