Async Runtime
Nostos is built on Tokio, the leading async runtime for Rust. This means everything is non-blocking - I/O, network calls, database queries, even mvar locks. Understanding this architecture helps you write efficient, scalable programs.
The Big Picture
Traditional languages block threads when waiting for I/O. If you have 100 database queries in flight, you need 100 OS threads - expensive!
Nostos uses async/await under the hood. When a process waits for I/O, it yields to the runtime, allowing other processes to run. A handful of OS threads can serve millions of concurrent operations.
Everything is Non-Blocking
These operations look synchronous but are actually async under the hood:
# All these operations are non-blocking!
main() = {
# File I/O - yields while waiting for disk
content = File.read("data.txt")
# HTTP request - yields while waiting for response
response = Http.get("https://api.example.com")
# Database query - yields while PostgreSQL processes
conn = Pg.connect("host=localhost user=postgres")
rows = Pg.query(conn, "SELECT * FROM users", [])
# Sleep - yields, doesn't block the thread
sleep(1000)
# Message receive - yields until message arrives
receive { msg -> println(msg) }
}
You don't write await keywords - the runtime handles it automatically. Your code reads like synchronous code but executes asynchronously.
MVars: Async Locks
Even mvar locks are non-blocking. When a process tries to access an mvar that's locked by another process, it yields instead of blocking the thread.
mvar counter: Int = 0
increment() = {
# This lock is async!
# If another process holds the lock, we yield - not block
counter = counter + 1
counter
}
main() = {
me = self()
# Spawn 1000 workers all trying to increment
for i = 0 to 1000 {
spawn {
increment()
me <- "done"
}
}
# Wait for all - workers yield while waiting for lock
for i = 0 to 1000 {
receive { "done" -> () }
}
println("Final: " ++ show(counter)) # Always 1000
}
Key insight: Because locks are async, you can have thousands of processes contending for the same mvar without blocking OS threads. The runtime schedules them efficiently.
Implications for Your Code
1. Spawn Freely
Since processes are cheap and I/O is non-blocking, don't hesitate to spawn:
# Fetch 100 URLs in parallel - only uses a few OS threads!
fetch_all(urls) = {
me = self()
urls.each(url => spawn {
result = Http.get(url)
me <- (url, result)
})
# Collect results
urls.map(_ => receive { (url, result) -> (url, result) })
}
2. Don't Fear Blocking Operations
In Nostos, "blocking" operations don't actually block. The runtime handles the async plumbing:
# This looks like it blocks, but doesn't!
process_order(order_id) = {
# Each step yields while waiting
order = Pg.query(conn, "SELECT * FROM orders WHERE id = $1", [order_id])
customer = Pg.query(conn, "SELECT * FROM customers WHERE id = $1", [order.customer_id])
inventory = Http.post("https://inventory.api/check", order.items)
# Other processes run while we wait for each step
finalize(order, customer, inventory)
}
3. Long-Running Computation
CPU-intensive work does block a thread. For heavy computation, consider breaking it up or running in a separate process:
# Long computation - runs on its own thread
heavy_work() = {
# This blocks a thread but doesn't affect other processes
result = expensive_calculation(1000000)
result
}
main() = {
me = self()
# Spawn heavy work so it doesn't block main
spawn { me <- heavy_work() }
# Main can continue doing other things
println("Computing in background...")
receive { result -> println("Got: " ++ show(result)) }
}
The Runtime Architecture
Here's what happens under the hood:
Tokio Multi-Threaded Runtime
A pool of OS threads (usually = CPU cores) handles all work
Green Thread Scheduler
Nostos processes (green threads) are multiplexed onto these threads
Work Stealing
Idle threads steal work from busy ones, keeping all cores utilized
Async I/O
All I/O uses epoll/kqueue/IOCP - never blocks threads
Performance Benefits
Memory Efficiency
100,000 processes use a fraction of the memory that 100,000 OS threads would require.
High Concurrency
Handle millions of connections with a small thread pool.
Low Latency
No thread context switching overhead - just lightweight coroutine switches.
CPU Utilization
Work stealing keeps all cores busy without manual load balancing.
Summary
Nostos gives you the simplicity of synchronous code with the performance of async. You don't need to think about async/await, futures, or callback hell. Just write straightforward code and let the runtime handle the complexity.