Skip to content

Commit 5cab79e

Browse files
committed
fix(rootfs/qemu-static): killswitch builder anchors emulation lock; native builder probes EX-NB before disabling qemu-arm
K-builder (NATIVE_ARMHF_ON_ARM64=no) now takes a SH-lock on /proc/sys/fs/binfmt_misc/qemu-arm for its lifetime instead of returning immediately. N-builder (default) probes EX-NB on a fresh fd before acquiring its own SH; if probe fails AND qemu-arm is observably enabled (state==1), a K-builder holds the emulation anchor and switching to native mid-flight would corrupt their qemu-arm-static routing — refuse with exit_with_error. Symmetric closure of the K↔N race: - N→K: K sees qemu-arm=0 → fail-fast via the existing prepare_host guard (addressed in 9d8b848). - K→N: N sees EX-NB fail + state=1 → fail-fast with 'concurrent build with NATIVE_ARMHF_ON_ARM64=no holds emulation lock' (this commit). Verified end-to-end on Hetzner CAX21 (4-core Ampere Altra, Ubuntu 6.8): - N first, K second: K exits 8s with 'qemu-arm globally disabled by another concurrent build'. - K first, N second: N exits 12s with 'cannot enable native armhf: concurrent build with NATIVE_ARMHF_ON_ARM64=no holds emulation lock'. - Cleanup of both K (release SH) and N (last-out restore echo 1) leaves qemu-arm in 'enabled' state. Assisted-by: Claude:claude-opus-4.7
1 parent 9d8b848 commit 5cab79e

1 file changed

Lines changed: 44 additions & 1 deletion

File tree

lib/functions/rootfs/qemu-static.sh

Lines changed: 44 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -177,10 +177,28 @@ function _native_armhf_observe_qemu_arm_state() {
177177
}
178178

179179
function _native_armhf_setup_binfmt_elf() {
180+
declare killswitch=no
180181
case "${NATIVE_ARMHF_ON_ARM64:-auto}" in
181-
no | never | disabled) return 1 ;;
182+
no | never | disabled) killswitch=yes ;;
182183
esac
183184

185+
# Killswitch path: still take SH-lock on qemu-arm so concurrent
186+
# native-armhf builders detect us via EX-NB probe and refuse to switch
187+
# qemu-arm off. Without this anchor an N-builder arriving mid-K-chroot
188+
# would echo 0 and silently break K's qemu-arm-static routing.
189+
if [[ "${killswitch}" == "yes" ]]; then
190+
if [[ -e /proc/sys/fs/binfmt_misc/qemu-arm ]] &&
191+
{ exec {_native_armhf_emul_lock_fd}< /proc/sys/fs/binfmt_misc/qemu-arm; } 2> /dev/null &&
192+
flock -s -n "${_native_armhf_emul_lock_fd}"; then
193+
add_cleanup_handler trap_handler_native_armhf_release_emul_lock
194+
display_alert "Native armhf via binfmt_elf" "killswitch active; emulation-mode SH-lock acquired (blocks concurrent native-armhf switchover)" "info"
195+
else
196+
[[ -n "${_native_armhf_emul_lock_fd:-}" ]] && exec {_native_armhf_emul_lock_fd}>&-
197+
unset _native_armhf_emul_lock_fd
198+
fi
199+
return 1
200+
fi
201+
184202
# Idempotent: callers in rootfs-create.sh and rootfs-image.sh invoke this
185203
# from both the cache-miss and cache-hit paths.
186204
[[ "${ARMBIAN_NATIVE_ARMHF_VIA_BINFMT_ELF:-no}" == "yes" ]] && return 0
@@ -209,6 +227,23 @@ function _native_armhf_setup_binfmt_elf() {
209227
display_alert "Native armhf via binfmt_elf" "cannot open binfmt_misc/qemu-arm; falling back to qemu emulation" "wrn"
210228
return 1
211229
fi
230+
231+
# EX-NB probe BEFORE acquiring our own SH (otherwise our own SH would
232+
# block the probe — flock counts per-OFD, our two fds on the same file
233+
# would interfere). EX-NB success means zero other SH-holders. Failure
234+
# with state="1" identifies a killswitch K-builder holding the
235+
# emulation-mode anchor — switching qemu-arm off would corrupt their
236+
# chroot exec routing. Failure with state="0" is a peer N-builder in
237+
# joiner-territory.
238+
if flock -x -n "${_native_armhf_lock_fd}"; then
239+
flock -u "${_native_armhf_lock_fd}"
240+
elif [[ "$(_native_armhf_observe_qemu_arm_state)" == "1" ]]; then
241+
exec {_native_armhf_lock_fd}>&-
242+
unset _native_armhf_lock_fd
243+
display_alert "Native armhf via binfmt_elf" "concurrent build holds emulation-mode lock (NATIVE_ARMHF_ON_ARM64=no)" "err"
244+
exit_with_error "cannot enable native armhf: concurrent build with NATIVE_ARMHF_ON_ARM64=no holds emulation lock. Wait for it to finish or run on a separate host."
245+
fi
246+
212247
if ! flock -s -w 30 "${_native_armhf_lock_fd}"; then
213248
display_alert "Native armhf via binfmt_elf" "could not acquire shared flock on binfmt_misc/qemu-arm within 30s; falling back to qemu emulation" "wrn"
214249
exec {_native_armhf_lock_fd}>&-
@@ -243,6 +278,14 @@ function _native_armhf_setup_binfmt_elf() {
243278
return 0
244279
}
245280

281+
# Killswitch path cleanup: just release the SH-lock fd. No state mutation,
282+
# no last-out detection — the killswitch builder never wrote to qemu-arm.
283+
function trap_handler_native_armhf_release_emul_lock() {
284+
[[ -n "${_native_armhf_emul_lock_fd:-}" ]] || return 0
285+
exec {_native_armhf_emul_lock_fd}>&-
286+
unset _native_armhf_emul_lock_fd
287+
}
288+
246289
# Cleanup ordering invariant: this handler must run AFTER cleanups that kill
247290
# the build's subshells (umount / SDCARD / MOUNT teardown). BSD flock is per-
248291
# OFD, so a forked subshell inheriting our SH-fd shares the same lock entry —

0 commit comments

Comments
 (0)