Skip to content

Suspension

Understand where Atoll code may yield and how it resumes.

Updated View as Markdown

Suspension is an effect, not a second kind of function. Atoll has no async fn modifier and no prefix await operator.

fn read_chunk(
    socket: TcpConnection,
    mut buffer: []byte
): int ! TcpError {
    return socket.read_into(buffer, 4096)?
}

The call looks ordinary. Its declaration or inferred effect row tells the compiler that it may pause and later continue.

Suspension sources

Current suspension sources include:

  • host calls decorated with @suspend;
  • joining a Task with .await();
  • a blocking select;
  • stream producer backpressure through send_await;
  • database and network operations backed by suspending host calls.

An operation may take a synchronous fast path. Awaiting an already completed task, for example, returns without actually yielding, but its static contract still includes Suspend.

Conversely, ordinary-looking code may suspend because a called function inherits the effect from deeper in the call graph. Read the function contract, not punctuation at the call site.

Resume behavior

At a suspension point the compiler preserves values needed later, records a resume continuation, and returns control to the scheduler. The scheduler re-enters the generated continuation after the completion source becomes ready.

This continuation machinery is compiler-managed. Application code should not construct frames, program counters, or resume functions.

The continuation resumes after the suspending operation and observes its result exactly as an ordinary call result. Local control flow—match, ?, loops, and deferred cleanup—continues through generated blocks rather than a second source-language state-machine syntax.

Local state

Values live across suspension when later expressions need them:

fn total(task: Task[int]): int {
    offset := 2
    value := task.await()
    return value + offset
}

The compiler decides whether those values remain in fixed frame slots, need managed storage, or require another representation. Raw pointers and borrowed references have stricter crossing rules; see References.

Values not needed after the call need not remain live across it. Keeping scopes small can reduce captured continuation state:

request_id := request.id
response := fetch(request_id)
return response.status

Avoid retaining a large aggregate merely because one field is used after a suspension.

Atomic regions

Cooperative scheduling means straight-line code runs until it reaches a safepoint or suspension. It does not mean a large computation can never be preempted—the runtime and generated loop safepoints may still cooperate with cancellation and fairness.

Do not use this property as a replacement for an ownership contract. Pass values into child tasks deliberately, keep shared handles explicit, and use channels for cross-task communication.

Long CPU loops should still reach generated loop safepoints so cancellation and fairness can progress. Unsafe raw-pointer regions must not span those points.

Function coloring

Because call syntax is uncolored, code remains composable:

fn load_page(id: int): Page ! LoadError {
    user := load_user(id)?
    settings := load_settings(user.id)?
    return Page { user, settings }
}

Effect inference, rather than syntax at each call, determines whether load_page needs the continuation ABI.

Cleanup

Managed locals live across suspension according to compiler liveness. Normal resume, error propagation, and cancellation each receive a cleanup path for owned values. Resource types with Drop can therefore release host handles when a parked task is canceled.

Cleanup methods themselves cannot suspend. Acquire a resource through a typed wrapper, keep it in an owned local, and let the wrapper’s synchronous Drop close it if control leaves early.

Boundaries

Host adapters mark waiting operations with @suspend. Pure host calls such as resource close remain synchronous. At a public API, an explicit using row can make this distinction part of the contract; otherwise inference propagates it automatically.

Navigation

Type to search…

↑↓ navigate↵ selectEsc close