Skip to content

await of a void async function never runs it: the async.execute token is not awaited #440

Closed

Description

@ASDAlexander77

await f() on an async function that returns nothing never runs f, under every memory model, AOT and JIT. The awaiting function continues straight past the await, and if the program ends first, f never runs at all.

let ran = 0;
async function f() { ran = 1; }
async function main() {
    await f();
    assert(ran == 1, "f did not run");   // fails under gc, rc, none, own
    print("done.");
}

With a value it works: async function f() { return 1; } and const v = await f(); run f and give 1.

Cause

MLIRGenImpl::mlirGen(AwaitExpression) (lib/TypeScript/MLIRGenExpressions.cpp) wraps the awaited expression in an async.execute, then checks the result with

EXIT_IF_FAILED_OR_NO_VALUE(result)

A call to a void function succeeds with no value, so the function returns there, before

builder.create<mlir::async::AwaitOp>(location, asyncExecOp.getToken());

The token is dropped, and nothing waits for the task. The MLIR for the example has the async.execute with the call, and no async.await after it.

Fix

Exit on failure only, EXIT_IF_FAILED(result), so a void await still awaits its token. The function already returns mlir::success() with no value when there is no result slot. Any await of a void call is affected, including await sleep(100).

Also seen

  • A failing assert hangs under the JIT after such a dropped task (gc and none): the run never exits. Two of these runs were left as tslang.exe processes that taskkill /F reports as "no running instance".

  • for await bodies run as concurrent tasks. Each body is its own async.execute, added to a group and awaited with async.await_all after the loop, so iterations are not sequential. In one run of

    let keep: number[] = [];
    async function* numbers() { yield 7; yield 8; }
    async function main() {
        for await (const x of numbers()) { print("body", x); keep.push(x); }
        print("after loop", keep.length);
    }

    the output was body 7, body 8, after loop 1: one push was lost. A version with three yields and an assert passed in all six model/mode runs, so it is intermittent. In TypeScript, the body of a for await runs one iteration at a time, in order.

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