kickstart lädt deine Plist nicht neu

Christian Drexler

Ich habe damit einen Abend verloren, und die Lehre passt in zwei Zeilen, also hier zuerst die zwei Zeilen:

launchctl bootout gui/$(id -u)/local.my-service
launchctl bootstrap gui/$(id -u) ~/Library/LaunchAgents/local.my-service.plist

Führ die nach jeder Änderung an einer Plist aus. Alles darunter ist das Warum.

Was kickstart tut

launchctl kickstart -k gui/<uid>/<label> ist der Befehl, den jeder für "diesen Dienst neu starten" lernt. Er beendet den laufenden Prozess und startet ihn wieder. Was er nicht tut: die Plist-Datei lesen. launchd behält die Jobdefinition, die es früher geladen hat, im Speicher, und kickstart startet aus dieser Kopie. Die Datei auf der Platte kann sonst was enthalten; launchd hat seit dem letzten Laden nicht mehr hineingeschaut.

Für den eigentlichen Zweck des Befehls ist das vernünftig: einen Prozess durchstarten, dessen Definition sich nicht geändert hat. Zur Falle wird es in dem Moment, in dem du die Plist bearbeitest, weil sich Bearbeiten und Neustart wie eine Handlung anfühlen und der Neustart Erfolg meldet.

Wie es aussieht, wenn es zuschlägt

Ich hatte ein Projekt in einen neuen Ordner verschoben und den Pfad in mehreren Plists angepasst. Jede bekam ein kickstart. Jede kam hoch und blieb dann stehen, und im Log stand, der alte Pfad existiere nicht. Was stimmte, ich hatte ihn gerade verschoben. Die naheliegende Lesart war, dass meine Änderung falsch war, also öffnete ich die Plist, prüfte den Pfad, sah den richtigen neuen und machte wieder kickstart. Gleicher Fehler. Das habe ich dreimal gemacht, bevor ich den Neustart statt die Änderung verdächtigt habe.

Das Erkennungszeichen, wenn du es einmal kennst: Der Fehler nennt einen Wert, der nicht mehr in der Datei steht. Ein alter Pfad, ein alter Port, eine alte Umgebungsvariable. Beschwert sich der laufende Prozess über etwas, das du schon geändert hast, führt launchd eine Kopie der Plist von vor deiner Änderung aus.

Ein zweites Zeichen: launchctl print gui/$(id -u)/<label> zeigt die geladene Definition. Vergleich dort program arguments und environment mit der Datei. Weichen sie ab, hast du dein Problem.

Die Lösung

Nach Label entladen, dann die Datei laden. bootout entfernt den Job komplett aus launchd, laufender Prozess inklusive. bootstrap liest die Plist frisch und lädt sie. bootout muss zuerst kommen, denn ein Label zu bootstrappen, das schon geladen ist, scheitert mit "service already loaded", und nichts ändert sich. Auf älteren Systemen heißt das Paar launchctl unload und launchctl load; die funktionieren noch, aber Apple nennt sie Legacy, und das Paar bootout und bootstrap gibt klarere Fehlermeldungen.

Danach ist kickstart wieder in Ordnung, für den Fall, für den es gemacht ist: Die Plist ist unverändert, und nur der Prozess braucht einen Schubs.

Die Gewohnheit

Eine Plist-Änderung gilt bei mir jetzt erst als fertig, wenn bootout und bootstrap gelaufen sind, so wie eine Konfigurationsänderung an einem Serverprogramm erst mit dem Reload fertig ist. Eine kleine Shell-Funktion, die ein Label nimmt und beides tut, spart das Tippen. Was sie vor allem spart, sind die drei Runden, in denen du auf eine richtige Datei starrst und dich fragst, warum sie falsch ist.