@samitouri / QOSamiQemu / commits / 93c8a97f99

migration: introduce MIGRATION_STATUS_FAILING

When migration connection is broken, the QEMU and libvirtd(8) process on the source side receive TCP connection reset notification. QEMU sets the migration status to FAILED and proceeds to migration_cleanup(). Meanwhile, Libvirtd(8) sends a QMP command to migrate_set_capabilities(). The migration_cleanup() and qmp_migrate_set_capabilities() calls race with each other. When the latter is invoked first, since the migration is not running (FAILED), migration capabilities are reset to false, so during migration_cleanup() the QEMU process crashes with assertion failure. Introduce a new migration status FAILING and use it as an interim status when an error occurs. Once migration_cleanup() is done, it sets the migration status to FAILED. This helps to avoid the above race condition and ensuing failure. Interim status FAILING is set wherever the execution moves towards migration_cleanup(): - postcopy_start() - migration_thread() - migration_cleanup() - multifd_send_setup() - bg_migration_thread() - migration_completion() - migration_detect_error() - bg_migration_completion() - multifd_send_error_propagate() - migration_connect_error_propagate() The migration status finally moves to FAILED and reports an appropriate error to the user. Interim status FAILING is _NOT_ set in the following routines because they do not follow the migration_cleanup() path to the FAILED state: - cpr_exec_cb() - qemu_savevm_state() - postcopy_listen_thread() - process_incoming_migration_co() - multifd_recv_terminate_threads() - migration_channel_process_incoming() Reviewed-by: Peter Xu <peterx@redhat.com> Signed-off-by: Prasad Pandit <pjp@fedoraproject.org> Link: https://lore.kernel.org/qemu-devel/20260224102547.226087-1-ppandit@redhat.com Signed-off-by: Fabiano Rosas <farosas@suse.de>

Prasad Pandit committed Feb 24, 2026 at 15:55 UTC 93c8a97f99d8d03ce0d5f4fecb0e82bf2d1a52b9
5 files changed +32 -19
migration/migration.c
+20 -12
@@ -1016,6 +1016,7 @@ bool migration_is_running(void)
1016 case MIGRATION_STATUS_DEVICE:
1017 case MIGRATION_STATUS_WAIT_UNPLUG:
1018 case MIGRATION_STATUS_CANCELLING:
1019 + case MIGRATION_STATUS_FAILING:
1020 case MIGRATION_STATUS_COLO:
1021 return true;
1022 default:
@@ -1158,6 +1159,7 @@ static void fill_source_migration_info(MigrationInfo *info)
1159 case MIGRATION_STATUS_POSTCOPY_PAUSED:
1160 case MIGRATION_STATUS_POSTCOPY_RECOVER_SETUP:
1161 case MIGRATION_STATUS_POSTCOPY_RECOVER:
1162 + case MIGRATION_STATUS_FAILING:
1163 /* TODO add some postcopy stats */
1164 populate_time_info(info, s);
1165 populate_ram_info(info, s);
@@ -1210,6 +1212,7 @@ static void fill_destination_migration_info(MigrationInfo *info)
1212 case MIGRATION_STATUS_POSTCOPY_PAUSED:
1213 case MIGRATION_STATUS_POSTCOPY_RECOVER:
1214 case MIGRATION_STATUS_FAILED:
1215 + case MIGRATION_STATUS_FAILING:
1216 case MIGRATION_STATUS_COLO:
1217 info->has_status = true;
1218 break;
@@ -1330,6 +1333,9 @@ static void migration_cleanup(MigrationState *s)
1333 if (s->state == MIGRATION_STATUS_CANCELLING) {
1334 migrate_set_state(&s->state, MIGRATION_STATUS_CANCELLING,
1335 MIGRATION_STATUS_CANCELLED);
1336 + } else if (s->state == MIGRATION_STATUS_FAILING) {
1337 + migrate_set_state(&s->state, MIGRATION_STATUS_FAILING,
1338 + MIGRATION_STATUS_FAILED);
1339 }
1340
1341 /*
@@ -1387,7 +1393,7 @@ void migration_connect_error_propagate(MigrationState *s, Error *error)
1393
1394 switch (current) {
1395 case MIGRATION_STATUS_SETUP:
1390 - next = MIGRATION_STATUS_FAILED;
1396 + next = MIGRATION_STATUS_FAILING;
1397 break;
1398
1399 case MIGRATION_STATUS_POSTCOPY_PAUSED:
@@ -1401,9 +1407,10 @@ void migration_connect_error_propagate(MigrationState *s, Error *error)
1407 break;
1408
1409 case MIGRATION_STATUS_CANCELLING:
1410 + case MIGRATION_STATUS_FAILING:
1411 /*
1405 - * Don't move out of CANCELLING, the only valid transition is to
1406 - * CANCELLED, at migration_cleanup().
1412 + * Keep the current state, next transition is to be done
1413 + * in migration_cleanup().
1414 */
1415 break;
1416
@@ -1553,6 +1560,7 @@ bool migration_has_failed(MigrationState *s)
1560 {
1561 return (s->state == MIGRATION_STATUS_CANCELLING ||
1562 s->state == MIGRATION_STATUS_CANCELLED ||
1563 + s->state == MIGRATION_STATUS_FAILING ||
1564 s->state == MIGRATION_STATUS_FAILED);
1565 }
1566
@@ -2479,7 +2487,7 @@ static int postcopy_start(MigrationState *ms, Error **errp)
2487 if (postcopy_preempt_establish_channel(ms)) {
2488 if (ms->state != MIGRATION_STATUS_CANCELLING) {
2489 migrate_set_state(&ms->state, ms->state,
2482 - MIGRATION_STATUS_FAILED);
2490 + MIGRATION_STATUS_FAILING);
2491 }
2492 error_setg(errp, "%s: Failed to establish preempt channel",
2493 __func__);
@@ -2642,7 +2650,7 @@ fail_closefb:
2650 qemu_fclose(fb);
2651 fail:
2652 if (ms->state != MIGRATION_STATUS_CANCELLING) {
2645 - migrate_set_state(&ms->state, ms->state, MIGRATION_STATUS_FAILED);
2653 + migrate_set_state(&ms->state, ms->state, MIGRATION_STATUS_FAILING);
2654 }
2655 bql_unlock();
2656 return -1;
@@ -2833,7 +2841,7 @@ fail:
2841 }
2842
2843 if (s->state != MIGRATION_STATUS_CANCELLING) {
2836 - migrate_set_state(&s->state, s->state, MIGRATION_STATUS_FAILED);
2844 + migrate_set_state(&s->state, s->state, MIGRATION_STATUS_FAILING);
2845 }
2846 }
2847
@@ -2870,7 +2878,7 @@ static void bg_migration_completion(MigrationState *s)
2878
2879 fail:
2880 migrate_set_state(&s->state, current_active_state,
2873 - MIGRATION_STATUS_FAILED);
2881 + MIGRATION_STATUS_FAILING);
2882 }
2883
2884 typedef enum MigThrError {
@@ -3071,7 +3079,7 @@ static MigThrError migration_detect_error(MigrationState *s)
3079 * For precopy (or postcopy with error outside IO, or before dest
3080 * starts), we fail with no time.
3081 */
3074 - migrate_set_state(&s->state, state, MIGRATION_STATUS_FAILED);
3082 + migrate_set_state(&s->state, state, MIGRATION_STATUS_FAILING);
3083 trace_migration_thread_file_err();
3084
3085 /* Time to stop the migration, now. */
@@ -3302,7 +3310,7 @@ static void migration_iteration_finish(MigrationState *s)
3310 migrate_start_colo_process(s);
3311 s->vm_old_state = RUN_STATE_RUNNING;
3312 /* Fallthrough */
3305 - case MIGRATION_STATUS_FAILED:
3313 + case MIGRATION_STATUS_FAILING:
3314 case MIGRATION_STATUS_CANCELLED:
3315 case MIGRATION_STATUS_CANCELLING:
3316 if (!migration_block_activate(&local_err)) {
@@ -3368,7 +3376,7 @@ static void bg_migration_iteration_finish(MigrationState *s)
3376 switch (s->state) {
3377 case MIGRATION_STATUS_COMPLETED:
3378 case MIGRATION_STATUS_ACTIVE:
3371 - case MIGRATION_STATUS_FAILED:
3379 + case MIGRATION_STATUS_FAILING:
3380 case MIGRATION_STATUS_CANCELLED:
3381 case MIGRATION_STATUS_CANCELLING:
3382 break;
@@ -3553,7 +3561,7 @@ static void *migration_thread(void *opaque)
3561 if (ret) {
3562 migrate_error_propagate(s, local_err);
3563 migrate_set_state(&s->state, MIGRATION_STATUS_ACTIVE,
3556 - MIGRATION_STATUS_FAILED);
3564 + MIGRATION_STATUS_FAILING);
3565 goto out;
3566 }
3567
@@ -3745,7 +3753,7 @@ fail:
3753 /* local_err is guaranteed to be set when reaching here */
3754 migrate_error_propagate(s, local_err);
3755 migrate_set_state(&s->state, MIGRATION_STATUS_ACTIVE,
3748 - MIGRATION_STATUS_FAILED);
3756 + MIGRATION_STATUS_FAILING);
3757
3758 done:
3759 bg_migration_iteration_finish(s);
migration/multifd.c
+2 -2
@@ -431,7 +431,7 @@ static void multifd_send_error_propagate(Error *err)
431 s->state == MIGRATION_STATUS_DEVICE ||
432 s->state == MIGRATION_STATUS_ACTIVE) {
433 migrate_set_state(&s->state, s->state,
434 - MIGRATION_STATUS_FAILED);
434 + MIGRATION_STATUS_FAILING);
435 }
436 }
437 }
@@ -986,7 +986,7 @@ bool multifd_send_setup(void)
986
987 err:
988 migrate_set_state(&s->state, MIGRATION_STATUS_SETUP,
989 - MIGRATION_STATUS_FAILED);
989 + MIGRATION_STATUS_FAILING);
990 return false;
991 }
992
qapi/migration.json
+6 -3
@@ -158,7 +158,10 @@
158 #
159 # @completed: migration is finished.
160 #
161 -# @failed: some error occurred during migration process.
161 +# @failing: error occurred during migration, clean-up underway.
162 +# (since 11.0)
163 +#
164 +# @failed: error occurred during migration, clean-up done.
165 #
166 # @colo: VM is in the process of fault tolerance, VM can not get into
167 # this state unless colo capability is enabled for migration.
@@ -181,8 +184,8 @@
184 'data': [ 'none', 'setup', 'cancelling', 'cancelled',
185 'active', 'postcopy-device', 'postcopy-active',
186 'postcopy-paused', 'postcopy-recover-setup',
184 - 'postcopy-recover', 'completed', 'failed', 'colo',
185 - 'pre-switchover', 'device', 'wait-unplug' ] }
187 + 'postcopy-recover', 'completed', 'failing', 'failed',
188 + 'colo', 'pre-switchover', 'device', 'wait-unplug' ] }
189
190 ##
191 # @VfioStats:
tests/qtest/migration/migration-qmp.c
+2 -1
@@ -241,7 +241,8 @@ void wait_for_migration_fail(QTestState *from, bool allow_active)
241 do {
242 status = migrate_query_status(from);
243 bool result = !strcmp(status, "setup") || !strcmp(status, "failed") ||
244 - (allow_active && !strcmp(status, "active"));
244 + (allow_active && !strcmp(status, "active")) ||
245 + !strcmp(status, "failing");
246 if (!result) {
247 fprintf(stderr, "%s: unexpected status status=%s allow_active=%d\n",
248 __func__, status, allow_active);
tests/qtest/migration/precopy-tests.c
+2 -1
@@ -1247,7 +1247,7 @@ void migration_test_add_precopy(MigrationTestEnv *env)
1247 }
1248
1249 /* ensure new status don't go unnoticed */
1250 - assert(MIGRATION_STATUS__MAX == 16);
1250 + assert(MIGRATION_STATUS__MAX == 17);
1251
1252 for (int i = MIGRATION_STATUS_NONE; i < MIGRATION_STATUS__MAX; i++) {
1253 switch (i) {
@@ -1259,6 +1259,7 @@ void migration_test_add_precopy(MigrationTestEnv *env)
1259 case MIGRATION_STATUS_POSTCOPY_PAUSED:
1260 case MIGRATION_STATUS_POSTCOPY_RECOVER_SETUP:
1261 case MIGRATION_STATUS_POSTCOPY_RECOVER:
1262 + case MIGRATION_STATUS_FAILING:
1263 continue;
1264 default:
1265 migration_test_add_suffix("/migration/cancel/src/after/",