REFERENCE

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.

PropertyValue
PurposeSet up initial state, register handlers, subscribe to sensors.
Return value0 = success; non-zero = failure (space transitions to FAILED).
Timeoutruntime.budgets.on_init_deadline_ms (default 5000 ms).
ConstraintsNetwork messaging is available, but other spaces may not be ready.

Phase 2 · Processing

The runtime calls on_process() repeatedly after successful initialization.

PropertyValue
PurposeMain event loop — receive messages, process data, emit results.
Return value0 = continue; non-zero = request stop.
Cooperative schedulingThe guest MUST call node.Yield() regularly so the runtime can deliver messages and perform housekeeping.
WatchdogIf 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):

  1. The runtime cancels any blocking calls (RecvNext, SensorPoll, Select).
  2. If the guest exports on_close(), the runtime calls it once.
  3. Open streams and sinks are closed automatically.
  4. 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.

PropertyValue
PurposeGraceful cleanup — unsubscribe sensors, release actuators, flush buffers, notify peers.
Return value0 = clean shutdown; non-zero is logged but ignored.
Timeoutruntime.budgets.on_close_deadline_ms (default 5000 ms). Exceeding the deadline forces termination.
ConstraintsNetwork 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.md in the Samoza repository.