diff options
| author | Bruce Ashfield <bruce.ashfield@gmail.com> | 2026-06-08 18:26:13 +0000 |
|---|---|---|
| committer | Bruce Ashfield <bruce.ashfield@gmail.com> | 2026-06-12 02:58:55 +0000 |
| commit | 23da02f0189cc046def58a43117b50d7918e62e1 (patch) | |
| tree | 6fc65ede1d22cac22d82b2c77eb467f20515db08 | |
| parent | a28f3d77c4abee80a38ee89d619c2fabc5279bd0 (diff) | |
| download | meta-virtualization-23da02f0189cc046def58a43117b50d7918e62e1.tar.gz | |
vrunner: poll for clean VM exit before SIGTERM in daemon_stop
The daemon_stop() flow sent ===SHUTDOWN=== over virtio-serial and then
slept exactly 2 seconds before unconditionally SIGTERMing QEMU. The
guest's graceful_shutdown(), which is what the SHUTDOWN command
ultimately triggers, does:
sync
umount /var/lib/containers/storage (ext4 journal commit)
sync
blockdev --flushbufs (per disk)
sync
sleep 2
reboot -f
Under load this routinely takes 5-30 seconds — the ext4 journal commit
on the state disk after a vimport that just wrote tens of MB of layer
blobs is the slow step. A fixed 2-second wait followed by SIGTERM kills
the guest mid-umount and leaves the state disk's journal half-committed:
layer files have correct inode metadata but partially-unwritten data
extents.
The next memres session remounts that disk and reads the apparently-OK
files. Tar-split reassembly during podman save / podman push then hits
the unwritten extents and produces:
Error: reading blob sha256:<hash>: EOF
Error: reading blob sha256:<hash>: file integrity checksum failed
for "<file>"
Reported via yocto-patches as a workaround in run-push-containers
(`<runtime> image rm --all` before push) on the autobuilder. The
"first push works, subsequent fail" pattern in that report comes from
the first push hitting a clean session and subsequent pushes inheriting
the corrupted state disk from the prior SIGTERM-truncated shutdown.
Replace the fixed sleep with a poll loop that watches for the QEMU
process to exit, up to 60 seconds (120 * 0.5s). 60s is generous enough
to cover heavy ext4 journal commits; short enough that a truly hung
guest doesn't block the caller indefinitely. The existing SIGTERM and
SIGKILL escalation paths remain as fallbacks for the genuinely-stuck
case.
Reproducer: vimport an OCI image as testimg:latest, save it, memres
restart, re-vimport, save again. Without this fix the second save
fails 100% of the time on the same blob digest with EOF or CRC error.
With this fix six consecutive vimport+save cycles across three restart
rounds complete cleanly.
Signed-off-by: Bruce Ashfield <bruce.ashfield@gmail.com>
| -rwxr-xr-x | recipes-containers/vcontainer/files/vrunner.sh | 26 |
1 files changed, 23 insertions, 3 deletions
diff --git a/recipes-containers/vcontainer/files/vrunner.sh b/recipes-containers/vcontainer/files/vrunner.sh index 2fd61655..92db55d2 100755 --- a/recipes-containers/vcontainer/files/vrunner.sh +++ b/recipes-containers/vcontainer/files/vrunner.sh | |||
| @@ -607,13 +607,33 @@ daemon_stop() { | |||
| 607 | local pid=$(cat "$DAEMON_PID_FILE") | 607 | local pid=$(cat "$DAEMON_PID_FILE") |
| 608 | log "INFO" "Stopping daemon (PID: $pid)..." | 608 | log "INFO" "Stopping daemon (PID: $pid)..." |
| 609 | 609 | ||
| 610 | # Send shutdown command via socket | 610 | # Send shutdown command via socket, then poll until the VM exits. |
| 611 | # | ||
| 612 | # The guest's graceful_shutdown() does sync + umount of | ||
| 613 | # /var/lib/containers/storage + blockdev --flushbufs + sync + sleep 2 | ||
| 614 | # + reboot -f. Under load (e.g. tens of MB of just-imported layer | ||
| 615 | # blobs awaiting ext4 journal commit) this routinely takes 5-30 | ||
| 616 | # seconds. A fixed 2-second wait followed by SIGTERM kills the | ||
| 617 | # guest mid-umount and leaves the state disk's ext4 journal | ||
| 618 | # half-committed: layer files have correct inode metadata but | ||
| 619 | # partially-unwritten data extents, and the next session's reads | ||
| 620 | # hit EOF or CRC failures during tar-split layer reassembly: | ||
| 621 | # | ||
| 622 | # Error: reading blob sha256:<hash>: EOF | ||
| 623 | # Error: reading blob sha256:<hash>: file integrity checksum | ||
| 624 | # failed for "<file>" | ||
| 611 | if [ -S "$DAEMON_SOCKET" ]; then | 625 | if [ -S "$DAEMON_SOCKET" ]; then |
| 612 | echo "===SHUTDOWN===" | socat - "UNIX-CONNECT:$DAEMON_SOCKET" 2>/dev/null || true | 626 | echo "===SHUTDOWN===" | socat - "UNIX-CONNECT:$DAEMON_SOCKET" 2>/dev/null || true |
| 613 | sleep 2 | 627 | # Poll up to 60s (120 * 0.5s). Generous enough to cover heavy |
| 628 | # ext4 journal commits; short enough that a truly hung guest | ||
| 629 | # doesn't block the caller indefinitely. | ||
| 630 | for _i in $(seq 1 120); do | ||
| 631 | kill -0 "$pid" 2>/dev/null || break | ||
| 632 | sleep 0.5 | ||
| 633 | done | ||
| 614 | fi | 634 | fi |
| 615 | 635 | ||
| 616 | # If still running, kill it | 636 | # If still running after the graceful window, escalate. |
| 617 | if kill -0 "$pid" 2>/dev/null; then | 637 | if kill -0 "$pid" 2>/dev/null; then |
| 618 | log "INFO" "Sending SIGTERM..." | 638 | log "INFO" "Sending SIGTERM..." |
| 619 | kill "$pid" 2>/dev/null || true | 639 | kill "$pid" 2>/dev/null || true |
