From: Fabiano Rosas <farosas@suse.de>
To: Peter Xu <peterx@redhat.com>, qemu-devel@nongnu.org
Cc: peterx@redhat.com, Akash Kulhalli <akulhall@redhat.com>,
Juraj Marcin <jmarcin@redhat.com>
Subject: Re: [PATCH v2 6/7] migration: Fix incoming channel race with multifd and preempt_pre_7_2
Date: Thu, 15 Oct 2026 10:28:44 -0300 [thread overview]
Message-ID: <87se27ks8j.fsf@suse.de> (raw)
In-Reply-To: <20261006182046.3767283-7-peterx@redhat.com>
Peter Xu <peterx@redhat.com> writes:
> PROBLEM
> =======
>
> Migration reliably hangs with old machine types (7.2-) when these migration
> capabilities are enabled: multifd, postcopy-ram, postcopy-preempt.
>
> Simplest reproducer :
>
> $ QTEST_QEMU_MACHINE_TYPE=pc-q35-7.2 \
> QTEST_QEMU_BINARY=./qemu-system-x86_64 \
> ./tests/qtest/migration-test --full \
> -r /x86_64/migration/multifd+postcopy/preempt/plain
>
> Current workaround is to kill destination QEMU, cancel the migration on
> source (with/without yank), disable one or more of the features, and retry.
>
> The hang is caused by receiving preempt channel before most of the multifd
> channels, the peek buffer operation will stuck forever.
>
> Incoming side QEMU hang (with relevant tracepoints enabled):
>
> migrate_set_state new state setup
> migration_socket_incoming_accepted
> migration_set_incoming_channel ioc=0x557d82c1caa0 ioctype=qio-channel-socket
> migration_socket_incoming_accepted
> migration_set_incoming_channel ioc=0x557d83023840 ioctype=qio-channel-socket
> multifd_recv_new_channel channel 1
> migration_socket_incoming_accepted
> migration_set_incoming_channel ioc=0x557d830c08c0 ioctype=qio-channel-socket
> multifd_recv_new_channel channel 0
> multifd_recv_thread_start 1
> migration_socket_incoming_accepted
> migration_set_incoming_channel ioc=0x557d8284a010 ioctype=qio-channel-socket
> multifd_recv_thread_start 0
> multifd_recv_unfill channel 0 packet_num 0 flags 0x1 next packet size 0
> multifd_recv_unfill channel 1 packet_num 1 flags 0x1 next packet size 0
> [dest qemu hangs here]
>
> Incoming side backtrace:
>
> __syscall_cancel_arch
> __internal_syscall_cancel
> __syscall_cancel
> recvmsg
> qio_channel_socket_readv
> qio_channel_readv_full
> migration_channel_read_peek
> migration_channel_identify
> migration_channel_process_incoming
> socket_accept_incoming_migration
> qio_net_listener_channel_func
> qio_channel_fd_source_dispatch
> g_main_context_dispatch_unlocked.lto_priv
> g_main_context_dispatch
> glib_pollfds_poll
> os_host_main_loop_wait
> main_loop_wait
> qemu_main_loop
> qemu_default_main
> main
>
> This patch tries to fix it, from incoming side first. This patch alone
> should enable single-direction migration to this new QEMU. The next patch
> will propose a fix for the outgoing path, fixing new->old migrations.
>
> BACKGROUND
> ==========
>
> preempt_pre_7_2 used to be a flag marking QEMU releases 7.1/7.2 only
> because they have a flaw on channel establishment on preempt channel.
>
> Now, after we have multifd + postcopy support, new QEMU binaries can
> migrate old machine types with both features enabled. Juraj reported
> constant migration hang at the very beginning for those old machine types
> in our downstream systems with such setup. Juraj found it's because of
> this flag.
>
> QEMU source initiates channels in this order on current master when both
> features enabled with postcopy-preempt mode (machine type 7.2, implies
> preempt_pre_7_2=on):
>
> - First, main channel connect() happens,
> - Second, postcopy_preempt_setup() invokes connect() for preempt channel,
> - Last, in migration_thread(), multifd_send_setup() invokes connect()s
> for all the multifd channels
>
> However on an old QEMU, it expect the channels arrive in order of
> "main"->"multifd"->"preempt". Even on a new QEMU, the destination expects
> the same order of arrival on the channels when the channels are not
> peekable.
>
> For peekable channels (non-tls sockets), this time we'll likely see preempt
> channel appearing very soon, peeking at it causing main thread hang, like
> mentioned in the PROBLEM section above.
>
> SOLUTION
> ========
>
> One major problem of current hang is we try to peek at preempt channel,
> which offers nothing at the beginning.
>
> Since we'll need to accept stream from old binaries (QEMU 10.1 to 11.1),
> arrival of preempt channel early is unavoidable.
>
> What we can do, though, is to allow concurrent monitoring of all channels
> rather than one. Meanwhile, we shouldn't peek at anything that has no
> data.
>
> migration_channel_read_peek() almost does the wait, but it's too late.
> This patch proposes to watch all channels from the start without reading at
> them at all, relying on an IO watch attached. This will make sure (1) for
> preempt channel, we never read on it (watch can be gracefully disarmed when
> we know it's preempt channel later) (2) since we have one IO watch for each
> channel, then we can allow accept() to happen in almost any order, process
> only when there's some header to read.
>
> To maintain these watches, a new GArray @channels_early is introduced, with
> each element to be a (ioc, GSource) tuple (@MigEarlyIncomingChannel), so
> that QEMU can manage all the IO watches later with it. Here I used GSource
> rather than io_tag because the GSource may or may not be attached to the
> default main thread gcontext: when OOB is used, it may be the iothread
> context instead. IOW, g_source_remove() on io_tags stops working, which
> only works for default main context.
>
> The trick this patch used is when we have the ability to wait on all
> channel headers at the same time, we pick one by one from main and multifd
> channels (since both of the types will contain headers dumped right at the
> very beginning), leave preempt channel alone. It means, after dest QEMU
> finds all main and multifd channels, there are three possibilities:
>
> - If preempt not enabled, we finished full channel establishment, done.
>
> - If preempt enabled,
>
> - If we have one registered IO watch with the IOC, it must be the
> preempt channel,
>
> - If we have no further IO watch, wait for the next accept(), which
> must be the preempt channel.
>
> This will make channel establish work on incoming side even with the old
> QEMU binaries.
>
> While working on it, I managed to refactor the peeking logic quite a bit:
> we used to have a very convoluted migration_channel_identify() function,
> taking care of two different cases:
>
> (1) when the channel is peekable (like normal socket channels),
> (2) when the channel is not peekable (like TLS or file migrations).
>
> This patch refactored that part to make it crystal clear on how we make
> decisions on which channel is which, and split the peek/identify paths.
>
> One example incoming migration from pc-q35-7.2 after this patch applied,
> and with relevant tracepoints set (8 multifd channels, 1 preempt channel):
>
> migrate_set_state new state setup
> migration_channel_process_incoming ioc=0x557dc8af4e20 ioctype=qio-channel-socket
> migration_incoming_channel_watch ioc=0x557dc8af4e20 ioctype=qio-channel-socket
> migration_channel_process_incoming ioc=0x557dc8abc5d0 ioctype=qio-channel-socket
> migration_incoming_channel_watch ioc=0x557dc8abc5d0 ioctype=qio-channel-socket
> migration_channel_process_incoming ioc=0x557dc953d680 ioctype=qio-channel-socket
> migration_incoming_channel_watch ioc=0x557dc953d680 ioctype=qio-channel-socket
> migration_set_incoming_channel ioc=0x557dc8abc5d0 ioctype=qio-channel-socket type=MULTIFD
> migration_channel_process_incoming ioc=0x557dc898a820 ioctype=qio-channel-socket
> migration_incoming_channel_watch ioc=0x557dc898a820 ioctype=qio-channel-socket
> migration_set_incoming_channel ioc=0x557dc953d680 ioctype=qio-channel-socket type=MULTIFD
> migration_channel_process_incoming ioc=0x557dc919bd70 ioctype=qio-channel-socket
> migration_incoming_channel_watch ioc=0x557dc919bd70 ioctype=qio-channel-socket
> migration_set_incoming_channel ioc=0x557dc898a820 ioctype=qio-channel-socket type=MULTIFD
> migration_channel_process_incoming ioc=0x557dc8734b20 ioctype=qio-channel-socket
> migration_incoming_channel_watch ioc=0x557dc8734b20 ioctype=qio-channel-socket
> migration_set_incoming_channel ioc=0x557dc919bd70 ioctype=qio-channel-socket type=MULTIFD
> migration_channel_process_incoming ioc=0x557dc898bdf0 ioctype=qio-channel-socket
> migration_incoming_channel_watch ioc=0x557dc898bdf0 ioctype=qio-channel-socket
> migration_set_incoming_channel ioc=0x557dc8734b20 ioctype=qio-channel-socket type=MULTIFD
> migration_channel_process_incoming ioc=0x557dc968cfd0 ioctype=qio-channel-socket
> migration_incoming_channel_watch ioc=0x557dc968cfd0 ioctype=qio-channel-socket
> migration_set_incoming_channel ioc=0x557dc898bdf0 ioctype=qio-channel-socket type=MULTIFD
> migration_channel_process_incoming ioc=0x557dc8c31810 ioctype=qio-channel-socket
> migration_incoming_channel_watch ioc=0x557dc8c31810 ioctype=qio-channel-socket
> migration_set_incoming_channel ioc=0x557dc968cfd0 ioctype=qio-channel-socket type=MULTIFD
> migration_set_incoming_channel ioc=0x557dc8c31810 ioctype=qio-channel-socket type=MULTIFD
> migration_set_incoming_channel ioc=0x557dc8af4e20 ioctype=qio-channel-socket type=MAIN
> migration_start_incoming
> migrate_set_state new state active
> migration_channel_process_incoming ioc=0x557dc8966160 ioctype=qio-channel-socket
> migration_set_incoming_channel ioc=0x557dc8966160 ioctype=qio-channel-socket type=PREEMPT
>
> While at it, I renamed the four channel incoming/outgoing tracepoints, make
> all to follow migration_[incoming|outgoing]_channel_*(). Especially for
> incoming, then we can enable migration_incoming_channel* for incoming
> channel debuggings.
>
> FINAL NOTE ON TLS
> =================
>
> Note that this solution isn't a magic bullet fixing everything. TLS is not
> fixable with this approach because TLS isn't peekable channel, hence the
> watch won't help.
>
> We can have other ways to hack it to make it work, e.g. we can make TLS
> channels peekable too, but I don't want to pollute the TLS iochannel only
> for this: let's hope handshake will arrive soon so we can drop these code.
>
> For now, when TLS is involved with such old machine type (7.2-), one needs
> to migrate it out with any combination of features except enabling all
> four (multifd + TLS + postcopy + preempt) to a patched binary, after that,
> all combinations will start working.
>
> BugLink: https://redhat.atlassian.net/browse/RHEL-254933
> Fixes: e27418861288 ("migration: enable multifd and postcopy together")
> Reported-by: Juraj Marcin <jmarcin@redhat.com>
> Signed-off-by: Peter Xu <peterx@redhat.com>
Reviewed-by: Fabiano Rosas <farosas@suse.de>
next prev parent reply other threads:[~2026-10-15 13:29 UTC|newest]
Thread overview: 17+ messages / expand[flat|nested] mbox.gz Atom feed top
2026-10-06 18:20 [PATCH v2 0/7] migration: Fix multifd+postcopy break on qemu-7.2 machine type Peter Xu
2026-10-06 18:20 ` [PATCH v2 1/7] tests/migration-test: Add multifd+postcopy test for preempt recovery Peter Xu
2026-10-14 21:49 ` Fabiano Rosas
2026-10-06 18:20 ` [PATCH v2 2/7] migration: Add two more trace points for incoming channel mgmt Peter Xu
2026-10-14 21:50 ` Fabiano Rosas
2026-10-06 18:20 ` [PATCH v2 3/7] migration: Fix socket listen backlog Peter Xu
2026-10-06 20:56 ` Daniel P. Berrangé
2026-10-07 19:39 ` Peter Xu
2026-10-14 21:51 ` Fabiano Rosas
2026-10-06 18:20 ` [PATCH v2 4/7] migration: Split migration_channel_process_incoming() Peter Xu
2026-10-15 13:04 ` Fabiano Rosas
2026-10-06 18:20 ` [PATCH v2 5/7] migration: Dump type of channel in set_incoming_channel tracepoint Peter Xu
2026-10-15 13:10 ` Fabiano Rosas
2026-10-06 18:20 ` [PATCH v2 6/7] migration: Fix incoming channel race with multifd and preempt_pre_7_2 Peter Xu
2026-10-15 13:28 ` Fabiano Rosas [this message]
2026-10-06 18:20 ` [PATCH v2 7/7] migration: Postpone the outgoing preempt channel for preempt_pre_7_2 Peter Xu
2026-10-15 13:30 ` Fabiano Rosas
Reply instructions:
You may reply publicly to this message via plain-text email
using any one of the following methods:
* Save the following mbox file, import it into your mail client,
and reply-to-all from there: mbox
Avoid top-posting and favor interleaved quoting:
https://en.wikipedia.org/wiki/Posting_style#Interleaved_style
* Reply using the --to, --cc, and --in-reply-to
switches of git-send-email(1):
git send-email \
--in-reply-to=87se27ks8j.fsf@suse.de \
--to=farosas@suse.de \
--cc=akulhall@redhat.com \
--cc=jmarcin@redhat.com \
--cc=peterx@redhat.com \
--cc=qemu-devel@nongnu.org \
/path/to/YOUR_REPLY
https://kernel.org/pub/software/scm/git/docs/git-send-email.html
* If your mail client supports setting the In-Reply-To header
via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line
before the message body.
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox