Process Model
Process Model
Section titled “Process Model”Wippy executes code in isolated processes—lightweight state machines that communicate through message passing. This actor-model approach eliminates shared-state bugs and makes concurrent programming predictable.
State Machine Execution
Section titled “State Machine Execution”Every process follows the same pattern: initialize, step through execution yielding on blocking operations, and close when complete. The scheduler multiplexes thousands of processes across a worker pool, running other processes while one waits for I/O.
Processes support multiple concurrent yields—you can start several async operations and wait for any or all to complete. This enables efficient parallel I/O without spawning additional processes.
flowchart LR Ready --> Running Running --> Blocked Running --> Idle Blocked --> Running Idle --> Running Running --> CompleteProcesses aren’t limited to Lua. The runtime already supports WebAssembly modules via the process.wasm kind, and the architecture admits any state machine implementation.
Process Hosts
Section titled “Process Hosts”Wippy runs multiple process hosts within a single runtime, each with different capabilities and security boundaries. System processes running privileged functions can live in one host, isolated from hosts running user sessions. Hosts can restrict what processes are allowed to do—in Erlang you’d need separate nodes for this level of isolation.
Some hosts are specialized. The Terminal host, for example, runs a single process but grants it access to IO operations that other hosts deny. This lets you mix trust levels in one deployment—system services with full access alongside sandboxed user code.
Security Model
Section titled “Security Model”Every process executes under an actor identity and security policy. Typically this is the user who initiated the call, but system processes run under a system actor with different privileges.
Access control works at multiple levels. Individual processes have their own access levels. Message sending between hosts can be forbidden based on security policy—a sandboxed user process might not be allowed to send messages to system hosts at all. The policy attached to the current actor determines what operations are permitted.
For the security implications of process isolation, see the Security Model.
Spawning Processes
Section titled “Spawning Processes”Create background processes with process.spawn():
local pid = process.spawn("app.workers:handler", "app:processes", arg1, arg2)The first argument is the registry entry, the second is the process host, and remaining arguments pass to the process.
Spawn variants control lifecycle relationships:
| Function | Behavior |
|---|---|
spawn | Fire and forget |
spawn_monitored | Receive EXIT events when child exits |
spawn_linked | Bidirectional—either crash notifies the other |
Message Passing
Section titled “Message Passing”Processes communicate through messages, never shared memory:
process.send(target_pid, "topic", payload)Messages from the same sender arrive in order. Messages from different senders may interleave. Delivery is fire-and-forget—use request-response patterns when you need confirmation.
Supervision
Section titled “Supervision”Any process can supervise others by monitoring them. A process spawns children with monitoring, watches for EXIT events, and restarts them on failure. This follows Erlang’s “let it crash” philosophy: processes crash on unexpected conditions, and the monitoring process handles recovery.
local worker = process.spawn_monitored("app.workers:handler", "app:processes")local event = process.events():receive()
if event.kind == process.event.EXIT and event.result.error then worker = process.spawn_monitored("app.workers:handler", "app:processes")endAt the root level, the runtime provides services that start and supervise long-running processes—similar to systemd in Linux. Define a process.service entry to have the runtime manage a process:
- name: worker.service kind: process.service process: app.workers:handler host: app:processes lifecycle: auto_start: true restart: max_attempts: 5 initial_delay: 1s max_delay: 30s backoff_factor: 2.0The service starts automatically, restarts on crash with backoff, and integrates with the runtime’s lifecycle management.
Process Upgrading
Section titled “Process Upgrading”Running processes can upgrade their code without losing identity. Call process.upgrade() to swap to a new definition while preserving PID, mailbox, and supervision relationships:
process.upgrade("app.workers:v2", current_state)The first argument is the new registry entry (or nil to reload the current definition). Additional arguments pass to the new version, letting you carry state across the upgrade. The process resumes execution with the new code immediately.
This enables hot code reload during development and zero-downtime updates in production. The runtime caches compiled protos, so upgrades don’t pay compilation cost repeatedly. If an upgrade fails for any reason, the process crashes and normal supervision semantics apply—a monitoring parent can restart it with the previous version or escalate the failure.
Scheduling
Section titled “Scheduling”The actor scheduler uses work-stealing across CPU cores. Each worker has a local queue for cache locality, with a global queue for distribution. Processes yield on blocking operations, allowing thousands to run concurrently on a handful of threads.