# 15. Async code and actors

> Chapter 15: waiting without blocking with async and await, passing work through channels, and isolating state in actors with links and supervisors.

Canonical: https://alice-lang.org/documentation/learn/async/

[Documentation index](https://alice-lang.org/documentation/llms.txt) · [Complete book](https://alice-lang.org/llms-full.txt)

Chapter 11 was about doing a lot of computation at once. This chapter is about a different problem: **waiting** for many things at once. A web server waits for thousands of clients. A shop waits for orders, a kitchen and a payment service. Waiting threads are expensive; a program that blocks one thread per client runs out of them quickly.

Alice has three tools for this, each suited to a shape of problem:

| Tool              | Use it for                                                         |
| ----------------- | ------------------------------------------------------------------ |
| `async` / `await` | one task that waits for I/O, timers or other tasks                 |
| `Channel(T)`      | a stream of values from producers to consumers                     |
| `Actor(T)`        | a component with private state that handles messages one at a time |

## 15.1 `async` and `await`

An `async def` can pause at an `await` without blocking its thread. While it waits, the thread does other work. Here two teas brew at the same time; the short one finishes first:

```alice
async def brew(tea : String, ms : Int32) : String
  await Task.Delay(ms)
  "#{tea} is ready"
end

async def serve
  assam = brew("Assam", 30)
  sencha = brew("Sencha", 10)
  first = await sencha
  puts first
  second = await assam
  puts second
end

serve.wait
```

```text title="Output"
Sencha is ready
Assam is ready
```

- Calling `brew(...)` starts it and returns a `Task(String)` right away: a value that will hold the result later.
- `await task` pauses `serve` until the task is done, and gives its result.
- Both teas brew during the same 30 milliseconds; the program does not wait 40.
- At the top level, `.wait` blocks until the task finishes. Use it only there; inside async code, `await`.

The return type of an `async def` is inferred as a `Task`. To wait for many tasks at once, `await Task.WhenAll(...)` returns all their results.

### Async sequences

An async method can produce values over time. Declare its return type `IAsyncEnumerable(T)`, `yield` each value, and consume it with `for await`:

```alice
async def ticks : IAsyncEnumerable(Int32)
  yield 1
  await Task.Yield
  yield 2
end

async def watch
  for await t in ticks
    puts "tick #{t}"
  end
end

watch.wait
```

```text title="Output"
tick 1
tick 2
```

## 15.2 Channels

A **channel** is a queue between threads. Producers `send` values; consumers `receive` them, or loop over them with `each`. When the producer is done, it `close`s the channel, and the consumer's loop ends after the last value.

```alice
orders = Channel(String).new(4)

kitchen = spawn do
  orders.each { |tea| puts "brewing #{tea}" }
end

["Assam", "Sencha", "Oolong"].each { |tea| orders.send(tea) }
orders.close
kitchen.wait
puts "kitchen closed"
```

```text title="Output"
brewing Assam
brewing Sencha
brewing Oolong
kitchen closed
```

`spawn do ... end` starts a block on another thread and returns a handle; `wait` waits for it to finish. `Channel(String).new(4)` can hold up to 4 values; when it is full, `send` waits for the consumer. That **backpressure** keeps a fast producer from flooding memory. `Channel(T).new` with no size is unbuffered: each `send` waits until a receiver takes the value.

Channels move values between threads, so the race checker of chapter 10 applies: an array you sent belongs to the receiver (`AL0219`).

## 15.3 Actors

An **actor** owns some state and changes it only in response to messages, one message at a time. Nobody else can touch the state, so there is nothing to lock. This model comes from Erlang, where it runs telephone switches for years without stopping.

`Actor(S, T).new(init, handler)` makes an actor with state of type `S` that receives messages of type `T`. `init` builds the starting state; `handler` takes the state and a message and returns the next state:

```alice
till = Actor(Int32, Int32).new(-> { 0 }, ->(total : Int32, cents : Int32) { total + cents })
till.send(250)
till.send(350)
puts till.finish
```

```text title="Output"
600
```

`send` puts a message in the actor's **mailbox** and returns immediately. `finish` waits until the mailbox is empty, stops the actor and returns its final state. An idle actor holds no thread, so a program can have thousands.

### Isolation is checked

An actor is only safe if its state is really private. The compiler enforces that. An actor may not capture mutable data from outside:

```alice fail="AL0231"
log = List(String).new
printer = Actor(String).new(->(line : String) { log.Add(line) })
```

```text title="Error"
error[AL0231]: actor is not isolated: it captures `log`
```

And messages must be **sendable**: values that cannot be changed behind the receiver's back, such as numbers, strings, enums, records of sendable fields, tuples and immutable collections:

```alice fail="AL0230"
batcher = Actor(List(Int32)).new(->(batch : List(Int32)) { puts batch.Count })
```

```text title="Error"
error[AL0230]: message type
is not sendable between actors
```

### Let it crash: supervisors

What should an actor do when a message makes it fail? Erlang's answer is surprising: let it crash, and have a **supervisor** restart it with a fresh state. Error handling moves out of the business logic into one place.

```alice
sup = Supervisor.one_for_one(3, 5000)
till = sup.supervise(Actor(Int32, Int32).new(-> { 0 }, ->(total : Int32, cents : Int32) {
  raise "negative amount" if cents < 0
  total + cents
}))

till.send(250)
till.send(-5)
till.join
puts "restarts: #{till.restarts}"
till.send(350)
puts till.finish
```

```text title="Output"
restarts: 1
350
```

The message `-5` crashed the actor. The supervisor restarted it: same handle, fresh state from `init`, empty mailbox. The 250 from before the crash is gone, which is the point: a crash means the state can no longer be trusted. `one_for_one(3, 5000)` allows at most 3 restarts in 5 seconds; beyond that, the supervisor itself gives up and exits, and its own supervisor decides.

Actors can also be **linked**: when one crashes, its linked partners exit too, and `on_exit` lets you watch any actor:

```alice
worker = Actor(Int32).new(->(m : Int32) { raise "boom" })
helper = Actor(Int32).new(->(m : Int32) { })
worker.link(helper)
worker.on_exit { |r| puts "worker exited: #{r.kind}" }
worker.send(1)
puts "helper exited: #{helper.join.kind}"
```

```text title="Output"
worker exited: crash
helper exited: linked
```

## 15.4 Choosing a tool

- Waiting for I/O inside one flow of logic: `async`/`await`.
- A pipeline of stages, each on its own thread: channels.
- A long-lived component with state that many parts of the program talk to: an actor, under a supervisor.
- Pure computation on many cores: not this chapter. Use chapter 11's forks and parallel loops.

## Project: the order counter

The shop's till becomes an actor. Several counters send it sales at the same time; the actor adds them up without locks; a bad amount crashes only the till, which its supervisor restarts.

```alice
record Sale(tea : String, cents : Int32)

sup = Supervisor.one_for_one(5, 10_000)
till = sup.supervise(Actor(Int32, Sale).new(-> { 0 }, ->(total : Int32, sale : Sale) {
  raise ArgumentException.new("bad sale") if sale.cents <= 0
  total + sale.cents
}))

counters = (1..4).map do |n|
  spawn do
    3.times { till.send(Sale.new("Assam", 250)) }
  end
end
counters.each { |c| c.wait }
puts "takings: #{till.finish}"
```

```text title="Output"
takings: 3000
```

Four threads sent twelve sales. The actor handled them one at a time, so no sale was lost and no lock was written. `Sale` is a record of sendable fields, so it may cross between threads.

:::caution\[Common mistakes]

- **Forgetting `await`.** `brew("Assam", 30)` alone starts the task but does not wait for it or give you the string.
- **Calling `.wait` inside async code.** It blocks a thread. Use `await` inside `async def`; use `.wait` only at the top level.
- **Not closing a channel.** A consumer looping with `each` waits forever for more values.
- **Capturing mutable state in an actor.** `error[AL0231]`. Build the state in `init`.
- **Sending a mutable collection.** `error[AL0230]`. Send a record, a tuple, or an immutable collection.
- **`puts await task` on one line.** Write `x = await task`, then `puts x`; the one-line form does not parse yet.
  :::

## Exercises

1. Write an `async def double_later(x : Int32) : Int32` that waits 5 ms and returns `x * 2`. Start three of them, then await and print their results in order.
2. Use a channel to send the numbers 1 to 5 from a spawned producer to the main program, which prints their sum.
3. Write a stateful actor that counts how many messages it received, whatever they contain. Send it four strings and print the count.

<details>
<summary>Answers</summary>

1. Answer:

   ```alice
   async def double_later(x : Int32) : Int32
     await Task.Delay(5)
     x * 2
   end

   async def run
     tasks = [double_later(1), double_later(2), double_later(3)]
     tasks.each do |t|
       v = await t
       puts v
     end
   end

   run.wait
   ```

   ```text title="Output"
   2
   4
   6
   ```

2. Answer:

   ```alice
   numbers = Channel(Int32).new(2)
   producer = spawn do
     (1..5).each { |i| numbers.send(i) }
     numbers.close
   end
   sum = 0
   numbers.each { |n| sum += n }
   producer.wait
   puts sum
   ```

   ```text title="Output"
   15
   ```

3. Answer:

   ```alice
   counter = Actor(Int32, String).new(-> { 0 }, ->(count : Int32, msg : String) { count + 1 })
   ["a", "b", "c", "d"].each { |s| counter.send(s) }
   puts counter.finish
   ```

   ```text title="Output"
   4
   ```

</details>

## Summary

- `async def` methods return tasks; `await` pauses without blocking a thread. `.wait` is for the top level.
- `IAsyncEnumerable(T)` with `yield` and `for await` streams values over time.
- `Channel(T)` connects producers and consumers; a bounded channel applies backpressure; `close` ends the stream.
- `Actor(S, T)` owns state and handles one message at a time. Isolation (`AL0231`) and sendable messages (`AL0230`) are checked.
- Supervisors restart crashed actors with fresh state; links and `on_exit` propagate and observe exits.

[Chapter 16](https://alice-lang.org/documentation/learn/gpu.md) sends loops to an even more parallel machine: the graphics card.

## Continue reading

- Previous: [14. Using .NET](https://alice-lang.org/documentation/learn/dotnet.md)
- Next: [16. The GPU](https://alice-lang.org/documentation/learn/gpu.md)
