Skip to content

The book · 06

Processes and channels

book.md · 83 lines · 2 min read

Goal: create processes with spawn, make them talk to each other over typed channels, and wait on several at once with the subjectless match (the “select”).

The unit of concurrency is the process: isolated (its own heap), cheap, managed by the runtime. You create one with spawn (like Go’s go):

spawn log_metric(x) // fire-and-forget: no cost, no reference
spawn worker(data) catch |e| { ... } // with a failure handler (death becomes |e|; see ch. 07)

Processes do not share memory (there is no @shared). They talk over a channel.

The channel is typed from the perspective of whoever holds the handle: <-T = “I send T”, ->T = “I receive T”. This eliminates the type inversion in the other side’s signature:

c: Channel[<-int, ->string] // I send int, receive string
spawn worker(c) // the compiler checks that the two sides are complementary
fn worker(c: Channel[<-string, ->int]) { // the worker: sends string, receives int
val := -> c // receive (blocks until something arrives)
c <- val.to_string() // send (async, fire-and-forget)
}

Operators: chan <- data (send), data := -> chan (receive), and the sync request-reply with a mandatory timeout:

reply := server <-> req timeout(5s) catch |e| { return e } // Timeout | ProcessDown

The timeout is mandatory on <-> because two processes doing <-> on each other would deadlock for sure: the deadline is the structural way out. And note there is no coloring: the <-> only suspends the current process; the scheduler takes care of the rest.

Waiting on several: subjectless match (select)

Section titled “Waiting on several: subjectless match (select)”

The same match, with no value between match and {, waits for the first ready channel:

match {
msg := -> chan_a => handle_a(msg)
msg := -> chan_b => handle_b(msg)
} timeout(100ms) {
deu_timeout() // 'timeout' is a trailer of the select; timeout(0) = poll
}

No Future/await: for N operations in parallel, you spawn N processes and receive the results:

spawn fetch("https://a", chan_a)
spawn fetch("https://b", chan_b)
ra := -> chan_a // blocks only when necessary
rb := -> chan_b

spawn is fire-and-forget and returns nothing. To inspect a process later, register it by name (stable across restart): there is no raw pid.

@register("db_writer")
spawn db_writer(conn)
p := runtime.process("db_writer") // Optional[Process], by name
me := runtime.self() // the own reference, from inside any process

Next: 07 · Resilience