What exactly is a Suspension point?

It is important that suspension points are only associated with explicit operations. In fact, it’s so important that this proposal requires that calls that might suspend be enclosed in an await expression. These calls are referred to as potential suspension points.

Any asynchronous function may or may not contain a suspension point. Does this mean that calling an asynchronous function itself does not guarantee that the current function will be removed from the thread stack(give up the thread)? As far as I understand, a suspension point means give up the thread?

So I still can't understand, if the very fact of calling an asynchronous function does not lead to give up the thread, then what does?

And I also don't quite understand the following paragraphs:

A suspension point can occur directly within a function, or it can occur within another asynchronous function that the function calls, but in either case the function and all of its asynchronous callers simultaneously abandon the thread. (In practice, asynchronous functions are compiled to not depend on the thread during an asynchronous call, so that only the innermost function needs to do any extra work.)

These calls are referred to as potential suspension points , because it is not known statically whether they will actually suspend: that depends both on code not visible at the call site (e.g., the callee might depend on asynchronous I/O) as well as dynamic conditions (e.g., whether that asynchronous I/O will have to wait to complete).

4 Likes

Suspension points are a dynamic, whole-program concept: they're the places which cause the task to actually suspend and abandon the thread.

Potential suspension points are the static, conservative, function-local view of suspension points: they're the points in the function where a suspension could potentially occur and therefore the function ought to be prepared to abandon the thread. Any dynamic suspension point is always "inside" a potential suspension point from the perspective of every async function on that task: all of the outer functions must be awaiting an asynchronous call, and the innermost function must be awaiting at the actual dynamic suspension point. From all of those functions' local perspectives, the exact reason for the suspension (which may be N levels of call deep) is much less important than the fact that a suspension is possible at that point in their execution.

16 Likes

A very simple example:

func foo() async {
    if something {
        await bar()
    } else {
        baz()
    }
}

— if the else path is taken, the function will (at least, per Swift) not suspend, as baz() is synchronous and doesn't cause to give up the thread. So, at the call site, even while you spell await foo(), but the else path is taken — it won't suspend just for the sake of it.

Some internal call on the OS/executor layer (like Task.yield()) that actually signals the runtime that the function is waiting for something. Anyway, it will be something deliberate.


While not germane to Swift, I can recommend watching Crust of Rust: async/await - YouTube, which explains the (very similar) Rust model in a great detail.

1 Like

Rust’s async/await implementation model is really not similar to Swift’s, despite syntactic similarity. It might still be a good introduction for how to think about async programming if you’re not used to doing so, but I don’t think the intuitions about guaranteed suspensions and non-suspensions transfers in either direction.

3 Likes