Re: [PATCH 4/5] migration: Fix rare hang of migration_channel_read_peek()

Fabiano Rosas <[email protected]>
Newsgroups gmane.comp.emulators.qemu
Message-ID <[email protected]>
Peter Xu <[email protected]> writes:

> On Tue, Jul 28, 2026 at 04:59:20PM -0300, Fabiano Rosas wrote:
>> Peter Xu <[email protected]> writes:
>> 
>> > On Tue, Jul 28, 2026 at 05:24:36PM +0100, Daniel P. Berrangé wrote:
>> >> On Tue, Jul 28, 2026 at 11:52:46AM -0400, Peter Xu wrote:
>> >> > In an unlikely case, when a migration stream is attached to the destination
>> >> > QEMU and only send <4 bytes to the channel as magic, it's possible that
>> >> > migration_channel_read_peek() may spin forever without yielding in the main
>> >> > thread causing two unwanted consequences:
>> >> > 
>> >> > - CPU will spin 100% waiting for the rest bytes until it reaches 4
>> >> > - (more importantly..) Main thread is stuck during this process as the qio
>> >> >   operation won't really yield the coroutine
>> >> > 
>> >> > Fix it by consuming the bytes that arrived.
>> >> > 
>> >> > Resolves: https://gitlab.com/qemu-project/qemu/-/work_items/3889
>> >> > Cc: Daniel P. Berrangé <[email protected]>
>> >> > Reported-by: Feifan Qian <[email protected]>
>> >> > Signed-off-by: Peter Xu <[email protected]>
>> >> > ---
>> >> >  migration/channel.c | 26 +++++++++++++++++++++++---
>> >> >  1 file changed, 23 insertions(+), 3 deletions(-)
>> >> > 
>> >> > diff --git a/migration/channel.c b/migration/channel.c
>> >> > index 1e2935f926..28fe1d2906 100644
>> >> > --- a/migration/channel.c
>> >> > +++ b/migration/channel.c
>> >> > @@ -294,11 +294,31 @@ int migration_channel_read_peek(QIOChannel *ioc,
>> >> >              return -1;
>> >> >          }
>> >> >  
>> >> > -        if (len == buflen) {
>> >> > +        if (len == iov.iov_len) {
>> >> >              break;
>> >> > -        }
>> >> > +        } else if (len == 0) {
>> >> > +            qio_channel_wait_cond(ioc, G_IO_IN);
>> >> > +        } else {
>> >> > +            ssize_t received = len;
>> >> >  
>> >> > -        qio_channel_wait_cond(ioc, G_IO_IN);
>> >> > +            /*
>> >> > +             * Partially arrived, read out to make qio_channel_wait_cond()
>> >> > +             * won't return immediately, causing an unwanted spin on this
>> >> > +             * CPU.
>> >> > +             */
>> >> > +            iov.iov_len = len;
>> >> > +            len = qio_channel_readv_full(ioc, &iov, 1, NULL, NULL, 0, errp);
>> >> 
>> >> Sure this breaks the API behaviour that the caller is expecting to
>> >> see.
>> >> 
>> >> migration_channel_identify will call migration_channel_read_peek
>> >> to match the magic bytes.
>> >> 
>> >> But something later in the flow will actually try to read the magic
>> >> bytes.  By consuming them in this migration_channel_read_peek
>> >> method, surely we're breaking the code that wants to read the bytes
>> >> later.
>> >
>> > Ah right, stupid me.  The best then is for partial read we apply a manual
>> > wait.
>> >
>> > I can also revert 604bb1badc ("migration: Properly wait on G_IO_IN when
>> > peeking messages"), looping with 1ms interval for data isn't too bad.  But
>> > the best is we only do that for partial, so:
>> >
>> > diff --git a/migration/channel.c b/migration/channel.c
>> > index 1e2935f926..88f89521f8 100644
>> > --- a/migration/channel.c
>> > +++ b/migration/channel.c
>> > @@ -296,9 +296,19 @@ int migration_channel_read_peek(QIOChannel *ioc,
>> >
>> >          if (len == buflen) {
>> >              break;
>> > +        } else if (len == 0) {
>> > +            qio_channel_wait_cond(ioc, G_IO_IN);
>> > +        } else {
>> > +            /*
>> > +             * When partially ready, we can't use qio_channel_wait_cond()
>> > +             * because it will return immediately.  Apply a manual wait.
>> > +             */
>> > +            if (qemu_in_coroutine()) {
>> 
>> There's no coroutine at this point, could replace this with an assert.
>
> Hmm, true..  Then it means this can stuck the main thread even if no data
> arrived (len==0), hang monitors..  I hope it's not a major issue and
> shouldn't easily happen in real life, because normally when src connected,
> at least the headers will be dumped very soon.
>

If I'm not mistaken the watch only dispatches when there's IO.

> I'll switch to assert for now.
>
>> 
>> > +                qemu_co_sleep_ns(QEMU_CLOCK_REALTIME, 1000000);
>> > +            } else {
>> > +                g_usleep(1000);
>> > +            }
>> >          }
>> > -
>> > -        qio_channel_wait_cond(ioc, G_IO_IN);
>> >      }
>> >
>> >      return 0;
>> >
>> > Any preference?
>> >
>> > Thanks,
>> >
>> >> 
>> >> > +            /*
>> >> > +             * QIO_CHANNEL_ERR_BLOCK also shouldn't happen, due to the
>> >> > +             * prior peek just happened.  We should be pretty sure we will
>> >> > +             * read what we peeked, or the channel was broken.
>> >> > +             */
>> >> > +            if (len != received) {
>> >> > +                return -1;
>> >> > +            }
>> >> > +            iov.iov_base += received;
>> >> > +            iov.iov_len = buflen - received;
>> >> > +        }
>> >> >      }
>> >> >  
>> >> >      return 0;
>> >> > -- 
>> >> > 2.54.0
>> >> > 
>> >> 
>> >> With regards,
>> >> Daniel
>> >> -- 
>> >> |: https://berrange.com       ~~        https://hachyderm.io/@berrange :|
>> >> |: https://libvirt.org          ~~          https://entangle-photo.org :|
>> >> |: https://pixelfed.art/berrange   ~~    https://fstop138.berrange.com :|
>> >> 
>>
lmpx.com only provides a reader for public news (NNTP) servers. It is not affiliated with the servers or forums shown here and is not responsible for the content of articles, which is written by their respective authors.