Echo-Service
Bauen Sie einen CLI-Echo-Service, der mehrere Wippy-Prozesse, Channels, Coroutinen, Message-Passing und Prozessüberwachung verwendet.
Klassifizierung: Ausführbares Tutorial. Es enthält die vollständige Registry und alle Lua-Quelldateien für eine lokale CLI-Anwendung auf einem einzelnen Knoten sowie Schritte zum Starten und Überprüfen.
Überblick
Dieses Tutorial erstellt einen CLI-Client, der Nachrichten an einen Relay-Service sendet, der Worker für jede Nachricht startet. Es demonstriert:
- Prozesse starten — Unterprozesse dynamisch erstellen
- Message-Passing — Mit Send- und Receive-Operationen zwischen Prozessen kommunizieren
- Channels und Select — Auf mehrere Ereignisquellen warten
- Coroutinen — Nebenläufige Arbeit innerhalb eines Prozesses ausführen
- Prozessregistrierung — Prozesse nach Namen finden
- Monitoring — Lebenszyklen von Unterprozessen verfolgen
Voraussetzungen
-
Die Wippy-Runtime
v0.3.32aist alswippyverfügbar. Prüfen Sie dies mitwippy version --short. -
Ein interaktives Terminal.
-
Ein leeres Arbeitsverzeichnis. Erstellen Sie das Projekt und das Quellverzeichnis, bevor Sie die folgenden Dateien hinzufügen:
mkdir echo-service cd echo-service mkdir src
Architektur
flowchart TB
subgraph terminal["terminal.host"]
CLI["CLI Process"]
end
subgraph processes["process.host"]
Relay["Relay Process
(+ stats coroutine)"]
W1["Worker 1"]
W2["Worker 2"]
W3["Worker N"]
end
CLI -->|"send('relay', 'echo', msg)"| Relay
Relay -->|"spawn_monitored(worker)"| W1
Relay -->|"spawn_monitored(...)"| W2
Relay -->|"spawn_monitored(...)"| W3
W1 -->|"send(sender, 'echo_response')"| CLI
W2 -->|"send(...)"| CLI
W3 -->|"send(...)"| CLI
Projektstruktur
echo-service/
├── wippy.lock
└── src/
├── _index.yaml
├── cli.lua
├── relay.lua
└── worker.lua
Entry-Definitionen
Erstellen Sie src/_index.yaml:
version: "1.0"
namespace: app
entries:
- name: policy
kind: security.policy
policy:
actions:
- process.send
- process.spawn
- process.spawn.monitored
- process.registry.register
resources: "*"
effect: allow
- name: terminal
kind: terminal.host
lifecycle:
auto_start: true
- name: processes
kind: process.host
lifecycle:
auto_start: true
- name: cli
kind: process.lua
source: file://cli.lua
method: main
modules:
- io
- time
security:
policies: [app:policy]
- name: relay
kind: process.lua
source: file://relay.lua
method: main
modules:
- logger
- time
security:
policies: [app:policy]
- name: relay-service
kind: process.service
process: app:relay
host: app:processes
lifecycle:
auto_start: true
- name: worker
kind: process.lua
source: file://worker.lua
method: main
modules:
- time
security:
policies: [app:policy]
Sicherheit ist standardmäßig ablehnend, deshalb trägt jeder Prozess einen security:-Block, der die Policy benennt, die die von ihm ausgeführten Aktionen gewährt: einen Namen registrieren, Nachrichten senden und überwachte Worker starten.
Der Relay-Prozess
Der Relay registriert sich, behandelt Nachrichten, startet Worker und führt eine Stats-Coroutine aus.
Erstellen Sie src/relay.lua:
local logger = require("logger")
local time = require("time")
local stats = {
messages = 0,
workers_spawned = 0
}
local function stats_reporter()
while true do
time.sleep("5s")
logger:info("stats", {
messages = stats.messages,
workers_spawned = stats.workers_spawned
})
end
end
local function main()
local inbox = process.inbox()
local events = process.events()
local _, register_err = process.registry.register("relay")
if register_err then
error("cannot register relay: " .. tostring(register_err))
end
logger:info("relay started", {pid = process.pid()})
coroutine.spawn(stats_reporter)
while true do
local r = channel.select {
inbox:case_receive(),
events:case_receive()
}
if r.channel == events then
local event = r.value
if event.kind == process.event.CANCEL then
logger:info("relay stopping", stats)
return
elseif event.kind == process.event.EXIT then
logger:info("worker exited", {
from = event.from,
result = event.result
})
end
else
local msg = r.value
if msg:topic() == "echo" then
local echo = msg:payload():data()
stats.messages = stats.messages + 1
local worker_pid, err = process.spawn_monitored(
"app:worker",
"app:processes",
echo.sender,
echo.data
)
if err then
logger:error("spawn failed", {error = tostring(err)})
else
stats.workers_spawned = stats.workers_spawned + 1
end
end
end
end
end
return { main = main }
Schlüsselmuster {id="relay-key-patterns"}
Coroutine-Spawning
coroutine.spawn(stats_reporter)
Dadurch wird eine Coroutine gestartet, die Speicher mit der Hauptfunktion teilt. Coroutinen yielden bei I/O-Operationen wie time.sleep.
Channel-Select
local r = channel.select {
inbox:case_receive(),
events:case_receive()
}
Wartet auf mehrere Channels. r.channel identifiziert welcher gefeuert hat, r.value enthält die Daten. Das CANCEL-Event trifft auf demselben Events-Channel ein, wenn die Runtime den Service herunterfährt; ein Return aus main an dieser Stelle lässt den Host sauber stoppen, statt sein Stop-Timeout abzuwarten.
Payload-Extraktion
local echo = msg:payload():data()
Nachrichten haben msg:topic() für den Topic-String und msg:payload():data() für die Payload.
Spawn mit Monitoring
local worker_pid, err = process.spawn_monitored("app:worker", "app:processes", ...)
Dadurch wird der Worker gestartet und gleichzeitig überwacht. Wenn er beendet wird, empfängt das Relay ein EXIT-Event.
Der Worker-Prozess
Worker erhalten Argumente direkt und senden Antworten an den Sender.
Erstellen Sie src/worker.lua:
local function main(sender_pid, data)
local response = {
data = string.upper(data),
worker = process.pid()
}
local _, send_err = process.send(sender_pid, "echo_response", response)
if send_err then
error("cannot send echo response: " .. tostring(send_err))
end
return 0
end
return { main = main }
Der CLI-Prozess
Das CLI sendet Nachrichten nach registriertem Namen und wartet auf Antworten mit Timeout.
Erstellen Sie src/cli.lua:
local io = require("io")
local time = require("time")
local reset = "\027"
local function dim(s) return "\027" .. s .. reset end
local function green(s) return "\027" .. s .. reset end
local function yellow(s) return "\027" .. s .. reset end
local function cyan(s) return "\027" .. s .. reset end
local function main()
local inbox = process.inbox()
-- Wait for relay to register its name
local deadline = time.after("5s")
while not process.registry.lookup("relay") do
local tick = time.after("50ms")
local r = channel.select { deadline:case_receive(), tick:case_receive() }
if r.channel == deadline then
io.print("relay not ready")
return 1
end
end
io.print(cyan("Echo Client"))
io.print(dim("Type messages to echo. Ctrl+C to exit.\n"))
while true do
local _, write_err = io.write(yellow("> "))
if write_err then
io.eprint("cannot write prompt:", write_err)
return 1
end
local _, flush_err = io.flush()
if flush_err then
io.eprint("cannot flush prompt:", flush_err)
return 1
end
local input, read_err = io.readline()
if read_err then
io.eprint("cannot read input:", read_err)
return 1
end
if not input or #input == 0 then
break
end
local msg = {
sender = process.pid(),
data = input
}
local _, err = process.send("relay", "echo", msg)
if err then
io.print(dim(" error: " .. tostring(err)))
else
local timeout = time.after("2s")
local r = channel.select {
inbox:case_receive(),
timeout:case_receive()
}
if r.channel == timeout then
io.print(dim(" timeout"))
else
local msg = r.value
if msg:topic() == "echo_response" then
local resp = msg:payload():data()
io.print(green(" " .. resp.data))
io.print(dim(" from worker: " .. resp.worker))
end
end
end
end
io.print("\nGoodbye!")
return 0
end
return { main = main }
Schlüsselmuster {id="cli-key-patterns"}
Nach Namen senden
process.send("relay", "echo", msg)
process.send akzeptiert einen registrierten Namen als Ziel und gibt einen Fehler zurück, wenn dieser Name nicht aufgelöst werden kann.
Timeout-Muster
local timeout = time.after("2s")
local r = channel.select {
inbox:case_receive(),
timeout:case_receive()
}
if r.channel == timeout then
-- timed out
end
Ausführen
wippy init
wippy run -x app:cli
Beispielausgabe:
Echo Client
Type messages to echo. Ctrl+C to exit.
> hello world
HELLO WORLD
von worker: {c49e0627-fcdf-53ec-a95d-6f84bc3715f3@app:processes|0x00005}
Die Worker-PID wird zur Laufzeit erzeugt und fällt daher anders aus. Geben Sie mehrere Zeilen ein und prüfen Sie, dass jede Antwort in Großbuchstaben erscheint. Senden Sie eine leere Zeile, um das Programm sauber zu beenden.
Fehlerbehebung und Bereinigung
relay not readybedeutet, dass sich das automatisch gestartete Relay nicht innerhalb von fünf Sekunden registriert hat. Prüfen Sie das Runtime-Log auf einen Start-, Policy- oder Registry-Fehler des Relays.not allowed to spawnodernot allowed to sendbedeutet, dass den Prozesseinträgen der oben gezeigte Sicherheitskontextapp:process-policyfehlt.no terminal host foundbedeutet, dass der Eintragterminal.hostfehlt. Wenn das Projekt mehrere Terminal Hosts hat, ergänzen Sie den Run-Befehl um--host app:terminal.- Ein Timeout nach dem Senden bedeutet, dass der Worker keine Antwort zurückgegeben
hat. Prüfen Sie das Relay-Log auf einen Spawn-Fehler und stellen Sie sicher, dass
app:workerundapp:processesden Eintragsnamen entsprechen. - Senden Sie eine leere Zeile, um die CLI zu verlassen. Drücken Sie Strg+C, wenn die
Runtime weiterläuft; löschen Sie anschließend
echo-service/, wenn es nur eine vorübergehende Übung war.
Nächste Schritte
- Prozessverwaltung — Referenz der Prozess-API
- Channels — Referenz der Channel-API
- Zeit und Dauer — Referenz der Zeit-API