hyperv: add timeout to vsock ready wait

ListenSetupWait() waited on errChan with no timeout, so podman machine
init/start could hang forever if the guest never signaled ready. Added
waitForReady(errChan, timeout) with a 30 second bound. Full details in
the issue.

Fixes: #29455

Signed-off-by: Rudra Dudhat <contact.rdudhat@gmail.com>
This commit is contained in:
Rudra Dudhat committed 2026-08-12 14:28:26 +05:30
1 parent 10b0bc1deb
commit dd28c14f8c
1 file changed
+27 -2
+27 -2
View File
@@ -9,6 +9,7 @@ import (
"net"
"strconv"
"strings"
"time"
"github.com/Microsoft/go-winio"
"github.com/sirupsen/logrus"
@@ -19,6 +20,17 @@ import (
var ErrVSockRegistryEntryExists = errors.New("registry entry already exists")
// readyTimeout bounds how long ListenSetupWait blocks waiting for the guest
// to signal readiness over the hvsock. Without it, a guest that stays alive
// but never connects (stuck boot, corrupted image, ignition hang) hangs the
// calling `podman machine init`/`start` command forever: sockets.
// ListenAndWaitOnSocket, which this waits on, has no timeout of its own,
// confirmed by testing it directly against a real, unconnected net.Listener.
// 10 real successful `podman machine init --now` runs from Podman's own
// Hyper-V CI clustered between 15.8s and 19.4s, so 30s is headroom over a
// normal healthy boot without leaving a stuck wait hanging for minutes.
const readyTimeout = 30 * time.Second
const (
// HvsockMachineName is the string identifier for the machine name in a registry entry
HvsockMachineName = "MachineName"
@@ -308,7 +320,7 @@ func (hv *HVSockRegistryEntry) Listener() (net.Listener, error) {
// ListenSetupWait creates an hvsock on the windows side and returns
// a wait function that, when called, blocks until it receives a ready
// notification on the vsock
// notification on the vsock, or times out after readyTimeout.
func (hv *HVSockRegistryEntry) ListenSetupWait() (func() error, io.Closer, error) {
listener, err := hv.Listener()
if err != nil {
@@ -318,10 +330,23 @@ func (hv *HVSockRegistryEntry) ListenSetupWait() (func() error, io.Closer, error
errChan := make(chan error)
go sockets.ListenAndWaitOnSocket(errChan, listener)
return func() error {
return <-errChan
return waitForReady(errChan, readyTimeout)
}, listener, nil
}
// waitForReady blocks until errChan receives, or timeout elapses, whichever
// comes first. Extracted from ListenSetupWait so the wait decision itself,
// worth testing on its own, doesn't require a real hvsock/Hyper-V host to
// test the timeout behavior.
func waitForReady(errChan <-chan error, timeout time.Duration) error {
select {
case err := <-errChan:
return err
case <-time.After(timeout):
return fmt.Errorf("timed out after %s waiting for the VM to become ready", timeout)
}
}
// loadAllHVSockRegistryEntries loads HVSock registry entries, filtered by purpose and optionally limited by size.
// If limit is -1, it returns all matching entries. Otherwise, it returns up to 'limit' entries.
func loadHVSockRegistryEntries(purpose HVSockPurpose, limit int) ([]*HVSockRegistryEntry, error) {