Add vsock as a second QGA transport alongside the virtio-serial channel.
It's attempted when there's no CONNECTED virtio-serial channel.

Failed vsock connects retry on a capped exponential backoff (2s..60s,
giving up after ~2 min), reset by any lifecycle event (channel-ready,
replug, restart).

priv->agentIsVsock specifies the type of the currently live transport,
so the EOF, channel-state and unplug paths know which transport is
dying.

Signed-off-by: Polina Vishneva <[email protected]>
---
 src/hypervisor/qemu_agent.c |  73 +++++----
 src/hypervisor/qemu_agent.h |   9 ++
 src/libvirt_private.syms    |   1 +
 src/qemu/qemu_domain.c      |  19 ++-
 src/qemu/qemu_domain.h      |   6 +
 src/qemu/qemu_driver.c      |  13 +-
 src/qemu/qemu_hotplug.c     |   6 +
 src/qemu/qemu_process.c     | 290 ++++++++++++++++++++++++++++++++----
 8 files changed, 357 insertions(+), 60 deletions(-)

diff --git a/src/hypervisor/qemu_agent.c b/src/hypervisor/qemu_agent.c
index e549947fbf..00bd44b52b 100644
--- a/src/hypervisor/qemu_agent.c
+++ b/src/hypervisor/qemu_agent.c
@@ -592,52 +592,37 @@ qemuAgentIO(GSocket *socket G_GNUC_UNUSED,
 
 
 qemuAgent *
-qemuAgentOpen(virDomainObj *vm,
-              const virDomainChrSourceDef *config,
-              GMainContext *context,
-              qemuAgentCallbacks *cb,
-              int timeout)
+qemuAgentOpenFd(virDomainObj *vm,
+                int fd,
+                GMainContext *context,
+                qemuAgentCallbacks *cb,
+                int timeout)
 {
-    qemuAgent *agent;
+    qemuAgent *agent = NULL;
     g_autoptr(GError) gerr = NULL;
 
     if (!cb || !cb->eofNotify) {
         virReportError(VIR_ERR_INTERNAL_ERROR, "%s",
                        _("EOF notify callback must be supplied"));
-        return NULL;
+        goto error;
     }
 
     if (qemuAgentInitialize() < 0)
-        return NULL;
+        goto error;
 
     if (!(agent = virObjectLockableNew(qemuAgentClass)))
-        return NULL;
+        goto error;
 
     agent->timeout = timeout;
-    agent->fd = -1;
+    agent->fd = fd;
     if (virCondInit(&agent->notify) < 0) {
         virReportSystemError(errno, "%s",
                              _("cannot initialize agent condition"));
-        virObjectUnref(agent);
-        return NULL;
+        goto error;
     }
     agent->vm = virObjectRef(vm);
     agent->cb = cb;
 
-    if (config->type != VIR_DOMAIN_CHR_TYPE_UNIX) {
-        virReportError(VIR_ERR_INTERNAL_ERROR,
-                       _("unable to handle agent type: %1$s"),
-                       virDomainChrTypeToString(config->type));
-        goto cleanup;
-    }
-
-    virObjectUnlock(vm);
-    agent->fd = qemuAgentOpenUnix(config->data.nix.path);
-    virObjectLock(vm);
-
-    if (agent->fd == -1)
-        goto cleanup;
-
     agent->context = g_main_context_ref(context);
 
     agent->socket = g_socket_new_from_fd(agent->fd, &gerr);
@@ -645,7 +630,8 @@ qemuAgentOpen(virDomainObj *vm,
         virReportError(VIR_ERR_INTERNAL_ERROR,
                        _("Unable to create socket object: %1$s"),
                        gerr->message);
-        goto cleanup;
+        qemuAgentClose(agent);
+        return NULL;
     }
 
     qemuAgentRegister(agent);
@@ -655,12 +641,41 @@ qemuAgentOpen(virDomainObj *vm,
 
     return agent;
 
- cleanup:
-    qemuAgentClose(agent);
+ error:
+    /* qemuAgentDispose() doesn't touch agent->fd, so close it here */
+    VIR_FORCE_CLOSE(fd);
+    virObjectUnref(agent);
     return NULL;
 }
 
 
+qemuAgent *
+qemuAgentOpen(virDomainObj *vm,
+              const virDomainChrSourceDef *config,
+              GMainContext *context,
+              qemuAgentCallbacks *cb,
+              int timeout)
+{
+    int fd;
+
+    if (config->type != VIR_DOMAIN_CHR_TYPE_UNIX) {
+        virReportError(VIR_ERR_INTERNAL_ERROR,
+                       _("unable to handle agent type: %1$s"),
+                       virDomainChrTypeToString(config->type));
+        return NULL;
+    }
+
+    virObjectUnlock(vm);
+    fd = qemuAgentOpenUnix(config->data.nix.path);
+    virObjectLock(vm);
+
+    if (fd == -1)
+        return NULL;
+
+    return qemuAgentOpenFd(vm, fd, context, cb, timeout);
+}
+
+
 static void
 qemuAgentNotifyCloseLocked(qemuAgent *agent)
 {
diff --git a/src/hypervisor/qemu_agent.h b/src/hypervisor/qemu_agent.h
index 3dbc3baec1..ce75ad2829 100644
--- a/src/hypervisor/qemu_agent.h
+++ b/src/hypervisor/qemu_agent.h
@@ -24,6 +24,9 @@
 #include "internal.h"
 #include "domain_conf.h"
 
+/* Fixed vsock port the guest agent listens on by convention */
+#define QEMU_AGENT_VSOCK_PORT 1024
+
 typedef struct _qemuAgent qemuAgent;
 
 typedef struct _qemuAgentCallbacks qemuAgentCallbacks;
@@ -41,6 +44,12 @@ qemuAgent *qemuAgentOpen(virDomainObj *vm,
                            qemuAgentCallbacks *cb,
                            int timeout);
 
+qemuAgent *qemuAgentOpenFd(virDomainObj *vm,
+                           int fd,
+                           GMainContext *context,
+                           qemuAgentCallbacks *cb,
+                           int timeout);
+
 void qemuAgentClose(qemuAgent *mon);
 
 void qemuAgentNotifyClose(qemuAgent *mon);
diff --git a/src/libvirt_private.syms b/src/libvirt_private.syms
index 6a9cd34134..95db6a885d 100644
--- a/src/libvirt_private.syms
+++ b/src/libvirt_private.syms
@@ -1735,6 +1735,7 @@ qemuAgentGetVCPUs;
 qemuAgentNotifyClose;
 qemuAgentNotifyEvent;
 qemuAgentOpen;
+qemuAgentOpenFd;
 qemuAgentSetResponseTimeout;
 qemuAgentSetTime;
 qemuAgentSetUserPassword;
diff --git a/src/qemu/qemu_domain.c b/src/qemu/qemu_domain.c
index 8539bb72fa..9560ae78ad 100644
--- a/src/qemu/qemu_domain.c
+++ b/src/qemu/qemu_domain.c
@@ -1980,6 +1980,22 @@ qemuDomainObjPrivateDataClear(qemuDomainObjPrivate *priv)
     priv->iommufdState = false;
 
     g_clear_pointer(&priv->memoryBackingDir, g_free);
+
+    qemuDomainCancelAgentVsockReconnect(priv);
+    priv->agentIsVsock = false;
+}
+
+
+void
+qemuDomainCancelAgentVsockReconnect(qemuDomainObjPrivate *priv)
+{
+    priv->agentVsockReconnectAttempts = 0;
+
+    if (!priv->agentVsockReconnectTimer)
+        return;
+
+    g_source_destroy(priv->agentVsockReconnectTimer);
+    g_clear_pointer(&priv->agentVsockReconnectTimer, g_source_unref);
 }
 
 
@@ -7417,7 +7433,8 @@ qemuDomainAgentAvailable(virDomainObj *vm,
         return false;
     }
     if (!priv->agent) {
-        if (qemuFindAgentConfig(vm->def)) {
+        if (qemuFindAgentConfig(vm->def) ||
+            qemuDomainVsockHasGuestCid(vm->def)) {
             if (reportError) {
                 virReportError(VIR_ERR_AGENT_UNRESPONSIVE, "%s",
                                _("QEMU guest agent is not connected"));
diff --git a/src/qemu/qemu_domain.h b/src/qemu/qemu_domain.h
index e994776996..a3d8fa7a86 100644
--- a/src/qemu/qemu_domain.h
+++ b/src/qemu/qemu_domain.h
@@ -120,6 +120,10 @@ struct _qemuDomainObjPrivate {
 
     qemuAgent *agent;
     bool agentError;
+    bool agentIsVsock;
+
+    GSource *agentVsockReconnectTimer;
+    unsigned int agentVsockReconnectAttempts;
 
     bool beingDestroyed;
     char *pidfile;
@@ -807,6 +811,8 @@ bool qemuDomainCheckABIStability(virQEMUDriver *driver,
 
 bool qemuDomainVsockHasGuestCid(const virDomainDef *def);
 
+void qemuDomainCancelAgentVsockReconnect(qemuDomainObjPrivate *priv);
+
 bool qemuDomainAgentAvailable(virDomainObj *vm,
                               bool reportError);
 
diff --git a/src/qemu/qemu_driver.c b/src/qemu/qemu_driver.c
index 0edc568bd7..a4a30627d4 100644
--- a/src/qemu/qemu_driver.c
+++ b/src/qemu/qemu_driver.c
@@ -3926,15 +3926,20 @@ processSerialChangedEvent(virQEMUDriver *driver,
     if (STREQ_NULLABLE(dev.data.chr->target.name, "org.qemu.guest_agent.0")) {
         virObjectEvent *agentEvent = NULL;
         if (newstate == VIR_DOMAIN_CHR_DEVICE_STATE_CONNECTED) {
+            qemuDomainCancelAgentVsockReconnect(priv);
             if (qemuConnectAgent(driver, vm) < 0)
                 goto endjob;
         } else {
-            qemuProcessResetAgent(vm);
+            if (!priv->agentIsVsock)
+                qemuProcessResetAgent(vm);
         }
 
-        agentEvent = virDomainEventAgentLifecycleNewFromObj(vm, newstate,
-                                                            
VIR_CONNECT_DOMAIN_EVENT_AGENT_LIFECYCLE_REASON_CHANNEL);
-        virObjectEventStateQueue(driver->domainEventState, agentEvent);
+        if ((newstate == VIR_DOMAIN_CHR_DEVICE_STATE_DISCONNECTED && 
!priv->agent) ||
+            (newstate == VIR_DOMAIN_CHR_DEVICE_STATE_CONNECTED && 
priv->agent)) {
+            agentEvent = virDomainEventAgentLifecycleNewFromObj(vm, newstate,
+                                                                
VIR_CONNECT_DOMAIN_EVENT_AGENT_LIFECYCLE_REASON_CHANNEL);
+            virObjectEventStateQueue(driver->domainEventState, agentEvent);
+        }
     }
 
     /* we deliberately allow for goto endjob to skip generic event emission
diff --git a/src/qemu/qemu_hotplug.c b/src/qemu/qemu_hotplug.c
index 5be567b510..8018b5d3c5 100644
--- a/src/qemu/qemu_hotplug.c
+++ b/src/qemu/qemu_hotplug.c
@@ -5467,9 +5467,15 @@ static int
 qemuDomainRemoveVsockDevice(virDomainObj *vm,
                             virDomainVsockDef *dev)
 {
+    qemuDomainObjPrivate *priv = vm->privateData;
+
     VIR_DEBUG("Removing vsock device %s from domain %p %s",
               dev->info.alias, vm, vm->def->name);
 
+    qemuDomainCancelAgentVsockReconnect(priv);
+    if (priv->agentIsVsock && priv->agent)
+        qemuProcessResetAgent(vm);
+
     qemuDomainReleaseDeviceAddress(vm, &dev->info);
     g_clear_pointer(&vm->def->vsock, virDomainVsockDefFree);
     return 0;
diff --git a/src/qemu/qemu_process.c b/src/qemu/qemu_process.c
index 77d9880d14..1d94dc4673 100644
--- a/src/qemu/qemu_process.c
+++ b/src/qemu/qemu_process.c
@@ -140,16 +140,123 @@ qemuProcessRemoveDomainStatus(virQEMUDriver *driver,
 }
 
 
+#define QEMU_AGENT_VSOCK_RECONNECT_MIN_MS       2000  /* initial backoff */
+#define QEMU_AGENT_VSOCK_RECONNECT_MAX_MS       60000 /* interval cap */
+#define QEMU_AGENT_VSOCK_RECONNECT_MAX_ATTEMPTS 6     /* give up (~2 min) */
+
+
+static int
+qemuChannelOpenVsock(virQEMUDriver *driver,
+                     virDomainObj *vm,
+                     unsigned int cid)
+{
+    int fd;
+
+    if (qemuSecuritySetDaemonSocketLabel(driver->securityManager, vm->def) < 
0) {
+        VIR_ERROR(_("Failed to set security context for vsock agent for %1$s"),
+                  vm->def->name);
+        return -1;
+    }
+
+    virObjectUnlock(vm);
+    fd = virVsockConnectQuiet(cid, QEMU_AGENT_VSOCK_PORT);
+    virObjectLock(vm);
+
+    if (qemuSecurityClearSocketLabel(driver->securityManager, vm->def) < 0)
+        VIR_ERROR(_("Failed to clear security context for vsock agent for 
%1$s"),
+                  vm->def->name);
+
+    return fd;
+}
+
+
+static gboolean qemuProcessAgentVsockReconnectTimer(gpointer user_data);
+static int qemuConnectAgentVsock(virQEMUDriver *driver, virDomainObj *vm);
+
+
+static void
+qemuProcessScheduleAgentVsockReconnect(virDomainObj *vm)
+{
+    qemuDomainObjPrivate *priv = vm->privateData;
+    unsigned int interval;
+
+    if (priv->agentVsockReconnectTimer || !priv->eventThread)
+        return;
+
+    if (priv->agentVsockReconnectAttempts >= 
QEMU_AGENT_VSOCK_RECONNECT_MAX_ATTEMPTS) {
+        VIR_DEBUG("Giving up vsock QGA reconnect for %s after %u attempts",
+                  vm->def->name, priv->agentVsockReconnectAttempts);
+        return;
+    }
+
+    interval = QEMU_AGENT_VSOCK_RECONNECT_MIN_MS << 
priv->agentVsockReconnectAttempts;
+    if (interval > QEMU_AGENT_VSOCK_RECONNECT_MAX_MS)
+        interval = QEMU_AGENT_VSOCK_RECONNECT_MAX_MS;
+    priv->agentVsockReconnectAttempts++;
+
+    VIR_DEBUG("Scheduling vsock QGA reconnect for %s in %u ms (attempt %u)",
+              vm->def->name, interval, priv->agentVsockReconnectAttempts);
+
+    priv->agentVsockReconnectTimer =
+        g_timeout_source_new(interval);
+    g_source_set_callback(priv->agentVsockReconnectTimer,
+                          qemuProcessAgentVsockReconnectTimer,
+                          virObjectRef(vm),
+                          (GDestroyNotify)virObjectUnref);
+    g_source_attach(priv->agentVsockReconnectTimer,
+                    virEventThreadGetContext(priv->eventThread));
+}
+
+
+static gboolean
+qemuProcessAgentVsockReconnectTimer(gpointer user_data)
+{
+    virDomainObj *vm = user_data;
+    qemuDomainObjPrivate *priv;
+
+    virObjectLock(vm);
+    priv = vm->privateData;
+
+    /* A concurrent cancel may have already destroyed and cleared the source
+     * while we blocked on the lock; drop our ref only if it's still ours. */
+    if (priv->agentVsockReconnectTimer)
+        g_clear_pointer(&priv->agentVsockReconnectTimer, g_source_unref);
+
+    if (!priv->beingDestroyed && virDomainObjIsActive(vm) &&
+        !priv->agent && qemuDomainVsockHasGuestCid(vm->def))
+        ignore_value(qemuConnectAgentVsock(priv->driver, vm));
+
+    virObjectUnlock(vm);
+    return G_SOURCE_REMOVE;
+}
+
+
 void
 qemuProcessResetAgent(virDomainObj *vm)
 {
     qemuDomainObjPrivate *priv = vm->privateData;
 
     g_clear_pointer(&priv->agent, qemuAgentClose);
+    priv->agentIsVsock = false;
     priv->agentError = false;
 }
 
 
+/* The virtio-serial agent lifecycle is reported from the channel event;
+ * vsock has no such event, so emit it from the connect/EOF paths instead. */
+static void
+qemuProcessEmitVsockAgentLifecycle(virQEMUDriver *driver,
+                                   virDomainObj *vm,
+                                   virDomainChrDeviceState newstate)
+{
+    virObjectEvent *event;
+
+    event = virDomainEventAgentLifecycleNewFromObj(vm, newstate,
+                VIR_CONNECT_DOMAIN_EVENT_AGENT_LIFECYCLE_REASON_CHANNEL);
+    virObjectEventStateQueue(driver->domainEventState, event);
+}
+
+
 /*
  * This is a callback registered with a qemuAgent *instance,
  * and to be invoked when the agent console hits an end of file
@@ -161,6 +268,7 @@ qemuProcessHandleAgentEOF(qemuAgent *agent,
                           virDomainObj *vm)
 {
     qemuDomainObjPrivate *priv;
+    bool wasVsock;
 
     virObjectLock(vm);
     VIR_DEBUG("Received EOF from agent on %p '%s'", vm, vm->def->name);
@@ -172,14 +280,31 @@ qemuProcessHandleAgentEOF(qemuAgent *agent,
         goto unlock;
     }
 
+    /* Stale EOF from an instance we already replaced. */
+    if (priv->agent != agent) {
+        VIR_DEBUG("Agent EOF from stale instance %p (current %p)",
+                  agent, priv->agent);
+        qemuAgentClose(agent);
+        goto unlock;
+    }
+
     if (priv->beingDestroyed) {
         VIR_DEBUG("Domain is being destroyed, agent EOF is expected");
         goto unlock;
     }
 
-    qemuAgentClose(agent);
-    priv->agent = NULL;
-    priv->agentError = false;
+    wasVsock = priv->agentIsVsock;
+
+    qemuProcessResetAgent(vm);
+
+    if (wasVsock) {
+        qemuProcessEmitVsockAgentLifecycle(priv->driver, vm,
+                                           
VIR_DOMAIN_CHR_DEVICE_STATE_DISCONNECTED);
+
+        /* The timer is never set while we hold an agent, so no cancel needed. 
*/
+        if (qemuDomainVsockHasGuestCid(vm->def))
+            qemuProcessScheduleAgentVsockReconnect(vm);
+    }
 
     virObjectUnlock(vm);
     return;
@@ -219,35 +344,31 @@ static qemuAgentCallbacks agentCallbacks = {
 };
 
 
-int
-qemuConnectAgent(virQEMUDriver *driver, virDomainObj *vm)
+/*
+ * Connect agent over the UNIX virtio-serial channel.
+ *
+ * Writes vm->privateData->agentError on failure.
+ *
+ * Returns: -1 if the domain is dead, 0 otherwise.
+ */
+static int
+qemuConnectAgentUnix(virQEMUDriver *driver, virDomainObj *vm)
 {
     qemuDomainObjPrivate *priv = vm->privateData;
     qemuAgent *agent = NULL;
     virDomainChrDef *config = qemuFindAgentConfig(vm->def);
 
-    if (!config)
-        return 0;
-
-    if (priv->agent)
-        return 0;
-
-    if (config->state != VIR_DOMAIN_CHR_DEVICE_STATE_CONNECTED) {
-        VIR_DEBUG("Deferring connecting to guest agent");
-        return 0;
-    }
-
     if (qemuSecuritySetDaemonSocketLabel(driver->securityManager, vm->def) < 
0) {
         VIR_ERROR(_("Failed to set security context for agent for %1$s"),
                   vm->def->name);
-        goto cleanup;
+        goto error;
     }
 
     agent = qemuAgentOpen(vm,
                           config->source,
                           virEventThreadGetContext(priv->eventThread),
                           &agentCallbacks,
-                          QEMU_DOMAIN_PRIVATE(vm)->agentTimeout);
+                          priv->agentTimeout);
 
     if (!virDomainObjIsActive(vm)) {
         qemuAgentClose(agent);
@@ -260,20 +381,120 @@ qemuConnectAgent(virQEMUDriver *driver, virDomainObj *vm)
         VIR_ERROR(_("Failed to clear security context for agent for %1$s"),
                   vm->def->name);
         qemuAgentClose(agent);
-        goto cleanup;
+        goto error;
+    }
+
+    if (!agent)
+        goto error;
+
+    /* Another caller may have connected while the domain was unlocked. */
+    if (priv->agent) {
+        qemuAgentClose(agent);
+        return 0;
     }
 
     priv->agent = agent;
-    if (!priv->agent)
-        VIR_INFO("Failed to connect agent for %s", vm->def->name);
+    priv->agentIsVsock = false;
+    priv->agentError = false;
+    return 0;
 
- cleanup:
-    if (!priv->agent) {
-        VIR_WARN("Cannot connect to QEMU guest agent for %s", vm->def->name);
-        priv->agentError = true;
-        virResetLastError();
+ error:
+    VIR_WARN("Cannot connect to QEMU guest agent for %s", vm->def->name);
+    priv->agentError = true;
+    virResetLastError();
+    return 0;
+}
+
+
+/*
+ * Connect agent over vsock.
+ *
+ * Sets the reconnect timer on failure.
+ *
+ * Returns: -1 if the domain is dead, 0 otherwise.
+ */
+static int
+qemuConnectAgentVsock(virQEMUDriver *driver, virDomainObj *vm)
+{
+    qemuDomainObjPrivate *priv = vm->privateData;
+    qemuAgent *agent = NULL;
+    unsigned int cid = vm->def->vsock->guest_cid;
+    int fd;
+
+    fd = qemuChannelOpenVsock(driver, vm, cid);
+
+    if (!virDomainObjIsActive(vm)) {
+        VIR_FORCE_CLOSE(fd);
+        return -1;
     }
 
+    /* Another caller may have connected while the domain was unlocked. */
+    if (priv->agent) {
+        VIR_FORCE_CLOSE(fd);
+        return 0;
+    }
+
+    /* The vsock device may have been unplugged while unlocked. */
+    if (!vm->def->vsock) {
+        VIR_FORCE_CLOSE(fd);
+        return 0;
+    }
+
+    if (fd < 0) {
+        VIR_DEBUG("vsock QGA connect failed for %s", vm->def->name);
+        goto retry;
+    }
+
+    agent = qemuAgentOpenFd(vm, fd,
+                            virEventThreadGetContext(priv->eventThread),
+                            &agentCallbacks,
+                            priv->agentTimeout);
+    if (agent == NULL) {
+        VIR_WARN("Cannot open vsock QGA for %s", vm->def->name);
+        goto retry;
+    }
+
+    priv->agent = agent;
+    priv->agentIsVsock = true;
+    priv->agentError = false;
+    qemuDomainCancelAgentVsockReconnect(priv);
+    qemuProcessEmitVsockAgentLifecycle(driver, vm,
+                                       VIR_DOMAIN_CHR_DEVICE_STATE_CONNECTED);
+    VIR_DEBUG("Connected to vsock QGA cid=%u for %s", cid, vm->def->name);
+    return 0;
+
+ retry:
+    virResetLastError();
+    qemuProcessScheduleAgentVsockReconnect(vm);
+    return 0;
+}
+
+
+/*
+ * Connect agent.
+ *
+ * Use the virtio-serial channel if it's CONNECTED.
+ * Otherwise, if a usable CID is configured, try vsock with exponential 
backoff.
+ *
+ * Returns: -1 if the domain is dead, 0 otherwise.
+ */
+int
+qemuConnectAgent(virQEMUDriver *driver, virDomainObj *vm)
+{
+    qemuDomainObjPrivate *priv = vm->privateData;
+    virDomainChrDef *config = qemuFindAgentConfig(vm->def);
+    bool unix_ready = config &&
+        config->state == VIR_DOMAIN_CHR_DEVICE_STATE_CONNECTED;
+
+    if (priv->agent)
+        return 0;
+
+    if (unix_ready)
+        return qemuConnectAgentUnix(driver, vm);
+
+    if (qemuDomainVsockHasGuestCid(vm->def))
+        return qemuConnectAgentVsock(driver, vm);
+
     return 0;
 }
 
@@ -448,6 +669,14 @@ qemuProcessHandleReset(qemuMonitor *mon G_GNUC_UNUSED,
     if (priv->agent)
         qemuAgentNotifyEvent(priv->agent, QEMU_AGENT_EVENT_RESET);
 
+    /* A rebooting guest brings its agent back up; restart vsock QGA reconnect
+     * from scratch in case we'd already given up.
+     * Skip if we're tearing down: the timer would outlive the event thread. */
+    if (!priv->beingDestroyed && !priv->agent && 
qemuDomainVsockHasGuestCid(vm->def)) {
+        qemuDomainCancelAgentVsockReconnect(priv);
+        qemuProcessScheduleAgentVsockReconnect(vm);
+    }
+
     qemuDomainSetFakeReboot(vm, false);
     qemuDomainSaveStatus(vm);
 
@@ -9375,6 +9604,10 @@ qemuProcessStop(virDomainObj *vm,
                  virDomainAsyncJobTypeToString(asyncJob));
     }
 
+    /* Drop the timer before the active-check: Stop can goto endjob early, and 
a
+     * live timer pins a vm ref and may fire after the event thread dies. */
+    qemuDomainCancelAgentVsockReconnect(priv);
+
     if (!virDomainObjIsActive(vm)) {
         VIR_DEBUG("VM '%s' not active", vm->def->name);
         goto endjob;
@@ -9409,6 +9642,11 @@ qemuProcessStop(virDomainObj *vm,
         virObjectUnlock(vm);
         virEventThreadStop(priv->eventThread);
         virObjectLock(vm);
+
+        /* The event thread is joined now, so no reconnect callback is running.
+         * Drop any timer one re-armed in the unlock window above; otherwise it
+         * would pin a vm ref forever. */
+        qemuDomainCancelAgentVsockReconnect(priv);
     }
 
     qemuProcessResetAgent(vm);
-- 
2.54.0

Reply via email to