Blame SOURCES/kvm-memory-Fix-qemu-crash-on-starting-dirty-log-twice-wi.patch

dc1fe0
From b3ed8e344c733bc8c2223c1b9e424a9fbcea56d4 Mon Sep 17 00:00:00 2001
dc1fe0
From: Peter Xu <peterx@redhat.com>
dc1fe0
Date: Mon, 7 Feb 2022 20:30:19 +0800
dc1fe0
Subject: [PATCH 6/6] memory: Fix qemu crash on starting dirty log twice with
dc1fe0
 stopped VM
dc1fe0
dc1fe0
RH-Author: Peter Xu <peterx@redhat.com>
dc1fe0
RH-MergeRequest: 77: memory: Fix qemu crash on continuous migrations of stopped VM
dc1fe0
RH-Commit: [2/2] 98ed2ef6226ec80a1896ebb554015aded0dc0c18 (peterx/qemu-kvm)
dc1fe0
RH-Bugzilla: 2044818
dc1fe0
RH-Acked-by: Paolo Bonzini <None>
dc1fe0
RH-Acked-by: David Hildenbrand <david@redhat.com>
dc1fe0
RH-Acked-by: quintela1 <quintela@redhat.com>
dc1fe0
dc1fe0
QEMU can now easily crash with two continuous migration carried out:
dc1fe0
dc1fe0
(qemu) migrate -d exec:cat>out
dc1fe0
(qemu) migrate_cancel
dc1fe0
(qemu) migrate -d exec:cat>out
dc1fe0
[crash] ../softmmu/memory.c:2782: memory_global_dirty_log_start: Assertion
dc1fe0
`!(global_dirty_tracking & flags)' failed.
dc1fe0
dc1fe0
It's because memory API provides a way to postpone dirty log stop if the VM is
dc1fe0
stopped, and that'll be re-done until the next VM start.  It was added in 2017
dc1fe0
with commit 1931076077 ("migration: optimize the downtime", 2017-08-01).
dc1fe0
dc1fe0
However the recent work on allowing dirty tracking to be bitmask broke it,
dc1fe0
which is commit 63b41db4bc ("memory: make global_dirty_tracking a bitmask",
dc1fe0
2021-11-01).
dc1fe0
dc1fe0
The fix proposed in this patch contains two things:
dc1fe0
dc1fe0
  (1) Instead of passing over the flags to postpone stop dirty track, we add a
dc1fe0
      global variable (along with current vmstate_change variable) to record
dc1fe0
      what flags to stop dirty tracking.
dc1fe0
dc1fe0
  (2) When start dirty tracking, instead if remove the vmstate hook directly,
dc1fe0
      we also execute the postponed stop process so that we make sure all the
dc1fe0
      starts and stops will be paired.
dc1fe0
dc1fe0
This procedure is overlooked in the bitmask-ify work in 2021.
dc1fe0
dc1fe0
Cc: Hyman Huang <huangy81@chinatelecom.cn>
dc1fe0
Bugzilla: https://bugzilla.redhat.com/show_bug.cgi?id=2044818
dc1fe0
Fixes: 63b41db4bc ("memory: make global_dirty_tracking a bitmask")
dc1fe0
Signed-off-by: Peter Xu <peterx@redhat.com>
dc1fe0
Message-Id: <20220207123019.27223-1-peterx@redhat.com>
dc1fe0
Signed-off-by: Paolo Bonzini <pbonzini@redhat.com>
dc1fe0
(cherry picked from commit a5c90c61a118027b86155cffdf4fe4e2e9de1020)
dc1fe0
Signed-off-by: Peter Xu <peterx@redhat.com>
dc1fe0
---
dc1fe0
 softmmu/memory.c | 61 +++++++++++++++++++++++++++++++++++-------------
dc1fe0
 1 file changed, 45 insertions(+), 16 deletions(-)
dc1fe0
dc1fe0
diff --git a/softmmu/memory.c b/softmmu/memory.c
dc1fe0
index 81d4bf1454..0311e362ee 100644
dc1fe0
--- a/softmmu/memory.c
dc1fe0
+++ b/softmmu/memory.c
dc1fe0
@@ -2769,19 +2769,32 @@ void memory_global_after_dirty_log_sync(void)
dc1fe0
     MEMORY_LISTENER_CALL_GLOBAL(log_global_after_sync, Forward);
dc1fe0
 }
dc1fe0
 
dc1fe0
+/*
dc1fe0
+ * Dirty track stop flags that are postponed due to VM being stopped.  Should
dc1fe0
+ * only be used within vmstate_change hook.
dc1fe0
+ */
dc1fe0
+static unsigned int postponed_stop_flags;
dc1fe0
 static VMChangeStateEntry *vmstate_change;
dc1fe0
+static void memory_global_dirty_log_stop_postponed_run(void);
dc1fe0
 
dc1fe0
 void memory_global_dirty_log_start(unsigned int flags)
dc1fe0
 {
dc1fe0
-    unsigned int old_flags = global_dirty_tracking;
dc1fe0
+    unsigned int old_flags;
dc1fe0
+
dc1fe0
+    assert(flags && !(flags & (~GLOBAL_DIRTY_MASK)));
dc1fe0
 
dc1fe0
     if (vmstate_change) {
dc1fe0
-        qemu_del_vm_change_state_handler(vmstate_change);
dc1fe0
-        vmstate_change = NULL;
dc1fe0
+        /* If there is postponed stop(), operate on it first */
dc1fe0
+        postponed_stop_flags &= ~flags;
dc1fe0
+        memory_global_dirty_log_stop_postponed_run();
dc1fe0
     }
dc1fe0
 
dc1fe0
-    assert(flags && !(flags & (~GLOBAL_DIRTY_MASK)));
dc1fe0
-    assert(!(global_dirty_tracking & flags));
dc1fe0
+    flags &= ~global_dirty_tracking;
dc1fe0
+    if (!flags) {
dc1fe0
+        return;
dc1fe0
+    }
dc1fe0
+
dc1fe0
+    old_flags = global_dirty_tracking;
dc1fe0
     global_dirty_tracking |= flags;
dc1fe0
     trace_global_dirty_changed(global_dirty_tracking);
dc1fe0
 
dc1fe0
@@ -2809,29 +2822,45 @@ static void memory_global_dirty_log_do_stop(unsigned int flags)
dc1fe0
     }
dc1fe0
 }
dc1fe0
 
dc1fe0
+/*
dc1fe0
+ * Execute the postponed dirty log stop operations if there is, then reset
dc1fe0
+ * everything (including the flags and the vmstate change hook).
dc1fe0
+ */
dc1fe0
+static void memory_global_dirty_log_stop_postponed_run(void)
dc1fe0
+{
dc1fe0
+    /* This must be called with the vmstate handler registered */
dc1fe0
+    assert(vmstate_change);
dc1fe0
+
dc1fe0
+    /* Note: postponed_stop_flags can be cleared in log start routine */
dc1fe0
+    if (postponed_stop_flags) {
dc1fe0
+        memory_global_dirty_log_do_stop(postponed_stop_flags);
dc1fe0
+        postponed_stop_flags = 0;
dc1fe0
+    }
dc1fe0
+
dc1fe0
+    qemu_del_vm_change_state_handler(vmstate_change);
dc1fe0
+    vmstate_change = NULL;
dc1fe0
+}
dc1fe0
+
dc1fe0
 static void memory_vm_change_state_handler(void *opaque, bool running,
dc1fe0
                                            RunState state)
dc1fe0
 {
dc1fe0
-    unsigned int flags = (unsigned int)(uintptr_t)opaque;
dc1fe0
     if (running) {
dc1fe0
-        memory_global_dirty_log_do_stop(flags);
dc1fe0
-
dc1fe0
-        if (vmstate_change) {
dc1fe0
-            qemu_del_vm_change_state_handler(vmstate_change);
dc1fe0
-            vmstate_change = NULL;
dc1fe0
-        }
dc1fe0
+        memory_global_dirty_log_stop_postponed_run();
dc1fe0
     }
dc1fe0
 }
dc1fe0
 
dc1fe0
 void memory_global_dirty_log_stop(unsigned int flags)
dc1fe0
 {
dc1fe0
     if (!runstate_is_running()) {
dc1fe0
+        /* Postpone the dirty log stop, e.g., to when VM starts again */
dc1fe0
         if (vmstate_change) {
dc1fe0
-            return;
dc1fe0
+            /* Batch with previous postponed flags */
dc1fe0
+            postponed_stop_flags |= flags;
dc1fe0
+        } else {
dc1fe0
+            postponed_stop_flags = flags;
dc1fe0
+            vmstate_change = qemu_add_vm_change_state_handler(
dc1fe0
+                memory_vm_change_state_handler, NULL);
dc1fe0
         }
dc1fe0
-        vmstate_change = qemu_add_vm_change_state_handler(
dc1fe0
-                                memory_vm_change_state_handler,
dc1fe0
-                                (void *)(uintptr_t)flags);
dc1fe0
         return;
dc1fe0
     }
dc1fe0
 
dc1fe0
-- 
dc1fe0
2.27.0
dc1fe0