Peter Xu <[email protected]> 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:
>
> - 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, 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 <[email protected]>
> Signed-off-by: Peter Xu <[email protected]>
> ---
> migration/channel.h | 3 +
> migration/migration.h | 35 ++++
> migration/channel.c | 414 +++++++++++++++++++++++++++++++++++------
> migration/migration.c | 23 ++-
> migration/trace-events | 7 +-
> 5 files changed, 421 insertions(+), 61 deletions(-)
Hi Peter, I think the code looks good, but there are some twists and
turns that I think we can get rid of. I put a fixup patch [1] at the end
with my suggestions and mentioned inline what I changed and why.
>
> diff --git a/migration/channel.h b/migration/channel.h
> index 678712f291..40bd339a3e 100644
> --- a/migration/channel.h
> +++ b/migration/channel.h
> @@ -49,4 +49,7 @@ bool migration_channel_parse_input(const char *uri,
> MigrationChannel **main_channelp,
> MigrationChannel **cpr_channelp,
> Error **errp);
> +
> +void migration_incoming_free_early_channels(MigrationIncomingState *mis);
> +
> #endif
> diff --git a/migration/migration.h b/migration/migration.h
> index 683bc7bdd5..6f23e1805b 100644
> --- a/migration/migration.h
> +++ b/migration/migration.h
> @@ -90,6 +90,28 @@ typedef enum {
> PREEMPT_THREAD_QUIT,
> } PreemptThreadStatus;
>
> +typedef struct {
> + /*
> + * The GSource we have allocated to track G_IO_IN events for the
> + * channel. Note: it may not be attached to the default main gcontext,
> + * for example, when in case of io watch created during an OOB
> + * migrate-recover command.
> + */
> + GSource *source;
> + /* The channel we will identify later after getting some buffers to read
> */
> + QIOChannel *ioc;
> +} MigEarlyIncomingChannel;
> +
> +typedef struct {
> + GArray *channels;
> + /*
> + * In most cases, channels are only operated with BQl, but since we
> + * have OOB command support, don't assume it, just use a standalone
> + * mutex to protect any access to the channels array.
> + */
> + QemuMutex mutex;
> +} MigEarlyIncomingChannels;
> +
> /* State for the incoming migration */
> struct MigrationIncomingState {
> QEMUFile *from_src_file;
> @@ -255,6 +277,19 @@ struct MigrationIncomingState {
>
> /* Do exit on incoming migration failure */
> bool exit_on_error;
> +
> + /*
> + * Maintains all channels only at early stage. After incoming
> + * migration started, this should be an empty array and no use anymore.
> + *
> + * TODO: we logically only need this to make new QEMU binaries work for
> + * old machines who have @preempt_pre_7_2 set on the source, but it's
> + * also good to have this early stage anyway, making sure when peek the
> + * channel there will be something in the channel. Ideally, we should
> + * manage all migration channels in the future with proper headers,
> + * then we can drop this.
> + */
> + MigEarlyIncomingChannels channels_early;
> };
>
> MigrationIncomingState *migration_incoming_get_current(void);
> diff --git a/migration/channel.c b/migration/channel.c
> index b9065efd03..acb7ef5206 100644
> --- a/migration/channel.c
> +++ b/migration/channel.c
> @@ -12,6 +12,8 @@
>
> #include "qemu/osdep.h"
> #include "qemu/cutils.h"
> +#include "qemu/main-loop.h"
> +#include "qemu/error-report.h"
> #include "channel.h"
> #include "exec.h"
> #include "fd.h"
> @@ -146,61 +148,84 @@ bool migration_has_all_channels(void)
> return true;
> }
>
> -static MigChannelType migration_channel_identify(MigrationIncomingState *mis,
> - QIOChannel *ioc, Error
> **errp)
> +static bool qio_channel_is_peekable(QIOChannel *ioc)
> +{
> + return qio_channel_has_feature(ioc, QIO_CHANNEL_FEATURE_READ_MSG_PEEK);
> +}
> +
> +/*
> + * With multiple channels, it is possible that we receive channels out of
> + * order on destination side, causing incorrect mapping of source channels
> + * on destination side.
> + *
> + * When the channel is peekable (e.g. non-TLS socket channels), check
> + * channel MAGIC to decide type of channel.
> + *
> + * Please note this is best effort, postcopy preempt channel does not send
> + * any magic number so avoid it for postcopy live migration.
> + *
> + * Returns: >0 if success, ==0 (CH_NONE) if error. If error happened,
> + * *errp must be set.
> + */
> +static MigChannelType migration_channel_peek(MigrationIncomingState *mis,
> + QIOChannel *ioc,
> + Error **errp)
> {
> MigChannelType channel = CH_NONE;
> uint32_t channel_magic = 0;
> int ret = 0;
>
> - if (!migration_has_main_and_multifd_channels()) {
> - if (qio_channel_has_feature(ioc, QIO_CHANNEL_FEATURE_READ_MSG_PEEK))
> {
> - /*
> - * With multiple channels, it is possible that we receive
> channels
> - * out of order on destination side, causing incorrect mapping of
> - * source channels on destination side. Check channel MAGIC to
> - * decide type of channel. Please note this is best effort,
> - * postcopy preempt channel does not send any magic number so
> - * avoid it for postcopy live migration. Also tls live migration
> - * already does tls handshake while initializing main channel so
> - * with tls this issue is not possible.
> - */
> - ret = migration_channel_read_peek(ioc, (void *)&channel_magic,
> - sizeof(channel_magic), errp);
> - if (ret != 0) {
> - goto out;
> - }
> + assert(qio_channel_is_peekable(ioc));
>
> - channel_magic = be32_to_cpu(channel_magic);
> - if (channel_magic == QEMU_VM_FILE_MAGIC) {
> - channel = CH_MAIN;
> - } else if (channel_magic == MULTIFD_MAGIC) {
> - assert(migrate_multifd());
> - channel = CH_MULTIFD;
> - } else if (!mis->from_src_file &&
> - mis->state == MIGRATION_STATUS_POSTCOPY_PAUSED) {
> - /* reconnect main channel for postcopy recovery */
> - channel = CH_MAIN;
> - } else {
> - error_setg(errp, "unknown channel magic: %u", channel_magic);
> - }
> - } else if (mis->from_src_file && migrate_multifd()) {
> - /*
> - * Non-peekable channels like tls/file are processed as
> - * multifd channels when multifd is enabled.
> - */
> - channel = CH_MULTIFD;
> - } else if (!mis->from_src_file) {
> - channel = CH_MAIN;
> - } else {
> - error_setg(errp, "non-peekable channel used without multifd");
> - }
> + ret = migration_channel_read_peek(ioc, (void *)&channel_magic,
> + sizeof(channel_magic), errp);
> + if (ret != 0) {
> + /* Failed */
> + return channel;
> + }
> +
> + channel_magic = be32_to_cpu(channel_magic);
> +
> + if (channel_magic == QEMU_VM_FILE_MAGIC) {
> + channel = CH_MAIN;
> + } else if (channel_magic == MULTIFD_MAGIC) {
> + assert(migrate_multifd());
> + channel = CH_MULTIFD;
> + } else if (!mis->from_src_file &&
> + mis->state == MIGRATION_STATUS_POSTCOPY_PAUSED) {
> + /* reconnect main channel for postcopy recovery */
> + channel = CH_MAIN;
This is not peeking, maybe it should be somewhere else. Not this patch's
fault, we can think about it some other day.
> } else {
> + error_setg(errp, "Unknown channel magic: %u", channel_magic);
> + }
> +
> + return channel;
> +}
> +
> +/*
> + * Returns: >0 if success, ==0 (CH_NONE) if error. If error happened,
> + * *errp must be set.
> + */
> +static MigChannelType migration_channel_identify(MigrationIncomingState *mis,
> + QIOChannel *ioc, Error
> **errp)
> +{
> + MigChannelType channel = CH_NONE;
> +
> + if (migration_has_main_and_multifd_channels()) {
> assert(migrate_postcopy_preempt());
> channel = CH_POSTCOPY;
This check is the same as the one at
migration_channel_process_incoming(). I think we can simplify this.
See [1].
> + } else if (!mis->from_src_file) {
> + channel = CH_MAIN;
> + } else if (migrate_multifd()) {
> + /*
> + * Non-peekable channels like tls/file are processed as
> + * multifd channels when multifd is enabled.
> + */
> + channel = CH_MULTIFD;
> + } else {
> + error_setg(errp, "Unexpected non-peekable channel observed");
> }
>
> -out:
> return channel;
> }
>
> @@ -214,31 +239,275 @@ static void
> migration_incoming_error_propagate(MigrationIncomingState *mis,
> }
> }
>
> +/* Must be with mis->channels_early.mutex held */
> +static void
> +migration_incoming_early_channel_insert(MigrationIncomingState *mis,
> + QIOChannel *ioc,
> + GSource *source)
> +{
> + MigEarlyIncomingChannel chan = {
> + .source = source,
> + .ioc = ioc,
> + };
> +
> + object_ref(OBJECT(ioc));
> + g_source_ref(source);
> +
> + g_array_append_val(mis->channels_early.channels, chan);
> +}
> +
> +static void migration_incoming_early_channel_free(GArray *channels, int i)
> +{
> + MigEarlyIncomingChannel *chan;
> +
> + chan = &g_array_index(channels, MigEarlyIncomingChannel, i);
> +
> + g_source_destroy(chan->source);
> + g_source_unref(chan->source);
> + object_unref(OBJECT(chan->ioc));
> +
> + g_array_remove_index_fast(channels, i);
> +}
> +
> +/*
> + * Remove this channel from the monitoring of @channels_early array.
> + * Return true if found and successful, false otherwise.
> + */
> +static bool
> +migration_incoming_early_channel_remove(MigrationIncomingState *mis,
> + QIOChannel *ioc)
> +{
> + GArray *channels = mis->channels_early.channels;
> + MigEarlyIncomingChannel *chan;
> + int i;
> +
> + QEMU_LOCK_GUARD(&mis->channels_early.mutex);
> +
> + for (i = 0; i < channels->len; i++) {
> + chan = &g_array_index(channels, MigEarlyIncomingChannel, i);
> + if (chan->ioc != ioc) {
> + continue;
> + }
> + migration_incoming_early_channel_free(channels, i);
> + return true;
> + }
> +
> + return false;
> +}
> +
> +void migration_incoming_free_early_channels(MigrationIncomingState *mis)
> +{
> + GArray *channels = mis->channels_early.channels;
> +
> + QEMU_LOCK_GUARD(&mis->channels_early.mutex);
> +
> + while (channels->len) {
> + migration_incoming_early_channel_free(channels, 0);
> + }
> +}
> +
> +static bool migration_incoming_setup_channel(QIOChannel *ioc,
> + MigChannelType ch,
> + Error **errp)
> +{
> + trace_migration_incoming_channel_set(ioc,
> + object_get_typename(OBJECT(ioc)),
> + mig_channel_str[ch]);
> + migration_ioc_register_yank(ioc);
> + /* TODO: make this return the success status instead */
> + migration_incoming_setup(ioc, ch, errp);
I implemented this TODO. It allows us to skip the main+mfd check after
starting migration because migration_incoming_setup() already returns
that value. See [1].
> +
> + return *errp == NULL;
> +}
> +
> static bool migration_incoming_channel_install(MigrationIncomingState *mis,
> QIOChannel *ioc,
> Error **errp)
> {
> - MigChannelType ch = migration_channel_identify(mis, ioc, errp);
> + MigChannelType ch;
> + bool ret;
> +
> + if (qio_channel_is_peekable(ioc)) {
This check is redundant, we can only arrive here via the watch and we
checked peekable already at migration_channel_process_incoming. See [1].
> + ch = migration_channel_peek(mis, ioc, errp);
> + } else {
> + ch = migration_channel_identify(mis, ioc, errp);
> + }
>
> if (!ch) {
> - assert(*errp);
> return false;
> }
>
> - trace_migration_set_incoming_channel(ioc,
> - object_get_typename(OBJECT(ioc)),
> - mig_channel_str[ch]);
> - migration_ioc_register_yank(ioc);
> + ret = migration_incoming_setup_channel(ioc, ch, errp);
> + if (!ret) {
> + return false;
> + }
>
> - if (migration_incoming_setup(ioc, ch, errp)) {
> + /* Installation succeeded, kickoff migration if needed */
> + if (migration_has_main_and_multifd_channels()) {
> migration_start_incoming();
> }
>
> - if (*errp) {
> - return false;
> + return true;
> +}
> +
> +static void
> +migration_incoming_channel_preempt_setup(QIOChannel *ioc)
> +{
> + assert(migrate_postcopy_preempt());
> + /* Installation of preempt channel should never fail */
> + migration_incoming_setup_channel(ioc, CH_POSTCOPY, &error_abort);
> +}
> +
> +static void
> +migration_incoming_detect_preempt_channel(MigrationIncomingState *mis)
> +{
> + GArray *channels = mis->channels_early.channels;
> + MigEarlyIncomingChannel *chan;
> +
> + QEMU_LOCK_GUARD(&mis->channels_early.mutex);
> +
> + /* When preempt mode not enabled, nothing to detect.. */
> + if (!migrate_postcopy_preempt()) {
> + /*
> + * .. but if we found something pending, throw an error only, which
> + * should not happen. Even if it happens, resources will still be
> + * released after incoming migration is completedly.
> + */
> + if (channels->len) {
> + error_report("%s: Found %u unused channels",
> + __func__, channels->len);
> + }
> + return;
> }
>
> - return true;
> + /* Preempt channel hasn't yet arrived? Process it later */
> + if (!channels->len) {
> + return;
> + }
> +
> + /*
> + * More than one channel should never happen.. capture it in case if
> + * it happens, then there's not much we can do.
> + */
> + if (channels->len > 1) {
> + error_report("%s: Found %u unused channels, "
> + "can't identify preempt channel",
> + __func__, channels->len);
> + return;
> + }
> +
> + assert(channels->len == 1);
> + /* This is the preempt channel, install it directly */
> + chan = &g_array_index(channels, MigEarlyIncomingChannel, 0);
> + migration_incoming_channel_preempt_setup(chan->ioc);
> + migration_incoming_early_channel_free(channels, 0);
> +}
> +
> +static gboolean migration_incoming_channel_readable(QIOChannel *ioc,
> + GIOCondition condition,
> + gpointer opaque)
> +{
> + MigrationIncomingState *mis = opaque;
> + Error *local_err = NULL;
> +
> + /*
> + * No need to monitor this channel anymore as long as anything arrived,
> + * remove it from tracking.
> + *
> + * NOTE: this means if partial data arrived we may still block here,
> + * but it shouldn't happen in production, only malicious stream. Since
> + * migration stream is trusted (either due to trusted network, or TLS),
> + * that's non-issue.
Could we check the amount of data and keep waiting?
> + *
> + * NOTE2: this will also release the ioc ref that we used to hold, but
> + * it's fine since we have at least one more refcount in the current
> + * event handler.
I don't understand "one more ref in the event handler". Looks like
you're trying to say the refcount is consistent, but why even mention
it? I may be missing something.
> + *
> + * NOTE3: it's theoretically possible that this entry is gone reaching
> + * here. Example: the main thread is doing incoming cleanup having this
> + * one removed, while this watch can be registered on the monitor
> + * iothread's context and fired at the exact same time but in the
> + * iothread instead. If it happens, skip the rest. I'm not sure if
> + * this could happen at all, may depend on iothread lifespan management
> + * in the main thread, but be prepared.
I wonder if we should extend the locking a bit further. Not because of
what you mentioned here^, but because everything the code running on the
monitor thread touches can race with changes from the main loop. All of
these migrate_foo_bar() helpers, the QEMUFiles, caps, params, etc.
> + */
> + if (!migration_incoming_early_channel_remove(mis, ioc)) {
> + goto out;
> + }
> +
I moved the peek into here and passed the already identified
channel into migration_incoming_channel_install(). See [1].
> + if (!migration_incoming_channel_install(mis, ioc, &local_err)) {
> + goto out;
> + }
> +
> + if (migration_has_main_and_multifd_channels()) {
This check became redundant because the _setup routine already returns
it. See [1].
> + /*
> + * Possibilities when reaching here:
> + *
> + * (1) if preempt not enabled, this should be no-op, all done,
> + * (2) if preempt enabled,
> + * (2.a) preempt channel arrived @channels_early, handle it now
> + * (2.b) preempt channel not arrived, to be handled in
> + * migration_channel_process_incoming() later
> + */
> + migration_incoming_detect_preempt_channel(mis);
Moved this into migration_incoming_channel_install(). See [1].
> + }
> +
> +out:
> + if (local_err) {
> + migration_incoming_error_propagate(mis, local_err);
> + }
> +
> + /*
> + * NOTE: we should have already detached the GSource, returning
> + * G_SOURCE_REMOVE to be logically consistent only.
> + */
> + return G_SOURCE_REMOVE;
> +}
> +
> +static void migration_incoming_channel_watch(MigrationIncomingState *mis,
> + QIOChannel *ioc)
> +{
> + GMainContext *context = g_main_context_get_thread_default();
> + GSource *source;
> + guint io_tag;
> +
> + trace_migration_incoming_channel_watch(ioc,
> + object_get_typename(OBJECT(ioc)));
> +
> + /*
> + * Careful: this can be run from either the main thread or the monitor
> + * iothread when migrate_recover is used with OOB=on, so we need to
> + * take the lock and use the full version to specify the correct
> + * context.
> + */
> + QEMU_LOCK_GUARD(&mis->channels_early.mutex);
> +
> + /*
> + * We should never watch an @ioc that is not peekable, because there's
> + * no point. What is worse is we lose the real order of accept()s via
> + * the asynchronous IO watch operation.
> + *
> + * Another note is TLS channel (non-peekable) may or may not work
> + * properly with IO watch due to its current .io_create_watch() impl,
> + * which is another story. Just guard both points.
> + */
> + assert(qio_channel_is_peekable(ioc));
> + io_tag = qio_channel_add_watch_full(ioc, G_IO_IN,
> + migration_incoming_channel_readable,
> + mis, NULL, context);
> +
> + /*
> + * Replace this if one day qio_channel_add_watch*() API can directly
> + * return the GSource*.. for now, stick with it.
> + */
> + source = g_main_context_find_source_by_id(context, io_tag);
> + /*
> + * Nothing can race with adding the IO watch, aka, concurrent removal
> + * is not possible when we have the lock. So it must be present.
> + */
> + assert(source);
> +
> + migration_incoming_early_channel_insert(mis, ioc, source);
> }
>
> /**
> @@ -254,13 +523,44 @@ void migration_channel_process_incoming(QIOChannel *ioc)
> MigrationIncomingState *mis = migration_incoming_get_current();
> Error *local_err = NULL;
>
> - trace_migration_channel_process_incoming(
> + trace_migration_incoming_channel_process(
> ioc, object_get_typename(OBJECT(ioc)));
>
> if (migrate_channel_requires_tls_upgrade(ioc)) {
> migration_tls_channel_process_incoming(ioc, &local_err);
> } else {
> - migration_incoming_channel_install(mis, ioc, &local_err);
> + if (migration_has_main_and_multifd_channels()) {
The fact that this check must come before everything is a major
weirdness. In fact, this is conceptually the same case as the first
branch in migration_channel_identify(). I therefore moved that function
to this point and added a comment explaining why this must come
first. See [1].
> + /*
> + * If all main+multifd channels are present already, this must
> + * be the preempt channel.
> + *
> + * QEMU can't register an IO watch for it if there is only the
> + * last preempt channel left, because it means the IO watch
> + * will never fire and nobody will pick it up: we rely on the
> + * one before the last one to pick both.
> + *
> + * See comment in migration_incoming_channel_readable() on
> + * the migration_incoming_detect_preempt_channel() call.
> + */
> + migration_incoming_channel_preempt_setup(ioc);
> + } else {
> + /*
> + * Register an IO watch for peekable channels, so that channels
> + * can be accept()ed with any order.
> + *
> + * Non-peekable channels (file, TLS, etc.) cannot register IO
> + * watch, not only because there's no data to look at to help
> + * making the decision, but also because after registering we
> + * will lose the real ordering we get from accept(), which is
> + * still so far the only source of truth to identify a channel
> + * in such case.
> + */
> + if (qio_channel_is_peekable(ioc)) {
I moved this line into migration_channel_identify(). That way we ensure
the ordering regarding the main+mfd check. The watch is still installed
at this point. See [1].
> + migration_incoming_channel_watch(mis, ioc);
> + } else {
> + migration_incoming_channel_install(mis, ioc, &local_err);
This invocation of install does not check for the preempt channel after
it returns. Is this simply assuming that if a non-peekable channel is
present then there will be no preempt channel at all?
Or if main+TLS is the first channel, then the next channel will be
preempt and already enter the first branch?
> + }
> + }
> }
>
> if (local_err) {
> @@ -270,7 +570,7 @@ void migration_channel_process_incoming(QIOChannel *ioc)
>
> void migration_channel_connect_outgoing(MigrationState *s, QIOChannel *ioc)
> {
> - trace_migration_set_outgoing_channel(ioc,
> object_get_typename(OBJECT(ioc)));
> + trace_migration_outgoing_channel_set(ioc,
> object_get_typename(OBJECT(ioc)));
>
> if (migrate_channel_requires_tls_upgrade(ioc)) {
> Error *local_err = NULL;
> diff --git a/migration/migration.c b/migration/migration.c
> index cfadcc79c2..4cc4077a76 100644
> --- a/migration/migration.c
> +++ b/migration/migration.c
> @@ -334,6 +334,10 @@ void migration_object_init(void)
> current_incoming->page_requested = g_tree_new(page_request_addr_cmp);
>
> current_incoming->exit_on_error = INMIGRATE_DEFAULT_EXIT_ON_ERROR;
> + /* zero_terminated=false, clear_=true */
There's an extra _ after 'clear' I think.
> + current_incoming->channels_early.channels =
> + g_array_new(FALSE, TRUE, sizeof(MigEarlyIncomingChannel));
> + qemu_mutex_init(¤t_incoming->channels_early.mutex);
>
> migration_object_check(current_migration, &error_fatal);
>
> @@ -447,6 +451,24 @@ void migration_incoming_state_destroy(void)
> MigrationIncomingState *mis = migration_incoming_get_current();
> PostcopyState ps = postcopy_state_get();
>
> + /* Cleanup listener to make sure no further accept() for sockets */
> + migration_incoming_transport_cleanup(mis);
> +
> + /*
> + * It's safer to free channel watches earlier than most of the rest, in
> + * case the IO watches could fire in the monitor iothread concurrently
> + * against this function.
Now I'm getting worried. Cleanup races are super annoying. This is
"just" about the recovery command, right?
> + *
> + * Above migration_incoming_transport_cleanup() should have disarmed
> + * anything that we could accept() new sockets.
> + *
> + * Here return of migration_incoming_free_early_channels() makes sure
> + * even if something already fired concurrently, it won't really do
> + * anything but return - see migration_incoming_early_channel_remove()
> + * and its return code for details.
> + */
> + migration_incoming_free_early_channels(mis);
> +
> multifd_recv_cleanup();
>
> if (ps != POSTCOPY_INCOMING_NONE) {
> @@ -491,7 +513,6 @@ void migration_incoming_state_destroy(void)
> mis->postcopy_remote_fds = NULL;
> }
>
> - migration_incoming_transport_cleanup(mis);
> qemu_event_reset(&mis->main_thread_load_event);
>
> if (mis->page_requested) {
> diff --git a/migration/trace-events b/migration/trace-events
> index 9ef42f54d1..c408deeb72 100644
> --- a/migration/trace-events
> +++ b/migration/trace-events
> @@ -207,9 +207,10 @@ migration_start_incoming(void) ""
> migration_transferred_bytes(uint64_t qemu_file, uint64_t multifd, uint64_t
> rdma) "qemu_file %" PRIu64 " multifd %" PRIu64 " RDMA %" PRIu64
>
> # channel.c
> -migration_channel_process_incoming(void *ioc, const char *ioctype) "ioc=%p
> ioctype=%s"
> -migration_set_incoming_channel(void *ioc, const char *ioctype, const char
> *type) "ioc=%p ioctype=%s type=%s"
> -migration_set_outgoing_channel(void *ioc, const char *ioctype) "ioc=%p
> ioctype=%s"
> +migration_outgoing_channel_set(void *ioc, const char *ioctype) "ioc=%p
> ioctype=%s"
> +migration_incoming_channel_process(void *ioc, const char *ioctype) "ioc=%p
> ioctype=%s"
> +migration_incoming_channel_set(void *ioc, const char *ioctype, const char
> *type) "ioc=%p ioctype=%s type=%s"
> +migration_incoming_channel_watch(void *ioc, const char *ioctype) "ioc=%p
> ioctype=%s"
>
> # global_state.c
> migrate_state_too_big(void) ""
[1] applies on top of the whole series. Tested:
make check
migration-test --full
multifd+postcopy/preempt/plain -M 7.2
multifd+postcopy/preempt/plain -M 8.2
multifd+postcopy/preempt/plain -M 11.1
multifd/tcp/uri/plain/none against 7.2 binary (src & dst)
multifd+postcopy/plain against 7.2 binary (src)
postcopy/preempt/plain against 7.2 binary (src)
compat against 11.1, 11.0 and 10.2.0
-- >8 --
>From 72f4153d983334f63c7efb91b54fffef15828f7f Mon Sep 17 00:00:00 2001
From: Fabiano Rosas <[email protected]>
Date: Thu, 24 Sep 2026 17:50:10 -0300
Subject: [PATCH] fixup! migration: Fix incoming channel race with multifd and
preempt_pre_7_2
---
migration/channel.c | 127 +++++++++++++++++++++-----------------------
1 file changed, 61 insertions(+), 66 deletions(-)
diff --git a/migration/channel.c b/migration/channel.c
index acb7ef5206..5c5a610d3c 100644
--- a/migration/channel.c
+++ b/migration/channel.c
@@ -43,6 +43,10 @@ static const char *const mig_channel_str[CH_NUM] = {
[CH_POSTCOPY] = "PREEMPT",
};
+static void
+migration_incoming_detect_preempt_channel(MigrationIncomingState *mis);
+
+
void migration_connect_outgoing(MigrationState *s, MigrationAddress *addr,
Error **errp)
{
@@ -203,27 +207,38 @@ static MigChannelType
migration_channel_peek(MigrationIncomingState *mis,
}
/*
- * Returns: >0 if success, ==0 (CH_NONE) if error. If error happened,
- * *errp must be set.
+ * Returns: >0 if channel was identified, ==0 (CH_NONE) if channel
+ * needs to be peeked asynchronously before identification.
*/
static MigChannelType migration_channel_identify(MigrationIncomingState *mis,
- QIOChannel *ioc, Error **errp)
+ QIOChannel *ioc)
{
- MigChannelType channel = CH_NONE;
+ MigChannelType channel;
if (migration_has_main_and_multifd_channels()) {
+ /*
+ * If all main+multifd channels are present already, this must
+ * be the preempt channel. Note that this branch must come
+ * first because the preempt channel is peekable but QEMU
+ * can't register an IO watch for it because the channel only
+ * transfers data during postcopy, which is too late.
+ */
assert(migrate_postcopy_preempt());
channel = CH_POSTCOPY;
+
+ } else if (qio_channel_is_peekable(ioc)) {
+ /* Needs to be peeked first */
+ channel = CH_NONE;
+
} else if (!mis->from_src_file) {
channel = CH_MAIN;
+
} else if (migrate_multifd()) {
/*
* Non-peekable channels like tls/file are processed as
* multifd channels when multifd is enabled.
*/
channel = CH_MULTIFD;
- } else {
- error_setg(errp, "Unexpected non-peekable channel observed");
}
return channel;
@@ -314,40 +329,36 @@ static bool migration_incoming_setup_channel(QIOChannel
*ioc,
object_get_typename(OBJECT(ioc)),
mig_channel_str[ch]);
migration_ioc_register_yank(ioc);
- /* TODO: make this return the success status instead */
- migration_incoming_setup(ioc, ch, errp);
-
- return *errp == NULL;
+ return migration_incoming_setup(ioc, ch, errp);
}
-static bool migration_incoming_channel_install(MigrationIncomingState *mis,
- QIOChannel *ioc,
+static void migration_incoming_channel_install(MigrationIncomingState *mis,
+ QIOChannel *ioc, MigChannelType
ch,
Error **errp)
{
- MigChannelType ch;
- bool ret;
-
- if (qio_channel_is_peekable(ioc)) {
- ch = migration_channel_peek(mis, ioc, errp);
- } else {
- ch = migration_channel_identify(mis, ioc, errp);
- }
-
- if (!ch) {
- return false;
- }
-
- ret = migration_incoming_setup_channel(ioc, ch, errp);
- if (!ret) {
- return false;
- }
-
- /* Installation succeeded, kickoff migration if needed */
- if (migration_has_main_and_multifd_channels()) {
+ if (migration_incoming_setup_channel(ioc, ch, errp)) {
migration_start_incoming();
- }
- return true;
+ /*
+ * Due to different implementations among QEMU versions, the
+ * postcopy preempt channel may not have yet been
+ * identified. Although peekable, it's not possible to use an
+ * IO watch to detect it because it does not contain any early
+ * data. At this point the main and multifd channels have
+ * already been identified, if there is any channel left in
+ * @channels_early, that would be the preempt channel. Look
+ * for it now.
+ *
+ * Possibilities when reaching here:
+ *
+ * (1) if preempt not enabled, this should be no-op, all done,
+ * (2) if preempt enabled,
+ * (2.a) preempt channel arrived @channels_early, handle it now
+ * (2.b) preempt channel not arrived, to be handled in
+ * migration_channel_process_incoming() later
+ */
+ migration_incoming_detect_preempt_channel(mis);
+ }
}
static void
@@ -409,6 +420,9 @@ static gboolean
migration_incoming_channel_readable(QIOChannel *ioc,
{
MigrationIncomingState *mis = opaque;
Error *local_err = NULL;
+ MigChannelType ch;
+
+ assert(qio_channel_is_peekable(ioc));
/*
* No need to monitor this channel anymore as long as anything arrived,
@@ -435,22 +449,12 @@ static gboolean
migration_incoming_channel_readable(QIOChannel *ioc,
goto out;
}
- if (!migration_incoming_channel_install(mis, ioc, &local_err)) {
+ ch = migration_channel_peek(mis, ioc, &local_err);
+ if (!ch) {
goto out;
}
- if (migration_has_main_and_multifd_channels()) {
- /*
- * Possibilities when reaching here:
- *
- * (1) if preempt not enabled, this should be no-op, all done,
- * (2) if preempt enabled,
- * (2.a) preempt channel arrived @channels_early, handle it now
- * (2.b) preempt channel not arrived, to be handled in
- * migration_channel_process_incoming() later
- */
- migration_incoming_detect_preempt_channel(mis);
- }
+ migration_incoming_channel_install(mis, ioc, ch, &local_err);
out:
if (local_err) {
@@ -529,20 +533,10 @@ void migration_channel_process_incoming(QIOChannel *ioc)
if (migrate_channel_requires_tls_upgrade(ioc)) {
migration_tls_channel_process_incoming(ioc, &local_err);
} else {
- if (migration_has_main_and_multifd_channels()) {
- /*
- * If all main+multifd channels are present already, this must
- * be the preempt channel.
- *
- * QEMU can't register an IO watch for it if there is only the
- * last preempt channel left, because it means the IO watch
- * will never fire and nobody will pick it up: we rely on the
- * one before the last one to pick both.
- *
- * See comment in migration_incoming_channel_readable() on
- * the migration_incoming_detect_preempt_channel() call.
- */
- migration_incoming_channel_preempt_setup(ioc);
+ MigChannelType ch = migration_channel_identify(mis, ioc);
+
+ if (ch) {
+ migration_incoming_channel_install(mis, ioc, ch, &local_err);
} else {
/*
* Register an IO watch for peekable channels, so that channels
@@ -555,11 +549,12 @@ void migration_channel_process_incoming(QIOChannel *ioc)
* still so far the only source of truth to identify a channel
* in such case.
*/
- if (qio_channel_is_peekable(ioc)) {
- migration_incoming_channel_watch(mis, ioc);
- } else {
- migration_incoming_channel_install(mis, ioc, &local_err);
- }
+ migration_incoming_channel_watch(mis, ioc);
+ /*
+ * async: the above will call into
+ * migration_incoming_channel_install() when it has
+ * identified the channel.
+ */
}
}
--
2.53.0