# Chapter 15: Async and Concurrency

**What you'll learn.** `async` functions and `Task[T]`, `await`, structured concurrency with `await group`, async generators and `for await`, `atomic {}` blocks, and `spawnBlocking` — and, just as important, how Loup 1.25 actually runs them: **eagerly and in order**, not interleaved on an event loop.

**Why it matters.** A web page is full of work that finishes later: requests, timers, user input. The Language Reference designs Loup's concurrency for that world (Volume 2, §2-1). The syntax and the compile-time checks are implemented today; the scheduler that would interleave tasks is not. If you know where the two differ, you can write code that is correct now and stays correct when the scheduler arrives.

## `async` functions and `Task[T]`

An `async func` is declared with the type its *call* produces, `Task[T]`. Inside the body you `return` a plain `T`, and a caller gets the `T` out with `await`:

```loup
@mode(web.compiled)

async func fetchUser(id: int): Task[string] {
    println("fetching user " + str(id));
    return "ann";
}

async func fetchCartTotal(user: string): Task[int] {
    println("fetching cart for " + user);
    return 2348;
}

async func main(): Task[void] {
    let user = await fetchUser(7);
    let total = await fetchCartTotal(user);
    println(user + " owes " + str(total) + " cents");
}
```

*Listing 15-1: Two async functions awaited in sequence from an `async main`*

```text
fetching user 7
fetching cart for ann
ann owes 2348 cents
```

The rules (Language Reference §2-1.1):

- `async func f(…): Task[T]` returns `T` values from its body; the call `f(…)` has type `Task[T]`. The short form `async func f(…): T` means the same thing; `Task[T]` is the usual spelling.
- `await e` needs `e` to be a `Task[T]` and evaluates to the `T`.
- `main` may itself be `async`, returning `Task[void]` or `Task[int]`; the runner awaits it, and a `Task[int]` result is the exit status as for an ordinary `int main` (ruling MAIN-1).
- `try`/`catch`/`finally` and local variables work across `await` exactly as in synchronous code. (`defer` is specified to work the same way, but on the 1.25 toolchain a `defer` body does not run; use `try`/`finally` for cleanup.)

The same program compiles to Wasm. `loup-bootstrap build --target browser` writes `build/<name>.wasm`, and `loup-launcher --run-bare` prints the same three lines. (The Wasm lane runs async code the same eager way described below.)

### `await` only works in an async context

`await` is allowed inside an `async func`, at the top level of an entry script (`web.scripted`, which has no `main`), and in the REPL. A synchronous `main` cannot use it:

```loup,does_not_compile
@mode(web.compiled)

async func half(n: int): Task[int] {
    return n // 2;
}

func main(): void {
    let h = await half(9);
    println(h);
}
```

```text
main.lpweb:8:13: error[E_AWAIT_NOT_ASYNC]: E_AWAIT_NOT_ASYNC: `await` is only valid inside an `async func` body, an entry script's top level, or the REPL (§2-1.1)
```

Make the enclosing function `async`. For `main`, that also means giving it a `Task[...]` return type:

```loup
@mode(web.compiled)

async func half(n: int): Task[int] {
    return n // 2;
}

async func main(): Task[void] {
    let h = await half(9);
    println(h);
}
```

```text
4
```

The opposite mistake — awaiting something that is not a task — is caught too. `await 5` gives `error[E0001]: can only await 'Task[T]' values, found 'int'`.

## How async code runs in Loup 1.25

The design (§1-2.1, §2-1.1) is a cooperative, single-threaded runtime: calling an async function starts a task, `await` suspends the current task, and while it is suspended the event loop runs other tasks. On a browser that event loop would be the page's own.

> ⚠ **Planned.** The task scheduler is not wired in. In Loup 1.25 — under `loup run` and in Wasm builds alike — calling an `async func` runs its whole body at once, to completion, before the call returns; `await` on the resulting task just hands back the value. Nothing interleaves. There is also no browser event loop yet, because no browser runtime ships (Chapter 1).

The difference is invisible in Listing 15-1, where every task is awaited as soon as it starts. It shows as soon as you start two tasks and await them later. This `web.scripted` program (it is `web.scripted` because `sleep` lives in `core.time`, and a `web.compiled` file cannot import the standard library — Chapter 13) starts a slow task and a fast one:

```loup,not_desired_behavior
@mode(web.scripted)

from core.time import (sleep, Duration);

async func step(name: string, ms: int): Task[string] {
    println(name + ": start");
    await sleep(Duration.milliseconds(ms));
    println(name + ": done");
    return name;
}

let slow = step("slow", 50);
let fast = step("fast", 1);
let a = await slow;
let b = await fast;
println(a + ", " + b);
```

*Listing 15-2: On the 1.25 runtime the two tasks run one after the other*

```text
slow: start
slow: done
fast: start
fast: done
slow, fast
```

Under the specified scheduler `slow` would suspend at its `sleep`, `fast` would start and finish, and the output would read `slow: start`, `fast: start`, `fast: done`, `slow: done`. On 1.25 the first call runs `slow` to the end — `sleep` blocks for the full 50 ms — before `fast` even starts. Two practical consequences:

- **Starting several tasks does not make them concurrent.** Write the code the way the design intends (start tasks, then await them), but do not expect it to be faster today.
- **Do not depend on ordering in either direction.** Code that only works because tasks run eagerly will break when they start to interleave; code that assumes interleaving does not get it yet.

### Where a failing task throws

The design says a task that fails stores its exception, and `await` rethrows it. Because the 1.25 runtime runs the body during the call, the exception comes out of the *call* instead. This handler never runs:

```loup,throws
@mode(web.compiled)

async func loadProfile(id: int): Task[string] {
    throw ValueError("no profile " + str(id));
}

async func main(): Task[void] {
    let task = loadProfile(42);
    println("task created");
    try {
        let name = await task;
        println(name);
    } catch (ValueError as e) {
        println("caught: " + e.message);
    }
}
```

```text
Uncaught ValueError: no profile 42
  at loadProfile (main.lpweb:4:11)
  at main (main.lpweb:8:16)
```

The trace points at line 8, the call, and `task created` is never printed: the exception left `main` before the `try` was entered. Put the call inside the `try`. That is correct under both the eager runtime and the specified one:

```loup
@mode(web.compiled)

async func loadProfile(id: int): Task[string] {
    throw ValueError("no profile " + str(id));
}

async func main(): Task[void] {
    try {
        let name = await loadProfile(42);
        println(name);
    } catch (ValueError as e) {
        println("caught: " + e.message);
    }
}
```

*Listing 15-3: Call and await inside the `try`, so the handler sees the failure on every runtime*

```text
caught: no profile 42
```

## Structured concurrency: `await group`

`await group { … }` is the structured way to run several child tasks (§2-1.2). Inside the block, `target = spawn call()` starts a child; the block does not finish until every child has, and then each target holds its child's result. If a child fails, the design cancels the others, waits for them, and rethrows the failure from the block.

```loup
@mode(web.compiled)

async func fetchUser(id: int): Task[string] {
    println("fetch user " + str(id));
    return "ann";
}

async func fetchCount(): Task[int] {
    println("fetch count");
    return 3;
}

async func main(): Task[void] {
    var user = "";
    var count = 0;
    await group {
        user = spawn fetchUser(7);
        count = spawn fetchCount();
    }
    println(user + " has " + str(count) + " items");
}
```

*Listing 15-4: Two children in an `await group`, each result assigned to a `var`*

```text
fetch user 7
fetch count
ann has 3 items
```

The targets are assigned when the group finishes, so they must be `var` bindings declared before the block. The specification's own examples declare them as `let user: User;` with no initializer; the 1.25 compiler rejects that:

```loup,does_not_compile
@mode(web.compiled)

async func fetchUser(id: int): Task[string] {
    return "ann";
}

async func main(): Task[void] {
    let user: string;
    await group {
        user = spawn fetchUser(7);
    }
    println(user);
}
```

```text
main.lpweb:10:9: error[E0001]: cannot assign to immutable binding 'user'
```

Declare the target with `var` and an initial value, as in Listing 15-4.

`spawn` itself only exists inside a group. Writing `let x = spawn one();` in an ordinary async body is `error[E0001]: 'spawn' can only appear inside an 'await group' block`; to start a task outside a group, call the async function (`let t = one();`) and `await` it later.

### A failing child

With eager execution, a child that throws stops the group at the `spawn` that started it, so later children are never started. The exception leaves the block as the design specifies:

```loup
@mode(web.compiled)

async func fetchUser(id: int): Task[string] {
    println("fetch user");
    throw ValueError("user " + str(id) + " not found");
}

async func fetchCount(): Task[int] {
    println("fetch count");
    return 3;
}

async func main(): Task[void] {
    var user = "";
    var count = 0;
    try {
        await group {
            user = spawn fetchUser(7);
            count = spawn fetchCount();
        }
        println("loaded");
    } catch (ValueError as e) {
        println("failed: " + e.message);
    }
}
```

*Listing 15-5: The first child's exception propagates out of the group*

```text
fetch user
failed: user 7 not found
```

`fetch count` never prints. Under the specified scheduler both children would start, and the group would cancel `fetchCount` by raising `CancellationError` at its next `await`.

> ⚠ **Planned.** Cancellation is not implemented: `CancellationError` is not defined in 1.25 (`undefined identifier`), and nothing is ever cancelled. `TimeoutError` is defined and can be thrown and caught like any exception.

### `TaskGroup` is not there yet

The specification also has a `TaskGroup` class with `collect`, `race`, `withTimeout`, `withCancellation` and `collectBounded` (§2-1.3). Code that uses it does not compile:

```loup,does_not_compile
@mode(web.compiled)

async func priceOf(sku: string): Task[int] {
    return sku.len() * 100;
}

async func main(): Task[void] {
    let prices = await TaskGroup.collect(["tea", "mug"], priceOf);
    println(prices);
}
```

```text
main.lpweb:8:24: error[E0001]: undefined identifier `TaskGroup`
```

For a list of items, await each one in a loop. The result is the same as `collect` would give, and it is what the 1.25 runtime does anyway:

```loup
@mode(web.compiled)

async func priceOf(sku: string): Task[int] {
    return sku.len() * 100;
}

async func main(): Task[void] {
    var prices: list[int] = [];
    for (sku in ["tea", "mug", "teapot"]) {
        prices.push(await priceOf(sku));
    }
    println(prices);
}
```

*Listing 15-6: Collecting results with a loop instead of the planned `TaskGroup.collect`*

```text
[300, 300, 600]
```

> ⚠ **Planned.** `TaskGroup` and `CancellationToken` (§2-1.3) are not implemented.

## Async generators and `for await`

An `async gen func` produces values over time as an `AsyncIterable[T]`, and `for await` consumes one (§2-1.4). The generator is lazy: each value is produced when the loop asks for it, and `break` stops it.

```loup
@mode(web.compiled)

async gen func pages(last: int): AsyncIterable[int] {
    var page = 1;
    while (page <= last) {
        println("loading page " + str(page));
        yield page;
        page = page + 1;
    }
}

async func main(): Task[void] {
    for await (p in pages(100)) {
        println("showing page " + str(p));
        if (p == 2) {
            break;
        }
    }
    println("stopped");
}
```

*Listing 15-7: An async generator consumed with `for await`; `break` stops it after two pages*

```text
loading page 1
showing page 1
loading page 2
showing page 2
stopped
```

`for await` needs an `AsyncIterable`. Using it on a list is an error:

```loup,does_not_compile
@mode(web.compiled)

async func main(): Task[void] {
    for await (sku in ["tea", "mug"]) {
        println(sku);
    }
}
```

```text
main.lpweb:4:5: error[E_NOT_ASYNC_ITERABLE]: E_NOT_ASYNC_ITERABLE: `for await` needs an `AsyncIterable[T]`, found 'list[string]' (§2-1.4)
```

A list is already all there; loop over it with a plain `for (sku in [...])`.

> ⚠ **Planned.** The `AsyncIterable` combinators `map`, `filter`, `take`, `merge` and `zip` (§2-1.5) are not implemented (`type 'AsyncIterable[int]' has no method 'take'`). In a Wasm build, `for await` over an async generator currently traps (`wasm trap: wasm 'unreachable' instruction executed`); it works under `loup run`.

## Tasks inside templates

A `Task[T]` is never awaited for you. Putting one straight into a template fence (Chapter 14) is a compile error:

````loup,does_not_compile
@mode(web.compiled)

async func pageTitle(): Task[string] {
    return "Your cart";
}

async func main(): Task[void] {
    let page = ```(html):(none)
    <h1>{$ pageTitle() $}</h1>
    ```;
    println(page.render({}));
}
````

```text
main.lpweb:9:9: error[E0001]: cannot insert Task[T] into template: use 'await' first
```

Await first, then interpolate the value:

````loup
@mode(web.compiled)

async func pageTitle(): Task[string] {
    return "Your cart";
}

async func main(): Task[void] {
    let title = await pageTitle();
    let page = ```(html):(none)
    <h1>{$ title $}</h1>
    ```;
    println(page.render({}));
}
````

```text
<h1>Your cart</h1>
```

## `atomic {}`: no suspension in here

Cooperative scheduling has its own kind of race. Between checking a condition and acting on it, an `await` lets other tasks run, and one of them may change what you checked — a time-of-check/time-of-use bug. An `atomic {}` block closes that window by forbidding every suspension point inside it (§2-1.7). It is available in `web.scripted`, `web.compiled`, `host.scripted` and `host.dual`.

```loup
@mode(web.compiled)

class Seats {
    pub var left: int = 1;

    pub modifier func take(): bool {
        var ok = false;
        atomic {
            if (this.left > 0) {
                this.left = this.left - 1;
                ok = true;
            }
        }
        return ok;
    }
}

async func main(): Task[void] {
    let seats = Seats();
    safe {
        println(seats.take());
        println(seats.take());
    }
    println(seats.left);
}
```

*Listing 15-8: Check-and-update of the last seat inside `atomic {}`*

```text
true
false
0
```

(`take` changes a field, so it is a `modifier` method and is called inside `safe { }`; Chapter 9 covers both.) An `await` inside the block is rejected:

```loup,does_not_compile
@mode(web.compiled)

async func audit(): Task[void] {
    println("audit");
}

async func main(): Task[void] {
    var left = 1;
    atomic {
        if (left > 0) {
            await audit();
            left = left - 1;
        }
    }
    println(left);
}
```

```text
main.lpweb:11:13: error[E_ATOMIC_AWAIT]: E_ATOMIC_AWAIT: await expression is not allowed inside atomic {} block
```

Do the check and the update inside the block, and the slow work outside it:

```loup
@mode(web.compiled)

async func audit(): Task[void] {
    println("audit");
}

async func main(): Task[void] {
    var left = 1;
    var took = false;
    atomic {
        if (left > 0) {
            left = left - 1;
            took = true;
        }
    }
    if (took) {
        await audit();
    }
    println(left);
}
```

```text
audit
0
```

`atomic {}` is not a lock: it does nothing about OS threads. It only guarantees that no other *task* runs in the middle. With today's eager runtime nothing could interleave anyway; the check still matters because it keeps the code correct once the scheduler arrives.

## `spawnBlocking`

`spawnBlocking(body)` is a prelude function that runs a synchronous closure off the async executor and gives back a `Task[T]` (§2-1.9). It is meant for CPU-heavy work that would otherwise stall every task:

```loup
@mode(web.compiled)

func sumTo(n: int): int {
    var total = 0;
    for (i in 0..n) {
        total = total + i;
    }
    return total;
}

async func main(): Task[void] {
    let job = spawnBlocking(func(): int => sumTo(1000));
    println(await job);
}
```

```text
499500
```

In 1.25 the closure runs immediately on the same thread, like every other task.

## What is not implemented

The rest of the concurrency material in the Language Reference is design only in Loup 1.25. Each item here is a one-line pointer rather than a listing, because none of it compiles or runs as specified.

> ⚠ **Planned.** **Actors** (`Actor[M]`, `send`, `ask`, supervision; §2-1.6) are not implemented.

> ⚠ **Planned.** **Web workers and `ScriptWorker`** (§2-1.10) — isolated Wasm instances with typed `WorkerChannel[T]` message passing — are not implemented; `spawnWorker` is declared in the prelude but there is no worker runtime. Chapter 18 describes the specified worker design; the `web.workers` module of earlier drafts is not in the Language Reference.

> ⚠ **Planned.** **`for concurrent`** (§2-1.11) does not bind its loop variable on the 1.25 interpreter (`for concurrent (i in 0..3) { println(i); }` gives `undefined identifier 'i'`).

> ⚠ **Planned.** **Shared memory** (§2-2). The `Shareable` rules are checked at compile time, but the runtime is missing: a `shared class` whose fields are all Shareable compiles, and constructing it with `shared Hits()` then fails under `loup run` with `internal interpreter limitation: unsupported instruction: NewSharedObject`. The compile-time check looks like this:

```loup,does_not_compile
@mode(web.compiled)

shared class Hits {
    pub var pages: list[string] = [];
}

func main(): void {
    let h = shared Hits();
    println(h.pages);
}
```

```text
main.lpweb:4:5: error[E_SHARED_FIELD_NOT_SHAREABLE]: E_SHARED_FIELD_NOT_SHAREABLE: field `Hits.pages` is not Shareable (`list[T]` is a growable collection and is never Shareable (§2-2.2) — use `SharedArray[T]` or `SharedQueue[T]`) — every field of a `shared class` must satisfy `core.Shareable` (§2-2.2)
```

A growable `list` cannot be shared between threads; the message names the shared alternatives, `SharedArray[T]` and `SharedQueue[T]`, which are themselves not usable from the 1.25 interpreter.

`Thread`, `ThreadPool`, `Mutex` and `RwLock` are specified for `host.dual` and `host.native` only; the web modes are single-threaded by design (§3-1.2). The 1.25 interpreter does not reject `Thread.spawn` in a `web.compiled` file, but a browser build has no threads to give it, so do not use it in web code.

## Summary

- `async func f(): Task[T]` returns a `T` from its body; calling it gives a `Task[T]`; `await` gets the value and is allowed only in async functions, `web.scripted` top-level code and the REPL (`E_AWAIT_NOT_ASYNC`). `main` can be `async`.
- In Loup 1.25 async code runs **eagerly**: a call runs the task to completion before returning, under `loup run` and in Wasm builds. Tasks do not interleave, and a failing task throws at the call, so keep the call inside the `try`.
- `await group { x = spawn f(); }` runs child tasks with `var` targets; `spawn` is only valid inside a group; a failing child's exception leaves the group. Cancellation, `CancellationError` and `TaskGroup` are planned.
- `async gen func` and `for await` work under `loup run` (`E_NOT_ASYNC_ITERABLE` for non-async sources); combinators are planned and `for await` traps in Wasm builds today.
- Await a task before putting its value in a template.
- `atomic {}` forbids `await` inside it (`E_ATOMIC_AWAIT`) and is the tool for check-then-act code.
- `spawnBlocking` exists and runs inline today. Actors, workers, `for concurrent` and shared-memory runtime support are planned; the web modes have no threads.
