Skip to content

systemd: Order bootc-finalize-staged after /boot mount - #2488

Open
ericcurtin wants to merge 1 commit into
bootc-dev:mainfrom
ericcurtin:finalize-staged-requires-boot
Open

ericcurtin wants to merge 1 commit into
bootc-dev:mainfrom
ericcurtin:finalize-staged-requires-boot

Conversation

@ericcurtin

Copy link
Copy Markdown
Contributor

bootc-finalize-staged.service writes to /boot (the unit even says so) but only declares RequiresMountsFor=/sysroot. With a systemd-gpt-auto-generator /boot automount there is no ordering between our ExecStop at shutdown and boot.mount/boot.automount, and lsblk can block forever in autofs_expire_wait:

[  737.917938] INFO: task lsblk:2605 blocked for more than 122 seconds.
...
[  737.941971]  autofs_expire_wait+0x88/0x120
[  737.943547]  autofs_d_manage+0x6c/0x180
...
[  737.955696]  user_statfs+0x57/0xd0
systemd[1]: bootc-finalize-staged.service: Stopping timed out. Terminating.

systemd kills it after TimeoutStopSec=5m, the staged deployment is never finalized, and in CI this shows up as plan-44-shadow-fixup "guest reboot timeout" (3 legs in https://github.com/bootc-dev/bootc/actions/runs/35764348603, from #2476).

Mirror ostree-finalize-staged.service, which has RequiresMountsFor=/sysroot /boot /etc since ostreedev/ostree#2544 for the same hazard.

Related: #2402.

Generated-by: AI
Diagnosed from the CI console/journal logs; I have not reproduced the deadlock locally, relying on CI to exercise it.

@cgwalters

Copy link
Copy Markdown
Collaborator

@ericcurtin Our AGENTS.md has this intentional stumbling block around the DCO, your PRs are welcome but please do add the signoff to reduce friction.

(We may change things around this in the future, xref bootc-dev/infra#260 )

@cgwalters

Copy link
Copy Markdown
Collaborator

On the technical side, I think it may be a bit more complicated than that...we changed ostree to have a unit which holds a fd open, we might need to do the same here. But this is not wrong at least!

The unit writes to /boot but only declared RequiresMountsFor=/sysroot.
When /boot is a systemd-gpt-auto automount, there is no ordering
between our ExecStop at shutdown and boot.mount/boot.automount, so
lsblk in composefs-finalize-staged can block forever in
autofs_expire_wait; systemd kills it after TimeoutStopSec=5m and the
staged deployment is never finalized. Seen intermittently in CI as
plan-44-shadow-fixup reboot timeouts.

Mirror ostree-finalize-staged.service, which gained /boot here for the
same reason (ostreedev/ostree#2544).

Generated-by: AI
Signed-off-by: Eric Curtin <eric.curtin@docker.com>
@ericcurtin
ericcurtin force-pushed the finalize-staged-requires-boot branch from ca7f513 to 5abe476 Compare September 22, 2026 22:15
@cgwalters-bot cgwalters-bot moved this to Todo in Workstream Sep 23, 2026
@cgwalters-bot cgwalters-bot moved this from Todo to In Progress in Workstream Sep 23, 2026
@cgwalters-bot cgwalters-bot moved this from In Progress to In Review in Workstream Sep 23, 2026
@Johan-Liebert1

Copy link
Copy Markdown
Member

I still the shadow-fixup test failing, so this isn't the exact cause I guess.

cgwalters-bot added a commit to cgwalters-bot/bootc that referenced this pull request Sep 23, 2026
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.

Assisted-by: AI

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants