Skip to main content

Patterns

Common shapes for concurrent Scrii code — fan-out, fan-in, and keeping servers alive.

Fan-out — spawn many, await all

The canonical pattern: build an array of futures, then await them.

var jobs = [
fn() { std.async.sleep(20); return "alpha" },
fn() { std.async.sleep(15); return "beta" },
fn() { std.async.sleep(10); return "gamma" },
]

var futures = []
for (job : jobs) { futures:insert(std.async.spawn(job)) }

var results = []
for (f : futures) { results:insert(std.async.await(f)) }
print(results) // ["alpha", "beta", "gamma"] — order preserved by await order

Preserve input order by awaiting in the same order you spawned. For the fastest-first order, await as they complete or use Race.

Parallel HTTP fan-out

The same shape works for I/O — all fetches overlap:

var urls = ["http://127.0.0.1:8200/a", "http://127.0.0.1:8200/b", "http://127.0.0.1:8200/c"]
var futures = []
for (u : urls) {
futures:insert(std.async.spawn(fn(u) {
return std.net.http_client().request({url: u}).body
}, u))
}
var bodies = []
for (f : futures) { bodies:insert(std.async.await(f)) } // ~1× slowest, not 3× sum

See also Spawn & Await for the 900 ms → 300 ms timing demo.

Keep-alive — servers and timers

A script that starts a server and then exits kills the server. The fix is to sleep or await:

// server
var srv = std.net.tcp_server()
srv.on_receive(fn(id, data) { srv.send(id, "echo: " + data) })
srv.listen(8999)
std.async.sleep(5000) // keep alive 5 s
srv.stop()

// repeating timer
var t = std.time.timer({callback: fn() { print("tick") }, duration: 1000, repeat: true})
std.async.sleep(3500)
t.cancel() // handle from std.time.timer

For one-shot timers, await the future instead:

var f = std.time.timer({callback: fn() { return 42 }, duration: 100, repeat: false})
print(std.async.await(f)) // 42

See std.time for handle vs future.

Shared state — coordinate via futures

Engine state is mutex-guarded, but script globals mutated by multiple fibers are not atomic:

var counter = 0
var fs = []
for (i : std.seq.range(10)) {
fs:insert(std.async.spawn(fn() { counter++ }))
}
for (f : fs) { std.async.await(f) }
print(counter) // often 10, but not guaranteed — races on counter++

Fix: aggregate via futures instead of shared mutation:

var fs2 = []
for (i : std.seq.range(10)) { fs2:insert(std.async.spawn(fn() { return 1 })) }
var sum = 0
for (f : fs2) { sum += std.async.await(f) }
print(sum) // reliably 10

See also

  • Overview — yield points and shared-state note
  • Errors — handling failures in fan-out
  • Race — first-wins alternative to fan-in