Skip to content

document async frame lifetime #8672

@marler8997

Description

@marler8997

Today I was made aware by @fengb that the storage for an async frame is on the stack just like a normal function. Once I understood this I was able to understand async alot better. However, neither me nor @fengb were able to see where in the documentation Zig says this is how async works. It was particularly confusing because Zig says that when a normal function is called "a frame is pushed to the stack" and in contrast, "An async function is a function whose callsite is split into an async initiation, followed by an await completion. Its frame is provided explicitly by the caller, and it can be suspended and resumed any number of times."

The 2 confusing parts here is that it says normal function frames are pushed to the stack, but doesn't mention this for async functions making it sound like they behave differently. The other part is that it says the frame is provided explicitly by the caller, but "explicitly" to me means it's specified in code, but the caller does not provide a memory pointer to the async call so this doesn't sound correct to me.

I think including something like this would have helped me alot:

When an async function is called, a frame is also pushed to the stack like a normal function. However, this frame differs from a normal function frame in that it must be large enough to accommodate the longest possible call chain. Also note that since the async frame is stored on the stack when it is called, the async frame is no longer valid once the calling function returns. Calling resume on an async frame where the "async" caller as returned is a "use after free" bug, i.e.

var the_frame: anyframe = undefined;
fn foo() void {
    suspend { the_frame = @frame(); }
}
fn callFooAsync() void {
    _ = async foo();
}
pub fn main() void {
    callFooAsync();
    resume the_frame; // use after free, the function that called "_ = async foo();" has returned
}

The corrected version would be:

var the_frame: anyframe = undefined;
fn foo() void {
    suspend { the_frame = @frame(); }
}
pub fn main() void {
    _ = async foo();
    resume the_frame;
}

Metadata

Metadata

Assignees

No one assigned

    Labels

    Type

    No type

    Projects

    No projects

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions