[PATCH] Fix deadlock in QEMU close callback APIs

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

 



From: "Daniel P. Berrange" <berrange@xxxxxxxxxx>

There is a lock ordering problem in the QEMU close callback
APIs.

When starting a guest we have a lock on the VM. We then
set a autodestroy callback, which acquires a lock on the
close callbacks.

When running auto-destroy, we obtain a lock on the close
callbacks, then run each callbacks - which obtains a lock
on the VM.

This causes deadlock if anyone tries to start a VM, while
autodestroy is taking place.

The fix is to do autodestroy in 2 phases. First obtain
all the callbacks and remove them from the list under
the close callback lock. Then invoke each callback
from outside the close callback lock.

Signed-off-by: Daniel P. Berrange <berrange@xxxxxxxxxx>
---
 src/qemu/qemu_conf.c  | 106 ++++++++++++++++++++++++++++++++++++++++++--------
 src/util/virnetlink.c |   5 ++-
 2 files changed, 92 insertions(+), 19 deletions(-)

diff --git a/src/qemu/qemu_conf.c b/src/qemu/qemu_conf.c
index 1cd4b7c..f3b09cf 100644
--- a/src/qemu/qemu_conf.c
+++ b/src/qemu/qemu_conf.c
@@ -797,20 +797,37 @@ virQEMUCloseCallbacksGet(virQEMUCloseCallbacksPtr closeCallbacks,
     return cb;
 }
 
+
+typedef struct _virQEMUCloseCallbacksListEntry virQEMUCloseCallbacksListEntry;
+typedef virQEMUCloseCallbacksListEntry *virQEMUCloseCallbacksListEntryPtr;
+struct _virQEMUCloseCallbacksListEntry {
+    unsigned char uuid[VIR_UUID_BUFLEN];
+    virQEMUCloseCallback callback;
+};
+
+
+typedef struct _virQEMUCloseCallbacksList virQEMUCloseCallbacksList;
+typedef virQEMUCloseCallbacksList *virQEMUCloseCallbacksListPtr;
+struct _virQEMUCloseCallbacksList {
+    size_t nentries;
+    virQEMUCloseCallbacksListEntryPtr entries;
+};
+
+
 struct virQEMUCloseCallbacksData {
-    virHashTablePtr list;
-    virQEMUDriverPtr driver;
     virConnectPtr conn;
+    virQEMUCloseCallbacksListPtr list;
+    bool oom;
 };
 
+
 static void
-virQEMUCloseCallbacksRunOne(void *payload,
+virQEMUCloseCallbacksGetOne(void *payload,
                             const void *key,
                             void *opaque)
 {
     struct virQEMUCloseCallbacksData *data = opaque;
     qemuDriverCloseDefPtr closeDef = payload;
-    virDomainObjPtr dom;
     const char *uuidstr = key;
     unsigned char uuid[VIR_UUID_BUFLEN];
 
@@ -823,35 +840,90 @@ virQEMUCloseCallbacksRunOne(void *payload,
     if (data->conn != closeDef->conn || !closeDef->cb)
         return;
 
-    if (!(dom = virDomainObjListFindByUUID(data->driver->domains, uuid))) {
-        VIR_DEBUG("No domain object with UUID %s", uuidstr);
+    if (VIR_EXPAND_N(data->list->entries,
+                     data->list->nentries, 1) < 0) {
+        data->oom = true;
         return;
     }
 
-    dom = closeDef->cb(data->driver, dom, data->conn);
-    if (dom)
-        virObjectUnlock(dom);
+    memcpy(data->list->entries[data->list->nentries - 1].uuid,
+           uuid, VIR_UUID_BUFLEN);
+    data->list->entries[data->list->nentries - 1].callback = closeDef->cb;
+}
+
+
+static virQEMUCloseCallbacksListPtr
+virQEMUCloseCallbacksGetForConn(virQEMUCloseCallbacksPtr closeCallbacks,
+                                virConnectPtr conn)
+{
+    virQEMUCloseCallbacksListPtr list = NULL;
+    struct virQEMUCloseCallbacksData data;
+
+    if (VIR_ALLOC(list) < 0) {
+        virReportOOMError();
+        return NULL;
+    }
+
+    data.conn = conn;
+    data.list = list;
+    data.oom = false;
+
+    virHashForEach(closeCallbacks->list, virQEMUCloseCallbacksGetOne, &data);
 
-    virHashRemoveEntry(data->list, uuid);
+    if (data.oom) {
+        VIR_FREE(list->entries);
+        VIR_FREE(list);
+        virReportOOMError();
+        return NULL;
+    }
+
+    return list;
 }
 
+
 void
 virQEMUCloseCallbacksRun(virQEMUCloseCallbacksPtr closeCallbacks,
                          virConnectPtr conn,
                          virQEMUDriverPtr driver)
 {
-    struct virQEMUCloseCallbacksData data = {
-        .driver = driver,
-        .conn = conn
-    };
+    virQEMUCloseCallbacksListPtr list;
+    size_t i;
 
     VIR_DEBUG("conn=%p", conn);
-    virObjectLock(closeCallbacks);
 
-    data.list = closeCallbacks->list;
-    virHashForEach(data.list, virQEMUCloseCallbacksRunOne, &data);
+    /* We must not hold the lock while running the callbacks,
+     * so first we obtain the list of callbacks, then remove
+     * them all from the hash. At that point we can release
+     * the lock and run the callbacks safely. */
 
+    virObjectLock(closeCallbacks);
+    list = virQEMUCloseCallbacksGetForConn(closeCallbacks, conn);
+    if (!list)
+        return;
+
+    for (i = 0 ; i < list->nentries ; i++) {
+        virHashRemoveEntry(closeCallbacks->list,
+                           list->entries[i].uuid);
+    }
     virObjectUnlock(closeCallbacks);
+
+    for (i = 0 ; i < list->nentries ; i++) {
+        virDomainObjPtr vm;
+
+        if (!(vm = virDomainObjListFindByUUID(driver->domains,
+                                              list->entries[i].uuid))) {
+            char uuidstr[VIR_UUID_STRING_BUFLEN];
+            virUUIDFormat(list->entries[i].uuid, uuidstr);
+            VIR_DEBUG("No domain object with UUID %s", uuidstr);
+            continue;
+        }
+
+        vm = list->entries[i].callback(driver, vm, conn);
+        if (vm)
+            virObjectUnlock(vm);
+    }
+    VIR_FREE(list->entries);
+    VIR_FREE(list);
 }
 
 struct _qemuSharedDiskEntry {
diff --git a/src/util/virnetlink.c b/src/util/virnetlink.c
index 0b36fdc..8b47ede 100644
--- a/src/util/virnetlink.c
+++ b/src/util/virnetlink.c
@@ -335,8 +335,9 @@ virNetlinkEventCallback(int watch,
     if (length == 0)
         return;
     if (length < 0) {
-        virReportSystemError(errno,
-                             "%s", _("nl_recv returned with error"));
+        virReportError(VIR_ERR_INTERNAL_ERROR,
+                       _("nl_recv returned with error: %s"),
+                       nl_geterror(length));
         return;
     }
 
-- 
1.7.11.7

--
libvir-list mailing list
libvir-list@xxxxxxxxxx
https://www.redhat.com/mailman/listinfo/libvir-list


[Index of Archives]     [Virt Tools]     [Libvirt Users]     [Lib OS Info]     [Fedora Users]     [Fedora Desktop]     [Fedora SELinux]     [Big List of Linux Books]     [Yosemite News]     [KDE Users]     [Fedora Tools]