Supervision
Der Supervisor verwaltet Service-Start, Abhängigkeitsreihenfolge, Restarts und kontrolliertes Herunterfahren. Services mit auto_start: true starten beim Anwendungsboot.
Lebenszyklus-Konfiguration
Dienste registrieren sich beim Supervisor mit einem lifecycle-Block. Für Prozesse verwenden Sie process.service um eine Prozessdefinition zu umhüllen:
# Process definition (the code)
- name: worker_process
kind: process.lua
source: file://worker.lua
method: main
# Supervised service (wraps the process with lifecycle management)
- name: worker
kind: process.service
process: app:worker_process
host: app:processes
lifecycle:
auto_start: true
startup: required
start_timeout: 30s
stop_timeout: 10s
stable_threshold: 5s
requires:
- app:database
restart:
initial_delay: 2s
max_delay: 60s
max_attempts: 10
host muss einen konfigurierten Process Host referenzieren. Ein Eintrag unter requires muss entweder einen anderen überwachten Service auflösen oder über die Registry-Abhängigkeitsextraktion einen überwachten Service, dem die referenzierte Ressource gehört.
| Feld | Standard | Beschreibung |
|---|---|---|
auto_start |
false |
Beim Start des Supervisors automatisch starten |
startup |
required |
Startrichtlinie für eine Auto-Start-Wurzel: required blockiert den Boot bei Fehlern; optional darf fehlschlagen und weitere Versuche ausführen, ohne unabhängige Zweige zu blockieren |
start_timeout |
10s |
Maximale erlaubte Zeit für den Start |
stop_timeout |
10s |
Maximale Zeit für Graceful Shutdown |
stable_threshold |
5s |
Laufzeit bevor Dienst als stabil gilt |
requires |
[] |
Dienste, die zuerst laufen müssen (Legacy-Alias: depends_on) |
startup |
required |
required meldet einen fehlgeschlagenen oder blockierten Auto-Start als Transaktionsfehler; optional lässt den Dienst im Hintergrund weiter versuchen, ohne den Batch scheitern zu lassen |
Abhängigkeitsauflösung
Der Supervisor löst Abhängigkeiten aus zwei Quellen auf:
- Explizite Abhängigkeiten deklariert in
requires(oder dem Legacy-depends_on) - Registry-extrahierte Abhängigkeiten aus Entry-Referenzen (z.B.
database: app:dbin Ihrer Konfiguration)
graph LR
A[HTTP Server] --> B[Router]
B --> C[Handler Function]
C --> D[Database]
C --> E[Cache]
Abhängigkeiten starten vor Abhängigen. Wenn Dienst C von A und B abhängt, müssen sowohl A als auch B den Running-Zustand erreichen, bevor C startet.
requires deklarieren. Der Supervisor extrahiert Abhängigkeiten automatisch aus Registry-Referenzen in Ihrer Entry-Konfiguration.
Neustart-Richtlinie
Wenn ein Service fehlschlägt, führt der Supervisor gemäß seinem restart-Block weitere Versuche aus:
lifecycle:
restart:
initial_delay: 1s # First retry wait
max_delay: 90s # Accepted backoff cap; see current behavior below
backoff_factor: 2.0 # Accepted multiplier; see current behavior below
jitter: 0.1 # ±10% randomization
max_attempts: 0 # 0 = infinite retries
In Runtime v0.3.32a erzeugt der Supervisor für jeden Retry einen neuen Backoff-Rechner und verwendet nur dessen erstes Intervall. Jeder Retry wartet daher initial_delay mit dem konfigurierten Jitter, bei den gezeigten Werten 0,9 bis 1,1 Sekunden. backoff_factor und max_delay werden als Konfigurationsfelder akzeptiert, ändern diesen Ablauf in der festgelegten Runtime jedoch nicht.
max_attempts zählt den ersten fehlgeschlagenen Start mit. Der Wert 1 erlaubt keinen Retry, 10 höchstens neun weitere Starts. 0 erlaubt unbegrenzt viele Versuche.
Wenn ein Dienst länger als stable_threshold läuft, wird der Wiederholungszähler zurückgesetzt. Dies verhindert, dass vorübergehende Fehler die Verzögerungen dauerhaft eskalieren.
Terminale Fehler
Diese Fehler stoppen Wiederholungsversuche:
- Context-Abbruch
- Explizite Beendigungsanforderung
- Als nicht wiederholbar markierte Fehler
Sicherheitskontext
Dienste können mit einer bestimmten Sicherheitsidentität laufen:
# Process definition
- name: admin_worker_process
kind: process.lua
source: file://admin_worker.lua
method: main
# Supervised service with security context
- name: admin_worker
kind: process.service
process: app:admin_worker_process
host: app:processes
lifecycle:
auto_start: true
security:
actor:
id: "service:admin-worker"
meta:
role: admin
groups:
- app:admin_policies
policies:
- app:data_access
Der Sicherheitskontext setzt:
| Feld | Beschreibung |
|---|---|
actor.id |
Identitäts-String für diesen Dienst |
actor.meta |
Schlüssel-Wert-Metadaten (Rolle, Berechtigungen, etc.) |
groups |
Anzuwendende Richtliniengruppen |
policies |
Anzuwendende einzelne Richtlinien |
Im Dienst laufender Code erbt diesen Sicherheitskontext. Das security-Modul kann dann Berechtigungen prüfen:
local security = require("security")
if security.can("delete", "users") then
-- allowed
end
Neuregistrierung und Ersetzung
Eine Registry-Änderung kann eine ID neu registrieren, für die bereits ein Controller läuft. Trägt die Registrierung dieselbe Dienstinstanz, wird nichts angetastet. Trägt sie eine andere Instanz — der Manager hat den Dienst neu gebaut, weil sich seine Konfiguration geändert hat — nimmt der Supervisor den bestehenden Controller außer Dienst und übernimmt den Ersatz.
Die Außerdienststellung betrifft mehr als nur den einen Dienst. Ein laufender Abhängiger hält die überholte Instanz fest und kann daher nicht gegen einen Dienst weiterlaufen, der unter ihm ersetzt wird; die Außerdienststellungs-Hülle umfasst den ersetzten Dienst plus jeden laufenden Dienst, der von ihm abhängt, gestoppt in Abhängigkeitsreihenfolge (Abhängige zuerst). Bereits gestoppte Dienste werden kein zweites Mal gestoppt — ein Manager, der seine eigene Instanz vor der Neuregistrierung stoppt, sieht kein überflüssiges Stop.
Die Übergabe ist transaktional:
- Der Plan wird berechnet, ohne etwas anzutasten, sodass ein Fehler bei der Planung die laufende Menge unverändert lässt.
- Der Stop-Batch läuft. Schlägt ein Stop fehl, wird die Übergabe abgelehnt: Die vom Batch bereits gestoppten Dienste werden wieder hochgefahren und der Fehler gemeldet. Ein Dienst, der nicht wieder hochgefahren werden konnte, wird in diesem Fehler benannt. Der Supervisor besitzt am Ende dieselbe laufende Menge wie vor dem Commit, nie eine halb außer Dienst gestellte.
- Erst nachdem der Batch erfolgreich war, werden die außer Dienst gestellten Controller verworfen und abgebrochen, was die überholten Dienstinstanzen freigibt.
- Der Ersatz wird über denselben abhängigkeitsbewussten Sequencer wie jeder andere Start erzeugt und gestartet, und die für die Übergabe gestoppten Abhängigen kommen gegen die übernommene Instanz wieder hoch.
Ein Dienst, der vor der Ersetzung lief, wird danach neu gestartet, selbst wenn die neue Registrierung auto_start: false setzt — das Ersetzen eines aktiven Dienstes ist ein Update, kein implizites Stoppen. Der Neustart eines gestoppten Abhängigen richtet sich nach dessen eigener Neustart-Richtlinie und blockiert den Commit nicht.
Dienstzustände
stateDiagram-v2
[*] --> Unknown
Unknown --> Starting
Starting --> Running
Running --> Stopping
Stopping --> Stopped
Stopping --> Failed : timeout/cancel
Stopped --> [*]
Running --> Failed
Starting --> Failed
Failed --> Starting : retry
Running --> Exited
Starting --> Exited
Exited --> [*]
Der Supervisor überführt Dienste durch diese Zustände:
| Zustand | Beschreibung |
|---|---|
Unknown |
Registriert aber nicht gestartet |
Starting |
Start in Bearbeitung |
Running |
Läuft normal |
Stopping |
Kontrolliertes Herunterfahren in Bearbeitung |
Stopped |
Sauber beendet |
Exited |
Durch ausdrückliche Anforderung oder einen nicht wiederholbaren/terminalen Fehler beendet |
Failed |
Fehler aufgetreten, kann wiederholt werden |
Start- und Herunterfahrreihenfolge :id=startup-and-shutdown-order
Start: Erst Abhängigkeiten, dann Abhängige. Dienste auf derselben Abhängigkeitsebene können parallel starten.
Herunterfahren: Erst Abhängige, dann Abhängigkeiten. Dies stellt sicher, dass abhängige Dienste fertig werden, bevor ihre Abhängigkeiten stoppen.
Startup: database → cache → handler → http_server
Shutdown: http_server → handler → cache → database
Bei SIGINT oder SIGTERM beginnt die Runtime einen sauberen Shutdown, und die gesamte Sequenz läuft unter einem einzigen Budget: shutdown.timeout in der Runtime-Konfiguration (Standard 30s). Dieses Budget ist eine frische Deadline, die den unterbrochenen Kontext nicht erbt, sodass ein Strg-C den Shutdown der Komponenten nicht abschneidet; das stop_timeout pro Dienst begrenzt darin weiterhin jeden einzelnen Stop. Ein zweites Signal überspringt die Sequenz und beendet sofort.
# .wippy.yaml
shutdown:
timeout: 60s
Siehe auch
- Prozessmodell — Prozesslebenszyklus
- Konfiguration — YAML-Konfigurationsformat
- Sicherheitsmodul — Berechtigungsprüfungen in Lua