Concurrency
Overview
Elle provides colorless concurrency from a small set of primitives: fibers for cooperative multitasking with signals, coroutines for generators and iterators, and OS threads via spawn/join for true parallelism.
Fibers
A fiber is an independent execution context — its own stack, call frames, signal mask, and heap. Fibers are cooperative and explicitly resumed. When a fiber emits a signal, it suspends and the parent decides what to do next.
# Create a fiber that yields values
(defn produce []
(emit :yield 1)
(emit :yield 2)
(emit :yield 3))
(def f (fiber/new produce |:yield|))
(fiber/resume f) (println (fiber/value f)) # => 1
(fiber/resume f) (println (fiber/value f)) # => 2
(fiber/resume f) (println (fiber/value f)) # => 3When a fiber finishes, its entire heap is freed in O(1) — no GC pause, no reference counting.
Signals
Signals are typed, cooperative flow-control interrupts. A signal is a keyword — :error, :log, :abort, or any user-defined name — that a fiber emits to its parent. The parent's signal mask determines which signals surface.
# Error handling via signals
(defn risky [x]
(if (< x 0)
(error {:error :bad-input :message "negative input"})
(* x x)))
(def f (fiber/new (fn [] (risky -1)) |:error|))
(fiber/resume f)
(if (= (fiber/status f) :paused)
(println "caught:" (fiber/value f))
(println "result:" (fiber/value f)))The compiler infers which functions can emit signals and enforces that silent contexts don't call yielding ones.
Coroutines
Coroutines are lightweight cooperative sequences. coro/new wraps a function, yield suspends with a value, coro/resume steps forward.
# Fibonacci generator
(def fib-gen (coro/new (fn []
(var a 0)
(var b 1)
(forever
(yield a)
(def tmp b)
(assign b (+ a b))
(assign a tmp)))))
# Pull first 8 Fibonacci numbers
(each _ in (range 8)
(print (coro/resume fib-gen) " "))
(println)
# => 0 1 1 2 3 5 8 13Threads
Use spawn to execute a closure in a new OS thread and join to wait for completion. Closures can capture immutable values; mutable values cannot cross thread boundaries.
# Parallel computation
(def h1 (spawn (fn [] (* 2 3))))
(def h2 (spawn (fn [] (* 4 5))))
(def h3 (spawn (fn [] (* 6 7))))
(println (+ (join h1) (join h2) (join h3))) # => 68Processes
Elle supports Erlang-style actors built on fibers. Processes communicate via send/recv, can be linked for crash propagation, and use trap-exit for supervision.
See examples/processes.lisp for a complete fiber-based process scheduler with message passing, links, and crash propagation.
Concurrency Primitives
| Function | Description |
|---|---|
| (fiber/new fn mask) | Create a fiber with a signal mask |
| (fiber/resume f) | Resume a suspended fiber |
| (fiber/status f) | Get fiber status (:new, :paused, :dead) |
| (fiber/value f) | Get the last yielded/returned value |
| (emit signal value) | Emit a signal to the parent fiber |
| (coro/new fn) | Create a coroutine from a zero-arg function |
| (coro/resume co) | Step a coroutine forward |
| (yield value) | Suspend coroutine and yield a value |
| (spawn fn) | Execute closure in a new OS thread |
| (join handle) | Wait for thread to complete, return result |
| (signal name) | Declare a user-defined signal keyword |
| (silence fn signals) | Restrict which signals a function may emit |