ABI · Lifecycle
The three lifecycle entry points every WASM guest exports — on_init, on_process, on_close.
Every WASM guest in Samoza OS exports up to three lifecycle functions plus an empty main:
package main
//export on_init
func on_init() int32 {
// Called once when the space starts.
// Initialize state, subscribe to sensors, etc.
// Return 0 on success, non-zero on failure.
return 0
}
//export on_process
func on_process() int32 {
// Called repeatedly by the runtime.
// This is the main event loop.
// Must call node.Yield() periodically.
buf := make([]byte, 4096)
for {
n := net.RecvOrYield(500, buf)
if n > 0 {
// handle message
}
node.Yield()
}
}
//export on_close
func on_close() int32 {
// Optional: graceful cleanup.
abi.SensorUnsubscribe("accelerometer")
abi.ActuatorRelease("haptics")
return 0
}
func main() {}
on_init is optional. on_process is required. on_close is optional. main must exist but should be empty.
Phase 1 · Initialization
The runtime calls on_init() once after loading the WASM module.
| Property | Value |
|---|---|
| Purpose | Set up initial state, register handlers, subscribe to sensors. |
| Return value | 0 = success; non-zero = failure (space transitions to FAILED). |
| Timeout | runtime.budgets.on_init_deadline_ms (default 5000 ms). |
| Constraints | Network messaging is available, but other spaces may not be ready. |
Phase 2 · Processing
The runtime calls on_process() repeatedly after successful initialization.
| Property | Value |
|---|---|
| Purpose | Main event loop — receive messages, process data, emit results. |
| Return value | 0 = continue; non-zero = request stop. |
| Cooperative scheduling | The guest MUST call node.Yield() regularly so the runtime can deliver messages and perform housekeeping. |
| Watchdog | If on_process runs longer than runtime.budgets.on_process_watchdog_ms without yielding, the runtime terminates the space. |
The canonical event loop:
//export on_process
func on_process() int32 {
buf := make([]byte, 4096)
for {
n := net.RecvOrYield(500, buf) // wait up to 500 ms
if n == 0 {
continue // RecvOrYield already yielded
}
processMessage(buf[:n])
node.Yield() // explicit yield after work
}
}
Variations:
- Sensor-driven IO space. Poll sensors instead of (or in addition to) network messages.
- Timer-driven space. Use
node.SelfEmitAfter()to schedule periodic work. - Multi-source space. Use
sys.Select()to wait on multiple streams simultaneously.
Phase 3 · Shutdown
When a space is stopped (topology teardown, retile, error):
- The runtime cancels any blocking calls (
RecvNext,SensorPoll,Select). - If the guest exports
on_close(), the runtime calls it once. - Open streams and sinks are closed automatically.
- Actuator locks held by the space are released.
on_close is a courtesy hook, not a requirement — all resources are cleaned up regardless of whether you export it.
| Property | Value |
|---|---|
| Purpose | Graceful cleanup — unsubscribe sensors, release actuators, flush buffers, notify peers. |
| Return value | 0 = clean shutdown; non-zero is logged but ignored. |
| Timeout | runtime.budgets.on_close_deadline_ms (default 5000 ms). Exceeding the deadline forces termination. |
| Constraints | Network emit is available; receive may fail (space is in STOPPING). |
Memory model
A WASM space has its own memory. The host cannot reach into it; the guest cannot reach into the host. All host/guest data crossing the boundary is copied — typically as JSON in/out of pre-allocated byte buffers.
The runtime allocates the WASM linear memory at instantiation. Growing memory at runtime is supported but discouraged in tight loops.
See also
- ABI overview — module index.
- Develop · Guest ABI — the developer’s intro.
- Source spec:
docs/reference/guest-abi/01-lifecycle.mdin the Samoza repository.