ilusm.dev
Guide

Concurrency

ilusm concurrency is message-passing. Spawn tasks with syn.run, communicate through channels with syn.chan. No shared mutable state across tasks.

Spawning a task

syn.run(f) runs a zero-argument function concurrently and returns a task handle. Call t.wai() to block until it finishes and get the result.

use syn

work() =
    # do something
    42

t = syn.run(work)
r = t.wai()        # {val, err}
prn r.val          # 42
prn r.err          # nil (no error)

Use a lambda for inline tasks:

use syn

t = syn.run(\() 1 + 1)
r = t.wai()
prn r.val   # 2

Handling task errors

If the task raises an error, wai() returns it in r.err instead of crashing:

use syn

bad() = err("something went wrong")

t = syn.run(bad)
r = t.wai()
if r.err:
    prn $"task failed: {r.err}"

Channels

Channels let tasks communicate safely. Create with syn.chan() (unbuffered) or syn.chan(n) (buffered).

ch = syn.chan()     # unbuffered - snd blocks until someone rcv's
ch = syn.chan(10)   # buffered - snd is non-blocking up to 10 items

Send and receive

use syn

ch = syn.chan()

producer = syn.run(\()
    i <- 0..4:
        ch.snd(i)
    ch.cls()   # close when done
)

# receive until channel closes
whl tru:
    val = ch.rcv()
    if val == nil: brk   # nil means channel closed
    prn val

Receive with timeout

val = ch.rcv(5000)   # wait up to 5000ms, nil on timeout
if val == nil: prn "timed out"

Fan-out: parallel tasks with a results channel

use syn

urls = [
    "https://api.example.com/a",
    "https://api.example.com/b",
    "https://api.example.com/c"
]

ch = syn.chan(len(urls))

url <- urls:
    u = url   # capture for lambda
    syn.run(\()
        use net
        res = net.get(u)
        ch.snd({url: u, cod: res.cod})
    )

i <- 0..len(urls)-1:
    r = ch.rcv()
    prn $"{r.url} → {r.cod}"

Pipeline with tasks

use syn

# Stage 1: produce numbers
produce(ch) =
    i <- 0..9:
        ch.snd(i)
    ch.cls()

# Stage 2: double each
process(in_ch, out_ch) =
    whl tru:
        v = in_ch.rcv()
        if v == nil: brk
        out_ch.snd(v * 2)
    out_ch.cls()

ch1 = syn.chan(5)
ch2 = syn.chan(5)

syn.run(\() produce(ch1))
syn.run(\() process(ch1, ch2))

whl tru:
    v = ch2.rcv()
    if v == nil: brk
    prn v   # 0, 2, 4, 6, ..., 18

The rule: no shared mutable state

Tasks in ilusm share the interpreter environment. Writing to the same variable from two tasks simultaneously is a data race. Always use channels to pass data between tasks - never mutate shared variables from concurrent tasks.

# BAD - data race
total = 0
t1 = syn.run(\() total = total + 1)
t2 = syn.run(\() total = total + 1)

# GOOD - use a channel
use syn
ch = syn.chan()
syn.run(\() ch.snd(1))
syn.run(\() ch.snd(1))
a = ch.rcv()
b = ch.rcv()
total = a + b