summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorBruce Ashfield <bruce.ashfield@gmail.com>2026-06-08 18:26:13 +0000
committerBruce Ashfield <bruce.ashfield@gmail.com>2026-06-12 02:58:55 +0000
commit23da02f0189cc046def58a43117b50d7918e62e1 (patch)
tree6fc65ede1d22cac22d82b2c77eb467f20515db08
parenta28f3d77c4abee80a38ee89d619c2fabc5279bd0 (diff)
downloadmeta-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-xrecipes-containers/vcontainer/files/vrunner.sh26
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