Repository navigation
composefs: Hold /boot open while a deployment is staged - #6
cgwalters-bot wants to merge 3 commits into
Conversation
|
Looks fine go ahead and push a PR to proper upstream |
|
Thanks! Promotion only goes ahead on an approval or a comment line that is exactly Generated-by: https://github.com/cgwalters/#llms |
|
Massively trim down the commit message it should basically just be like: Copy over the logic from ostreedev/ostree@f3db79e7 Then link to the bug |
| } | ||
|
|
||
| /// The mount that must stay busy while a deployment is staged. | ||
| const HOLD_PATH: &str = "/boot"; |
There was a problem hiding this comment.
Pretty sure we have another const for this somewhere
There was a problem hiding this comment.
Now using the existing crate::install::BOOT ("boot"), opening /{BOOT} like the other /boot users do; HOLD_PATH is gone.
Generated-by: https://github.com/cgwalters/#llms
This really is part of what an individual uki "boots". Signed-off-by: Alexander Larsson <alexl@redhat.com>
Otherwise there is no difference in bootc status after a rollback. Signed-off-by: Alexander Larsson <alexl@redhat.com>
On composefs installs /boot is usually the ESP, automounted by systemd-gpt-auto-generator with a 2 minute idle timeout. If that expire begins after the shutdown transaction is queued, but before bootc-finalize-staged's ExecStop looks up /boot (lsblk statfs()es every mountpoint), the two deadlock: the lookup blocks in autofs_expire_wait until systemd unmounts boot.mount, and boot.mount is ordered to stop after finalize via local-fs.target. After 5+5 minutes finalize is killed, and the system boots the old deployment. That's the "guest reboot timeout" flake seen mostly in plan-44, where the gap between `bootc switch` and the reboot is about 130s in CI, on both systemd-boot and grub legs. If the expire instead completes before shutdown, systemd won't remount /boot, so finalize fails immediately with "Failed to open /boot: Host is down" on grub and the old deployment boots. That's the "expected exactly one testbootcgroup" plan-44 failure on composefs grub legs. See the analyses in https://gist.github.com/cgwalters-bot/37b1789c26fd0af22f6879d93aa0a4bd and https://gist.github.com/cgwalters-bot/793b7485f34dd1586127982e40351e04 ostree hit the same problem and fixed it in ostreedev/ostree@f3db79e7 ("finalize-staged: Ensure /boot automount doesn't expire") with ostree-finalize-staged-hold.service, which keeps a file descriptor for /boot open from the root mount namespace for as long as a deployment is staged. Port that: bootc-finalize-staged.service now pulls in bootc-finalize-staged-hold.service, which runs `bootc composefs-finalize-staged --hold`. It's ordered before the finalize unit, so at shutdown it's only stopped after finalization is done. A busy /boot never expires, so it can't race with shutdown. It would also make adding Requires=boot.mount to the finalize unit (as bootc-dev#2488 does) safe, since without the hold an idle expire stops the finalize unit while the system is still running. Deviations from ostree: the hold only opens /boot rather than loading the whole sysroot, since that's all that's needed and it keeps the hold independent of storage setup (which may enter a private mount namespace, where autofs wouldn't see the fd). For the same reason the hold unit only has RequiresMountsFor=/boot, not /sysroot. And the finalize unit keeps RequiresMountsFor=/sysroot without /boot: the stop ordering against boot.mount already comes via local-fs.target, and leaving out Requires=boot.mount means that if the hold somehow fails, we're no worse off than before. test-44 now checks that staging on composefs starts the hold unit. Tested manually by rebooting inside the race window: that hung 4/4 times on main and 0/7 with this change, and plan-44 passed 3/3. Generated-by: AI
6e8438f to
013018a
Compare
|
Opened upstream as bootc-dev#2496. Closing this review draft. |
|
Both changes (trimmed commit message, reusing Generated-by: https://github.com/cgwalters/#llms |
On composefs installs
/bootis usually the ESP, automounted by systemd-gpt-auto-generator withTimeoutIdleSec=2min. When that idle expire overlaps with a staged deployment, finalization breaks in one of two ways, and both end with the machine booting the old deployment:bootc-finalize-staged.service'sExecStoplooks at/boot(lsblkdoesstatfs()on every mountpoint), they deadlock. The lookup blocks inautofs_expire_waituntil systemd unmountsboot.mount, butboot.mountis ordered to stop after finalize (vialocal-fs.target). Finalize is killed after 5+5 minutes.Failed to open /boot: Host is down (os error 112).This is behind most of the plan-44-shadow-fixup failures in CI:
test-44stages, then waits about 120-130s before rebooting, which lands right on the expire. In the 50 most recent failed CI runs, plan-44 was the most common failing plan (24 legs). 13 of those were the "guest reboot timeout" (the deadlock, on systemd-boot, grub and grub-cc legs). The other 11 wereexpected exactly one testbootcgroup in /etc/group, got: []on composefs grub legs, which is the EHOSTDOWN case: the old deployment booted, so the test found no group. Analyses with journals and CI links: deadlock, EHOSTDOWN.ostree hit the same problem and fixed it in ostreedev/ostree@f3db79e7 ("finalize-staged: Ensure /boot automount doesn't expire", ostreedev/ostree#2543) with
ostree-finalize-staged-hold.service. This ports that design.bootc-finalize-staged.servicenowWants=/After=a newbootc-finalize-staged-hold.service, which runsbootc composefs-finalize-staged --holdin the root mount namespace (ExecStart=+). That keeps an fd for/bootopen until it's stopped, which at shutdown happens after finalization. autofs never expires a busy mount, so nothing can race with shutdown. The hold only opens/bootand is dispatched before storage is loaded, so it doesn't depend on sysroot setup or end up in a private mount namespace, where autofs wouldn't see it.Relation to bootc-dev#2488: that PR adds
RequiresMountsFor=/bootto the finalize unit. Without a hold, the impliedRequires=boot.mountmakes the idle expire stop the finalize unit while the system is still running, so any reboot more than about 2 minutes after staging hangs. Its CI shows this: plan-44 hit a reboot timeout on 3 legs in the first attempt and 2 in the retry (centos-10 composefs systemd-boot, plus one fedora grub leg), each with finalize stopped 5-10s before the reboot (CI analysis). ostree hasRequiresMountsFor=/bootonly together with its hold unit, and that is what would make it safe here too. This PR deliberately keeps the finalize unit atRequiresMountsFor=/sysroot: the stop ordering againstboot.mountalready comes fromlocal-fs.target, and withoutRequires=boot.mounta failed hold leaves us no worse off than today.test-44now also asserts that staging on composefs starts the hold unit.Testing
All on a 64-core RHEL 10 runner with bcvk 0.19.0 and tmt 1.78.0, using CentOS Stream 10 composefs images built with
just build(BOOTC_variant=composefs, systemd-boot, xfs, BLS, unsealed), where/bootis the ESP as asystemd-1autofs withtimeout=120, as in CI.The key test reboots right inside the race: stage a trivial derived image the way test-44 does, then schedule
systemctl reboot0.25-1.0s before systemd's next/bootexpire check (after/boothas been idle for more than 120s). Without this change that hung for about 15 minutes and booted the old deployment every time (4/4 at those offsets, 4/10 across a wider -1.0..0s sweep). With it, nothing hung: before the rebase 0/7 at those offsets and 0/13 across the sweep, and on the rebased commit 3/3 more at -0.25, -0.5 and -1.0s (reboot about 133s after staging). Every run came up in the new deployment, withbootc-finalize-staged-hold.serviceactive and noboot.mountexpire in the previous boot's journal. Letting a staged system sit idle for 240s left finalize active and/bootmounted. On a Fedora 43 composefs grub image, rebooting after/boothad expired booted the old deployment on main (4/4, the EHOSTDOWN case), and 3/3 runs with the hold that waited 300s before rebooting booted the new one.plan-44-shadow-fixupviacargo xtask run-tmtwith CI's flags passed 3/3 before the rebase and 2/2 after, andjust validateandjust unit-testspass on the rebased commit. The tmt runs only show the plan and the new assertion work; the devspace is fast enough that switch→reboot stays under 120s there, so they don't measure the flake rate.Caveats
/boot. A system with the ESP automounted at/efiand/booton the root filesystem isn't covered, andlsblkin finalize stillstatfs()es every mountpoint, so another expiring automount could in principle still hang it. Whether finalize needslsblkat all is worth a separate look.Signed-off-by; add one before merging if you want it.Generated-by: https://github.com/cgwalters/#llms
Review draft in cgwalters-forge, not upstream yet. This section is removed when the PR is opened upstream.
bootc-dev/bootc, basemainPVTI_lAHOAQ_SPs4Bj2Gizg8P2EITo review:
/draft, then approve, to open it upstream as a draft (/readyundoes that).