Fix systemd oneshot upgrade handoff deadlock

This commit is contained in:
admin committed 2026-09-10 14:40:24 +02:00
1 parent 56fbcdad74
commit 898df2e926
5 files changed
+190 -20

No files matched your search

+27 -8
View File
@@ -40,7 +40,8 @@ done
need systemctl; need flock
# This lock excludes two coordinators, independently from ordinary backup jobs.
exec 8>/run/app-backup-upgrade.lock
if [[ $UP_WORKER == true ]]; then flock -w 60 8; else flock -n 8 || die 'Another updater is preparing/running'; fi
# Workers install their persistent error reporting before waiting on this lock.
if [[ $UP_WORKER == false ]]; then flock -n 8 || die 'Another updater is preparing/running'; fi
prompt() {
local answer
@@ -244,20 +245,34 @@ if [[ $UP_WORKER == false ]]; then
sha256sum "$UP_STATE/candidate.conf" | cut -d ' ' -f 1 > "$UP_STATE/candidate.sha256"
echo "Vorpruefung erfolgreich. Writer: $(tr '\n' ' ' < "$UP_STATE/writers")"
echo 'Jetzt folgen Installation, ein vollstaendiges Backup und Remote-Restore-Pruefung.'
printf 'QUEUED\n' > "$UP_STATE/result.txt"
if [[ $UP_FOREGROUND == true ]]; then
flock -u 9; flock -u 8
exec bash "$UPDATE_ROOT/update.sh" --worker "$UP_STATE"
fi
# A new systemd service inherits no shell FDs; it acquires both locks itself.
printf 'QUEUED\n' > "$UP_STATE/result.txt"
systemd-run --unit=app-backup-upgrade --collect --property=Type=oneshot \
# A oneshot start job completes only when ExecStart exits. Waiting for that
# job while holding FD 8 deadlocks with the worker waiting for the same lock.
# Only enqueue here; result.txt, not systemd-run's exit code, proves success.
# The launcher must not keep copies of our locks; systemd starts a fresh worker.
systemd-run --no-block --unit=app-backup-upgrade --collect --property=Type=oneshot \
--property=TimeoutStartSec=24h --property=TimeoutStopSec=5min --property=KillMode=mixed \
/bin/bash "$UPDATE_ROOT/update.sh" --worker "$UP_STATE" || {
printf 'FAILED: systemd worker could not be started; installed package/config/timer unchanged.\n' > "$UP_STATE/result.txt"
/bin/bash "$UPDATE_ROOT/update.sh" --worker "$UP_STATE" 8>&- 9>&- || {
# Revoke the queued state while still holding the coordinator lock. Even
# if systemd accepted the job but its reply failed, it must not install.
printf 'FAILED: systemd submission failed; installed package/config/timer unchanged.\n' > "$UP_STATE/result.txt"
cat "$UP_STATE/result.txt"
echo "Details: sudo journalctl -u app-backup-upgrade.service -n 100 --no-pager"
echo "Ergebnis: sudo cat $UP_STATE/result.txt"
exit 1
}
echo 'Update laeuft unabhaengig von SSH weiter.'
echo 'Verlauf: sudo journalctl -fu app-backup-upgrade.service'
# Release the backup lock FIRST. Once FD 8 is free, the worker may acquire
# both immediately; reversing the order can make its backup lock check fail.
flock -u 9
flock -u 8
exec 9>&- 8>&-
echo 'Update-Auftrag an systemd uebergeben; Ausfuehrung unabhaengig von SSH.'
echo 'Abgeschlossen ist das Update erst bei SUCCESS in der Ergebnisdatei.'
echo 'Verlauf: sudo journalctl -fu app-backup-upgrade.service --no-pager'
echo "Ergebnis: sudo cat $UP_STATE/result.txt"
exit 0
fi
@@ -289,6 +304,10 @@ worker_finish() {
trap worker_finish EXIT
trap 'exit 130' INT
trap 'exit 143' TERM
# Record lock timeouts in update.log/result.txt as well as journald. Previously
# this wait happened before logging/traps, leaving only systemd's exit status.
flock -w 60 8 || die 'Timed out waiting for update coordinator lock (60s); installation not started'
[[ $(cat "$UP_STATE/result.txt") == QUEUED ]] || die 'Upgrade is not queued or submission was cancelled; rerun updater'
printf 'RUNNING\n' > "$UP_STATE/result.txt"
acquire_backup_lock
export APP_BACKUP_INHERIT_LOCK=1