Skip to content

async function called as synchronous #7793

@hvenev

Description

@hvenev

Example:

const assert = @import("std").debug.assert;
var counter: u8 = 0;

var frame: anyframe = undefined;

fn asyncfunc() callconv(.Async) u8 {
    suspend {
        frame = @frame();
    }
    counter += 1;
    return counter;
}

fn wrapasync() u8 {
    return asyncfunc();
}

fn callit(inner: fn() u8) u8 {
    return inner();
}

pub fn main() void {
    assert(counter == 0);
    var coro = async wrapasync();
    assert(counter == 0);
    resume frame;
    assert(counter == 1);
    const val1 = nosuspend await coro;
    assert(counter == 1);
    assert(val1 == 1);

    _ = callit(wrapasync);
}

The first call to wrapasync() creates a coroutine. It suspends, is resumed, and finishes. Everything behaves as expected.

However, in the second call wrapasync is implicitly converted to fn() u8, and callit() calls it using the calling convention for regular function. The result is this:

thread NNNNN: panic: resumed an async function which already returned
..../async.zig:14:1: 0x234562 in wrapasync (async)
fn wrapasync() u8 {
^
...../async.zig:19:17: 0x23466e in callit (async)
    return inner();
                ^
...../async.zig:32:24: 0x22d359 in main (async)
    _ = callit(wrapasync);
                       ^
/usr/lib/zig/std/start.zig:335:22: 0x204d8e in std.start.posixCallMainAndExit (async)
            root.main();
                     ^
/usr/lib/zig/std/start.zig:163:5: 0x204c62 in std.start._start (async)
    @call(.{ .modifier = .never_inline }, posixCallMainAndExit, .{});
    ^
Aborted (core dumped)

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugObserved behavior contradicts documented or intended behavior

    Type

    No type

    Projects

    No projects

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions