fix(installer): free the rc.common lock before the package manager runs

Bounding the hooks fixes future upgrades, but the hook that runs during
THIS one belongs to the build already installed, and every release up to
1.2.66 shipped a prerm with no timeout. It blocks on rc.common's
flock -w 1000, which procd keeps re-populating with retry_start_on_wan_up
waiters between pre- and post-upgrade, so apk's pre-upgrade outlives its
patience and the transaction is rolled back.

cleanup_legacy already stops and disables the service; it now also kills
the flock waiters, so the old unbounded hook has nothing left to wait on.
This commit is contained in:
Dushnilin
2026-08-09 18:35:42 +03:00
parent f770316345
commit 2fc57d383d
2 changed files with 46 additions and 0 deletions
+16
View File
@@ -1194,6 +1194,21 @@ function installer_deactivate_legacy_base() {
return true;
}
// Every /etc/init.d/tachyon call goes through rc.common, which serializes on
// `flock -w 1000`. procd re-triggers retry_start_on_wan_up between a package's
// pre- and post-upgrade, so waiters keep appearing and a wedged one holds the
// lock for a quarter of an hour. That is long enough for the package manager to
// give up on the pre-upgrade hook and roll the whole transaction back, leaving
// the old build installed. Builds from 1.2.66 and earlier ship a prerm with no
// timeout of its own, and it is that OLD hook which runs during this upgrade —
// so the lock has to be free before the package manager is invoked at all.
function installer_release_init_lock() {
for (let pattern in [ "99-tachyon-wan|flock 1000", "/etc/init.d/tachyon" ])
system("ps 2>/dev/null | grep -E '" + pattern + "' | grep -v grep | awk '{print $1}' | " +
"while read _pid; do kill -9 \"$_pid\" 2>/dev/null; done; true");
return true;
}
function installer_cleanup_legacy() {
let tachyon_installed = installer_package_installed("tachyon");
let legacy_installed = LEGACY_BRAND != "" && installer_package_installed(LEGACY_BACKEND_PACKAGE);
@@ -1212,6 +1227,7 @@ function installer_cleanup_legacy() {
run_args([ "timeout", "10", active_init, "stop" ]);
installer_restore_dnsmasq(active_bin, legacy_installed);
run_args([ "timeout", "10", active_init, "disable" ]);
installer_release_init_lock();
}
if (path_executable("/etc/init.d/netshift")) {
+30
View File
@@ -78,4 +78,34 @@ for pm in apk opkg; do
fi
done
# Bounding the hooks only helps the build being installed; during THIS upgrade the
# package manager runs the prerm of whatever is already on disk, and every release
# up to 1.2.66 shipped one with no timeout. It blocks on rc.common's `flock -w
# 1000`, so the installer has to free that lock itself before invoking the package
# manager — otherwise the pre-upgrade hook outlives apk's patience and the whole
# transaction is rolled back.
sed -n '/^function installer_release_init_lock() {/,/^}/p' "$INSTALLER" >"$WORK_DIR/lock.uc"
[ -s "$WORK_DIR/lock.uc" ] ||
fail "install.sh must define installer_release_init_lock to unwedge the old prerm hook"
for pattern in '99-tachyon-wan|flock 1000' '/etc/init.d/tachyon'; do
grep -Fq "$pattern" "$WORK_DIR/lock.uc" ||
fail "installer_release_init_lock must clear '$pattern' waiters"
done
# ucode resolves names lexically and does not hoist, so a call placed above the
# declaration silently becomes null and the lock is never released.
lock_decl="$(grep -n '^function installer_release_init_lock() {' "$INSTALLER" | head -1 | cut -d: -f1)"
lock_call="$(grep -n 'installer_release_init_lock();' "$INSTALLER" | head -1 | cut -d: -f1)"
[ -n "$lock_decl" ] && [ -n "$lock_call" ] ||
fail "installer_release_init_lock must be both declared and called"
[ "$lock_decl" -lt "$lock_call" ] ||
fail "installer_release_init_lock is called before its declaration (ucode does not hoist)"
# The lock has to be dropped as part of stopping the service, i.e. before any
# package is handed to the package manager.
sed -n '/^function installer_cleanup_legacy() {/,/^}/p' "$INSTALLER" >"$WORK_DIR/cleanup.uc"
grep -Fq 'installer_release_init_lock();' "$WORK_DIR/cleanup.uc" ||
fail "installer_cleanup_legacy must release the init lock before the packages are installed"
printf 'PASS: installer rollback detection\n'