diff options
| author | Bruce Ashfield <bruce.ashfield@gmail.com> | 2026-04-22 20:17:55 +0000 |
|---|---|---|
| committer | Bruce Ashfield <bruce.ashfield@gmail.com> | 2026-04-22 20:17:55 +0000 |
| commit | 891c00db7ba647d0b68a929ca1ad15b0ba9dc5a1 (patch) | |
| tree | 15dcbd63c1f9361118fb8532fe8d99a3c45daee7 /recipes-containers/vcontainer | |
| parent | b02e400b63849991c1590a3572e711740758583f (diff) | |
| download | meta-virtualization-891c00db7ba647d0b68a929ca1ad15b0ba9dc5a1.tar.gz | |
vcontainer: detach background-process stdio from memres start caller
The memres start operation spawns long-running background processes
(host-side idle watchdog and Xen domain monitor) that persist beyond
the vrunner.sh script. These processes inherited file descriptors
0/1/2 from the parent shell without redirection.
When invoked through a harness capturing output via pipes—such as
pytest's subprocess.run(..., capture_output=True)—the inherited pipe
write-ends kept the caller's read/communicate() operations blocked
until memres stop executed, potentially for up to 30 minutes
(IDLE_TIMEOUT default).
The fix fully detaches stdio from three background spawners:
- vrunner.sh: Watchdog subshell now redirects stdin from /dev/null,
stdout/stderr to /dev/null, and uses disown
- vrunner-backend-qemu.sh: Adds stdin redirection from /dev/null
to existing log file redirections
- vrunner-backend-xen.sh: Applies same detachment plus disown for
daemon mode; redirects stdin for ephemeral-mode console reader
From: Tim Orling <tim.orling@konsulko.com>
Signed-off-by: Bruce Ashfield <bruce.ashfield@gmail.com>
Diffstat (limited to 'recipes-containers/vcontainer')
3 files changed, 35 insertions, 7 deletions
diff --git a/recipes-containers/vcontainer/files/vrunner-backend-qemu.sh b/recipes-containers/vcontainer/files/vrunner-backend-qemu.sh index 87054876..3ea73cc7 100644 --- a/recipes-containers/vcontainer/files/vrunner-backend-qemu.sh +++ b/recipes-containers/vcontainer/files/vrunner-backend-qemu.sh | |||
| @@ -188,10 +188,16 @@ hv_start_vm_background() { | |||
| 188 | local log_file="$2" | 188 | local log_file="$2" |
| 189 | local timeout_val="$3" | 189 | local timeout_val="$3" |
| 190 | 190 | ||
| 191 | # Fully detach stdio from the invoking shell. In daemon mode this | ||
| 192 | # process outlives vrunner.sh, and if the CLI that invoked us was | ||
| 193 | # wrapped by something that pipes stdout/stderr (e.g. a test harness | ||
| 194 | # using subprocess.run(capture_output=True)), any inherited fd here | ||
| 195 | # would block the parent's read/communicate() call until QEMU exits. | ||
| 196 | # Redirect fd 0 from /dev/null and fd 1/fd 2 to the log file. | ||
| 191 | if [ -n "$timeout_val" ]; then | 197 | if [ -n "$timeout_val" ]; then |
| 192 | timeout $timeout_val $HV_CMD $HV_OPTS -append "$kernel_append" > "$log_file" 2>&1 & | 198 | timeout $timeout_val $HV_CMD $HV_OPTS -append "$kernel_append" </dev/null > "$log_file" 2>&1 & |
| 193 | else | 199 | else |
| 194 | $HV_CMD $HV_OPTS -append "$kernel_append" > "$log_file" 2>&1 & | 200 | $HV_CMD $HV_OPTS -append "$kernel_append" </dev/null > "$log_file" 2>&1 & |
| 195 | fi | 201 | fi |
| 196 | HV_VM_PID=$! | 202 | HV_VM_PID=$! |
| 197 | } | 203 | } |
diff --git a/recipes-containers/vcontainer/files/vrunner-backend-xen.sh b/recipes-containers/vcontainer/files/vrunner-backend-xen.sh index 55e87bd1..f490f91c 100644 --- a/recipes-containers/vcontainer/files/vrunner-backend-xen.sh +++ b/recipes-containers/vcontainer/files/vrunner-backend-xen.sh | |||
| @@ -397,13 +397,23 @@ hv_start_vm_background() { | |||
| 397 | # Monitor process: stays alive while domain exists. | 397 | # Monitor process: stays alive while domain exists. |
| 398 | # vcontainer-common.sh checks /proc/$pid → alive means daemon running. | 398 | # vcontainer-common.sh checks /proc/$pid → alive means daemon running. |
| 399 | # When domain dies (xl destroy, guest reboot), monitor exits. | 399 | # When domain dies (xl destroy, guest reboot), monitor exits. |
| 400 | # | ||
| 401 | # Detach the monitor's stdio from the invoking shell: in daemon | ||
| 402 | # mode this process outlives vrunner.sh, and any inherited fd | ||
| 403 | # would keep a pipe-wrapped caller (e.g. subprocess.run with | ||
| 404 | # capture_output=True) blocked in communicate() until the domain | ||
| 405 | # exits. Redirect fd 0/1/2 and disown. | ||
| 400 | local _domname="$HV_DOMNAME" | 406 | local _domname="$HV_DOMNAME" |
| 401 | (while xl list "$_domname" >/dev/null 2>&1; do sleep 10; done) & | 407 | (while xl list "$_domname" >/dev/null 2>&1; do sleep 10; done) \ |
| 408 | </dev/null >/dev/null 2>&1 & | ||
| 402 | HV_VM_PID=$! | 409 | HV_VM_PID=$! |
| 410 | disown $! 2>/dev/null || true | ||
| 403 | else | 411 | else |
| 404 | # Ephemeral mode: capture guest console (hvc0) to log file | 412 | # Ephemeral mode: capture guest console (hvc0) to log file |
| 405 | # so the monitoring loop in vrunner.sh can see output markers | 413 | # so the monitoring loop in vrunner.sh can see output markers. |
| 406 | stdbuf -oL xl console "$HV_DOMNAME" >> "$log_file" 2>&1 & | 414 | # Detach stdin so the background reader doesn't hold the caller's |
| 415 | # fd 0 open. | ||
| 416 | stdbuf -oL xl console "$HV_DOMNAME" </dev/null >> "$log_file" 2>&1 & | ||
| 407 | _XEN_CONSOLE_PID=$! | 417 | _XEN_CONSOLE_PID=$! |
| 408 | log "DEBUG" "Console capture started (PID: $_XEN_CONSOLE_PID)" | 418 | log "DEBUG" "Console capture started (PID: $_XEN_CONSOLE_PID)" |
| 409 | fi | 419 | fi |
diff --git a/recipes-containers/vcontainer/files/vrunner.sh b/recipes-containers/vcontainer/files/vrunner.sh index f1fb4d2b..1744245a 100755 --- a/recipes-containers/vcontainer/files/vrunner.sh +++ b/recipes-containers/vcontainer/files/vrunner.sh | |||
| @@ -1365,7 +1365,18 @@ if [ "$DAEMON_MODE" = "start" ]; then | |||
| 1365 | # Set up port forwards via backend (e.g., iptables for Xen) | 1365 | # Set up port forwards via backend (e.g., iptables for Xen) |
| 1366 | hv_setup_port_forwards | 1366 | hv_setup_port_forwards |
| 1367 | 1367 | ||
| 1368 | # Start host-side idle watchdog if timeout is set | 1368 | # Start host-side idle watchdog if timeout is set. |
| 1369 | # | ||
| 1370 | # The watchdog is a long-running background subshell that outlives | ||
| 1371 | # vrunner.sh itself. It MUST fully detach from the invoking shell's | ||
| 1372 | # stdio: when the caller (e.g. the vdkr CLI, or a test harness that | ||
| 1373 | # wraps vdkr in subprocess.run(capture_output=True)) reads | ||
| 1374 | # stdout/stderr via pipes, any inherited write-end fd in the | ||
| 1375 | # watchdog keeps those pipes open and blocks the caller's | ||
| 1376 | # communicate()/read until the daemon is stopped (up to | ||
| 1377 | # IDLE_TIMEOUT, default 30 minutes). Redirect all three fds so the | ||
| 1378 | # watchdog holds no descriptors from the caller, and disown it so | ||
| 1379 | # the shell's job table doesn't retain it either. | ||
| 1369 | if [ "$IDLE_TIMEOUT" -gt 0 ] 2>/dev/null; then | 1380 | if [ "$IDLE_TIMEOUT" -gt 0 ] 2>/dev/null; then |
| 1370 | ACTIVITY_FILE="$DAEMON_SOCKET_DIR/activity" | 1381 | ACTIVITY_FILE="$DAEMON_SOCKET_DIR/activity" |
| 1371 | touch "$ACTIVITY_FILE" | 1382 | touch "$ACTIVITY_FILE" |
| @@ -1399,7 +1410,8 @@ if [ "$DAEMON_MODE" = "start" ]; then | |||
| 1399 | exit 0 | 1410 | exit 0 |
| 1400 | fi | 1411 | fi |
| 1401 | done | 1412 | done |
| 1402 | ) & | 1413 | ) </dev/null >/dev/null 2>&1 & |
| 1414 | disown $! 2>/dev/null || true | ||
| 1403 | log "DEBUG" "Started host-side idle watchdog (timeout: ${IDLE_TIMEOUT}s)" | 1415 | log "DEBUG" "Started host-side idle watchdog (timeout: ${IDLE_TIMEOUT}s)" |
| 1404 | fi | 1416 | fi |
| 1405 | 1417 | ||
