@samitouri / QOSamiQemu / commits / 4e8c4dda97

tests/qtest/migration: Force exit-on-error=false

Some tests can cause QEMU to exit(1) too early while the incoming coroutine has not yielded for a first time yet. This trips ASAN because resources related to dispatching the incoming process will still be allocated in the io/channel.c layer without a straight-forward way for the migration code to clean them up. As an example of one such issue, the UUID validation happens early enough that the temporary socket from qio_net_listener_channel_func() still has an elevated refcount. If it fails, the listener dispatch code never gets to free the resource: Direct leak of 400 byte(s) in 1 object(s) allocated from: #0 0x55e668890a07 in malloc asan_malloc_linux.cpp:68:3 #1 0x7f3c7e2b6648 in g_malloc ../glib/gmem.c:130 #2 0x55e66a8ef05f in object_new_with_type ../qom/object.c:767:15 #3 0x55e66a8ef178 in object_new ../qom/object.c:789:12 #4 0x55e66a93bcc6 in qio_channel_socket_new ../io/channel-socket.c:70:31 #5 0x55e66a93f34f in qio_channel_socket_accept ../io/channel-socket.c:401:12 #6 0x55e66a96752a in qio_net_listener_channel_func ../io/net-listener.c:64:12 #7 0x55e66a94bdac in qio_channel_fd_source_dispatch ../io/channel-watch.c:84:12 #8 0x7f3c7e2adf4b in g_main_dispatch ../glib/gmain.c:3476 #9 0x7f3c7e2adf4b in g_main_context_dispatch_unlocked ../glib/gmain.c:4284 #10 0x7f3c7e2b00c8 in g_main_context_dispatch ../glib/gmain.c:4272 The exit(1) also requires some tests to setup qtest to expect a return code of 1 from the QEMU process. Although we can check migration status changes to be fairly certain where the failure happened, there is always the possibility of QEMU exiting for another reason and the test passing. This happens frequently with sanitizers enabled, but also risks masking issues in the regular build. Stop allowing the incoming migration to exit and instead require the tests to wait for the FAILED state and end QEMU gracefully with qtest_quit. In practice this means setting exit-on-error=false for every incoming migration, changing MIG_TEST_FAIL_DEST_QUIT_ERR to MIG_TEST_FAIL and waiting for a change of state where necessary. With this, the MIG_TEST_FAIL_DEST_QUIT_ERR error result is now unused, remove it. The affected tests are: validate_uuid_error multifd_tcp_cancel dirty_limit precopy_unix_tls_x509_default_host precopy_tcp_tls_no_hostname tcp_tls_x509_mismatch_host dbus_vmstate_missing_src dbus_vmstate_missing_dst Also add a comment to QEMU source explaining that the incoming coroutine might block for a while until it yields as this is the actual root cause of the issue. Reviewed-by: Peter Xu <peterx@redhat.com> Reviewed-by: Prasad Pandit <pjp@fedoraproject.org> Link: https://lore.kernel.org/qemu-devel/20260311213418.16951-6-farosas@suse.de [assert that key doesn't already exists] Signed-off-by: Fabiano Rosas <farosas@suse.de>

Fabiano Rosas committed Mar 11, 2026 at 18:34 UTC 4e8c4dda97adf7e6ec233102ea9f34430dfcc667
8 files changed +31 -23
migration/migration.c
+5
@@ -898,6 +898,11 @@ void migration_start_incoming(void)
898
899 Coroutine *co = qemu_coroutine_create(process_incoming_migration_co, NULL);
900 qemu_coroutine_enter(co);
901 + /*
902 + * This doesn't return right away. The coroutine will run
903 + * unimpeded until its first yield, which may happen as late as
904 + * the force yield at ram_load_precopy().
905 + */
906 }
907
908 int migrate_send_rp_switchover_ack(MigrationIncomingState *mis)
tests/qtest/dbus-vmstate-test.c
+3 -2
@@ -219,8 +219,8 @@ test_dbus_vmstate(Test *test)
219
220 dstaddr = g_strsplit(g_test_dbus_get_bus_address(dstbus), ",", 2);
221 dst_qemu_args =
222 - g_strdup_printf("-object dbus-vmstate,id=dv,addr=%s -incoming %s",
223 - dstaddr[0], uri);
222 + g_strdup_printf("-object dbus-vmstate,id=dv,addr=%s -incoming defer",
223 + dstaddr[0]);
224
225 src_qemu = qtest_init(src_qemu_args);
226 dst_qemu = qtest_init(dst_qemu_args);
@@ -229,6 +229,7 @@ test_dbus_vmstate(Test *test)
229
230 thread = g_thread_new("dbus-vmstate-thread", dbus_vmstate_thread, loop);
231
232 + migrate_incoming_qmp(dst_qemu, uri, NULL, "{}");
233 migrate_qmp(src_qemu, uri, "{}");
234 test->src_qemu = src_qemu;
235 if (test->migrate_fail) {
tests/qtest/migration/framework.c
+1 -4
@@ -576,6 +576,7 @@ static int migrate_postcopy_prepare(QTestState **from_ptr,
576 migrate_prepare_for_dirty_mem(from);
577 qtest_qmp_assert_success(to, "{ 'execute': 'migrate-incoming',"
578 " 'arguments': { "
579 + " 'exit-on-error': false,"
580 " 'channels': [ { 'channel-type': 'main',"
581 " 'addr': { 'transport': 'socket',"
582 " 'type': 'inet',"
@@ -906,10 +907,6 @@ int test_precopy_common(MigrateCommon *args)
907 if (args->result != MIG_TEST_SUCCEED) {
908 bool allow_active = args->result == MIG_TEST_FAIL;
909 wait_for_migration_fail(from, allow_active);
909 -
910 - if (args->result == MIG_TEST_FAIL_DEST_QUIT_ERR) {
911 - qtest_set_expected_status(to, EXIT_FAILURE);
912 - }
910 } else {
911 if (args->live) {
912 /*
tests/qtest/migration/framework.h
-2
@@ -208,8 +208,6 @@ typedef struct {
208 MIG_TEST_SUCCEED = 0,
209 /* This test should fail, dest qemu should keep alive */
210 MIG_TEST_FAIL,
211 - /* This test should fail, dest qemu should fail with abnormal status */
212 - MIG_TEST_FAIL_DEST_QUIT_ERR,
211 /* The QMP command for this migration should fail with an error */
212 MIG_TEST_QMP_ERROR,
213 } result;
tests/qtest/migration/migration-qmp.c
+7
@@ -173,6 +173,13 @@ void migrate_incoming_qmp(QTestState *to, const char *uri, QObject *channels,
173 /* This function relies on the event to work, make sure it's enabled */
174 migrate_set_capability(to, "events", true);
175
176 + /*
177 + * Set the incoming migration to never exit QEMU abruptly during
178 + * the tests. It causes issues when running sanitizers and
179 + * expecting a failure exit code can mask other issues.
180 + */
181 + g_assert(!qdict_haskey(args, "exit-on-error"));
182 + qdict_put_bool(args, "exit-on-error", false);
183 rsp = qtest_qmp(to, "{ 'execute': 'migrate-incoming', 'arguments': %p}",
184 args);
185
tests/qtest/migration/misc-tests.c
+2 -2
@@ -131,7 +131,7 @@ static void do_test_validate_uuid(MigrateStart *args, bool should_fail)
131 g_autofree char *uri = g_strdup_printf("unix:%s/migsocket", tmpfs);
132 QTestState *from, *to;
133
134 - if (migrate_start(&from, &to, uri, args)) {
134 + if (migrate_start(&from, &to, "defer", args)) {
135 return;
136 }
137
@@ -146,10 +146,10 @@ static void do_test_validate_uuid(MigrateStart *args, bool should_fail)
146 /* Wait for the first serial output from the source */
147 wait_for_serial("src_serial");
148
149 + migrate_incoming_qmp(to, uri, NULL, "{}");
150 migrate_qmp(from, to, uri, NULL, "{}");
151
152 if (should_fail) {
152 - qtest_set_expected_status(to, EXIT_FAILURE);
153 wait_for_migration_fail(from, true);
154 } else {
155 wait_for_migration_complete(from);
tests/qtest/migration/precopy-tests.c
+5 -7
@@ -545,8 +545,7 @@ static void test_multifd_tcp_cancel(MigrateCommon *args, bool postcopy_ram)
545 migrate_cancel(from);
546
547 /* Make sure QEMU process "to" exited */
548 - qtest_set_expected_status(to, EXIT_FAILURE);
549 - qtest_wait_qemu(to);
548 + migration_event_wait(to, "failed");
549 qtest_quit(to);
550
551 /*
@@ -634,7 +633,7 @@ static void test_cancel_src_after_cancelled(QTestState *from, QTestState *to,
633 const char *uri, const char *phase,
634 MigrateStart *args)
635 {
637 - migrate_incoming_qmp(to, uri, NULL, "{ 'exit-on-error': false }");
636 + migrate_incoming_qmp(to, uri, NULL, "{}");
637
638 wait_for_serial("src_serial");
639 migrate_ensure_converge(from);
@@ -659,7 +658,7 @@ static void test_cancel_src_after_complete(QTestState *from, QTestState *to,
658 const char *uri, const char *phase,
659 MigrateStart *args)
660 {
662 - migrate_incoming_qmp(to, uri, NULL, "{ 'exit-on-error': false }");
661 + migrate_incoming_qmp(to, uri, NULL, "{}");
662
663 wait_for_serial("src_serial");
664 migrate_ensure_converge(from);
@@ -690,7 +689,7 @@ static void test_cancel_src_after_none(QTestState *from, QTestState *to,
689 wait_for_serial("src_serial");
690 migrate_cancel(from);
691
693 - migrate_incoming_qmp(to, uri, NULL, "{ 'exit-on-error': false }");
692 + migrate_incoming_qmp(to, uri, NULL, "{}");
693
694 migrate_ensure_converge(from);
695 migrate_qmp(from, to, uri, NULL, "{}");
@@ -709,7 +708,7 @@ static void test_cancel_src_pre_switchover(QTestState *from, QTestState *to,
708 migrate_set_capability(from, "multifd", true);
709 migrate_set_capability(to, "multifd", true);
710
712 - migrate_incoming_qmp(to, uri, NULL, "{ 'exit-on-error': false }");
711 + migrate_incoming_qmp(to, uri, NULL, "{}");
712
713 wait_for_serial("src_serial");
714 migrate_ensure_converge(from);
@@ -1101,7 +1100,6 @@ static void test_dirty_limit(char *name, MigrateCommon *args)
1100
1101 /* destination always fails after cancel */
1102 migration_event_wait(to, "failed");
1104 - qtest_set_expected_status(to, EXIT_FAILURE);
1103 qtest_quit(to);
1104
1105 /* Check if dirty limit throttle switched off, set timeout 1ms */
tests/qtest/migration/tls-tests.c
+8 -6
@@ -441,10 +441,10 @@ static void test_precopy_unix_tls_x509_default_host(char *name,
441 g_autofree char *uri = g_strdup_printf("unix:%s/migsocket", tmpfs);
442
443 args->connect_uri = uri;
444 - args->listen_uri = uri;
444 + args->listen_uri = "defer";
445 args->start_hook = migrate_hook_start_tls_x509_default_host;
446 args->end_hook = migrate_hook_end_tls_x509;
447 - args->result = MIG_TEST_FAIL_DEST_QUIT_ERR;
447 + args->result = MIG_TEST_FAIL;
448
449 args->start.hide_stderr = true;
450
@@ -522,10 +522,11 @@ migrate_hook_start_tls_x509_no_host(QTestState *from, QTestState *to)
522
523 static void test_precopy_tcp_tls_no_hostname(char *name, MigrateCommon *args)
524 {
525 - args->listen_uri = "tcp:127.0.0.1:0";
525 + args->listen_uri = "defer";
526 + args->connect_uri = "tcp:127.0.0.1:0";
527 args->start_hook = migrate_hook_start_tls_x509_no_host;
528 args->end_hook = migrate_hook_end_tls_x509;
528 - args->result = MIG_TEST_FAIL_DEST_QUIT_ERR;
529 + args->result = MIG_TEST_FAIL;
530
531 args->start.hide_stderr = true;
532
@@ -556,10 +557,11 @@ static void test_precopy_tcp_tls_x509_override_host(char *name,
557 static void test_precopy_tcp_tls_x509_mismatch_host(char *name,
558 MigrateCommon *args)
559 {
559 - args->listen_uri = "tcp:127.0.0.1:0";
560 + args->listen_uri = "defer";
561 + args->connect_uri = "tcp:127.0.0.1:0";
562 args->start_hook = migrate_hook_start_tls_x509_mismatch_host;
563 args->end_hook = migrate_hook_end_tls_x509;
562 - args->result = MIG_TEST_FAIL_DEST_QUIT_ERR;
564 + args->result = MIG_TEST_FAIL;
565
566 args->start.hide_stderr = true;
567