Skip to content

for await runs its body as concurrent tasks: iterations interleave and lose writes #498

Description

@ASDAlexander77

The body of a for await loop runs as concurrent tasks, not one iteration at a time. Iterations interleave, and writes from one iteration are lost to another, under gc, rc and none.

let keep: number[] = [];
let log = "";
async function* numbers() { yield 7; yield 8; yield 9; }
async function main() {
    for await (const x of numbers()) {
        log += "<" + x;
        keep.push(x);
        log += ">";
    }
    print(log, keep.length);
}

Expected: <7><8><9> 3, every time.

Six JIT runs per model on main with #440 (PR #497) applied:

Output gc rc none
<7><8><9> 3 1 3 4
<7<8><9> 3 (interleaved) 1
<7><9> 2, <8><9> 2, ><9> 2 (a push lost) 4
<7> 3, <8> 3, <9> 3 (appends lost) 3 1
no output at all, exit 0 1

In TypeScript, the body of a for await runs one iteration at a time, in order: the next value is not requested until the body has finished.

Cause

MLIRGenImpl::mlirGen(ForStatement) (lib/TypeScript/MLIRGenStatements.cpp), when the loop has await:

  • creates an async.create_group before the loop;
  • wraps each iteration's body in its own async.execute, and adds its token to the group with async.add_to_group;
  • waits for the whole group once after the loop, with async.await_all.

So every iteration's body is started as a task without waiting for the previous one. The bodies run concurrently on the async runtime's threads, and race on whatever they share: here log, a string rebuilt by +=, and keep.push, which can reallocate the array.

Fix

Await each iteration's token before going on to the next iteration (async.await on the execute's token in the loop body, as await does since #440), and drop the group and the async.await_all. Then the next value is requested only after the body is done.

Found while working on #440 (listed there under "Also seen").

🤖 Generated with Claude Code

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions