|
@@ -54,6 +54,7 @@
|
|
#include "qemu/job.h"
|
|
#include "qemu/job.h"
|
|
#include "qemu/main-loop.h"
|
|
#include "qemu/main-loop.h"
|
|
#include "block/snapshot.h"
|
|
#include "block/snapshot.h"
|
|
|
|
+#include "block/thread-pool.h"
|
|
#include "qemu/cutils.h"
|
|
#include "qemu/cutils.h"
|
|
#include "io/channel-buffer.h"
|
|
#include "io/channel-buffer.h"
|
|
#include "io/channel-file.h"
|
|
#include "io/channel-file.h"
|
|
@@ -131,6 +132,35 @@ static struct mig_cmd_args {
|
|
* generic extendable format with an exception for two old entities.
|
|
* generic extendable format with an exception for two old entities.
|
|
*/
|
|
*/
|
|
|
|
|
|
|
|
+/***********************************************************/
|
|
|
|
+/* Optional load threads pool support */
|
|
|
|
+
|
|
|
|
+static void qemu_loadvm_thread_pool_create(MigrationIncomingState *mis)
|
|
|
|
+{
|
|
|
|
+ assert(!mis->load_threads);
|
|
|
|
+ mis->load_threads = thread_pool_new();
|
|
|
|
+ mis->load_threads_abort = false;
|
|
|
|
+}
|
|
|
|
+
|
|
|
|
+static void qemu_loadvm_thread_pool_destroy(MigrationIncomingState *mis)
|
|
|
|
+{
|
|
|
|
+ qatomic_set(&mis->load_threads_abort, true);
|
|
|
|
+
|
|
|
|
+ bql_unlock(); /* Load threads might be waiting for BQL */
|
|
|
|
+ g_clear_pointer(&mis->load_threads, thread_pool_free);
|
|
|
|
+ bql_lock();
|
|
|
|
+}
|
|
|
|
+
|
|
|
|
+static bool qemu_loadvm_thread_pool_wait(MigrationState *s,
|
|
|
|
+ MigrationIncomingState *mis)
|
|
|
|
+{
|
|
|
|
+ bql_unlock(); /* Let load threads do work requiring BQL */
|
|
|
|
+ thread_pool_wait(mis->load_threads);
|
|
|
|
+ bql_lock();
|
|
|
|
+
|
|
|
|
+ return !migrate_has_error(s);
|
|
|
|
+}
|
|
|
|
+
|
|
/***********************************************************/
|
|
/***********************************************************/
|
|
/* savevm/loadvm support */
|
|
/* savevm/loadvm support */
|
|
|
|
|
|
@@ -2783,16 +2813,68 @@ static int qemu_loadvm_state_setup(QEMUFile *f, Error **errp)
|
|
return 0;
|
|
return 0;
|
|
}
|
|
}
|
|
|
|
|
|
-void qemu_loadvm_state_cleanup(void)
|
|
|
|
|
|
+struct LoadThreadData {
|
|
|
|
+ MigrationLoadThread function;
|
|
|
|
+ void *opaque;
|
|
|
|
+};
|
|
|
|
+
|
|
|
|
+static int qemu_loadvm_load_thread(void *thread_opaque)
|
|
|
|
+{
|
|
|
|
+ struct LoadThreadData *data = thread_opaque;
|
|
|
|
+ MigrationIncomingState *mis = migration_incoming_get_current();
|
|
|
|
+ g_autoptr(Error) local_err = NULL;
|
|
|
|
+
|
|
|
|
+ if (!data->function(data->opaque, &mis->load_threads_abort, &local_err)) {
|
|
|
|
+ MigrationState *s = migrate_get_current();
|
|
|
|
+
|
|
|
|
+ /*
|
|
|
|
+ * Can't set load_threads_abort here since processing of main migration
|
|
|
|
+ * channel data could still be happening, resulting in launching of new
|
|
|
|
+ * load threads.
|
|
|
|
+ */
|
|
|
|
+
|
|
|
|
+ assert(local_err);
|
|
|
|
+
|
|
|
|
+ /*
|
|
|
|
+ * In case of multiple load threads failing which thread error
|
|
|
|
+ * return we end setting is purely arbitrary.
|
|
|
|
+ */
|
|
|
|
+ migrate_set_error(s, local_err);
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ return 0;
|
|
|
|
+}
|
|
|
|
+
|
|
|
|
+void qemu_loadvm_start_load_thread(MigrationLoadThread function,
|
|
|
|
+ void *opaque)
|
|
|
|
+{
|
|
|
|
+ MigrationIncomingState *mis = migration_incoming_get_current();
|
|
|
|
+ struct LoadThreadData *data;
|
|
|
|
+
|
|
|
|
+ /* We only set it from this thread so it's okay to read it directly */
|
|
|
|
+ assert(!mis->load_threads_abort);
|
|
|
|
+
|
|
|
|
+ data = g_new(struct LoadThreadData, 1);
|
|
|
|
+ data->function = function;
|
|
|
|
+ data->opaque = opaque;
|
|
|
|
+
|
|
|
|
+ thread_pool_submit_immediate(mis->load_threads, qemu_loadvm_load_thread,
|
|
|
|
+ data, g_free);
|
|
|
|
+}
|
|
|
|
+
|
|
|
|
+void qemu_loadvm_state_cleanup(MigrationIncomingState *mis)
|
|
{
|
|
{
|
|
SaveStateEntry *se;
|
|
SaveStateEntry *se;
|
|
|
|
|
|
trace_loadvm_state_cleanup();
|
|
trace_loadvm_state_cleanup();
|
|
|
|
+
|
|
QTAILQ_FOREACH(se, &savevm_state.handlers, entry) {
|
|
QTAILQ_FOREACH(se, &savevm_state.handlers, entry) {
|
|
if (se->ops && se->ops->load_cleanup) {
|
|
if (se->ops && se->ops->load_cleanup) {
|
|
se->ops->load_cleanup(se->opaque);
|
|
se->ops->load_cleanup(se->opaque);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
+
|
|
|
|
+ qemu_loadvm_thread_pool_destroy(mis);
|
|
}
|
|
}
|
|
|
|
|
|
/* Return true if we should continue the migration, or false. */
|
|
/* Return true if we should continue the migration, or false. */
|
|
@@ -2943,6 +3025,7 @@ out:
|
|
|
|
|
|
int qemu_loadvm_state(QEMUFile *f)
|
|
int qemu_loadvm_state(QEMUFile *f)
|
|
{
|
|
{
|
|
|
|
+ MigrationState *s = migrate_get_current();
|
|
MigrationIncomingState *mis = migration_incoming_get_current();
|
|
MigrationIncomingState *mis = migration_incoming_get_current();
|
|
Error *local_err = NULL;
|
|
Error *local_err = NULL;
|
|
int ret;
|
|
int ret;
|
|
@@ -2952,6 +3035,8 @@ int qemu_loadvm_state(QEMUFile *f)
|
|
return -EINVAL;
|
|
return -EINVAL;
|
|
}
|
|
}
|
|
|
|
|
|
|
|
+ qemu_loadvm_thread_pool_create(mis);
|
|
|
|
+
|
|
ret = qemu_loadvm_state_header(f);
|
|
ret = qemu_loadvm_state_header(f);
|
|
if (ret) {
|
|
if (ret) {
|
|
return ret;
|
|
return ret;
|
|
@@ -2983,12 +3068,18 @@ int qemu_loadvm_state(QEMUFile *f)
|
|
|
|
|
|
/* When reaching here, it must be precopy */
|
|
/* When reaching here, it must be precopy */
|
|
if (ret == 0) {
|
|
if (ret == 0) {
|
|
- if (migrate_has_error(migrate_get_current())) {
|
|
|
|
|
|
+ if (migrate_has_error(migrate_get_current()) ||
|
|
|
|
+ !qemu_loadvm_thread_pool_wait(s, mis)) {
|
|
ret = -EINVAL;
|
|
ret = -EINVAL;
|
|
} else {
|
|
} else {
|
|
ret = qemu_file_get_error(f);
|
|
ret = qemu_file_get_error(f);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
+ /*
|
|
|
|
+ * Set this flag unconditionally so we'll catch further attempts to
|
|
|
|
+ * start additional threads via an appropriate assert()
|
|
|
|
+ */
|
|
|
|
+ qatomic_set(&mis->load_threads_abort, true);
|
|
|
|
|
|
/*
|
|
/*
|
|
* Try to read in the VMDESC section as well, so that dumping tools that
|
|
* Try to read in the VMDESC section as well, so that dumping tools that
|