diff mbox series

[v2,09/22] migration/block-dirty-bitmap: relax error handling in incoming part

Message ID 20200217150246.29180-10-vsementsov@virtuozzo.com
State New
Headers show
Series Fix error handling during bitmap postcopy | expand

Commit Message

Vladimir Sementsov-Ogievskiy Feb. 17, 2020, 3:02 p.m. UTC
Bitmaps data is not critical, and we should not fail the migration (or
use postcopy recovering) because of dirty-bitmaps migration failure.
Instead we should just lose unfinished bitmaps.

Still we have to report io stream violation errors, as they affect the
whole migration stream.

Signed-off-by: Vladimir Sementsov-Ogievskiy <vsementsov@virtuozzo.com>
---
 migration/block-dirty-bitmap.c | 148 +++++++++++++++++++++++++--------
 1 file changed, 113 insertions(+), 35 deletions(-)

Comments

Andrey Shinkevich Feb. 18, 2020, 6:54 p.m. UTC | #1
On 17/02/2020 18:02, Vladimir Sementsov-Ogievskiy wrote:
> Bitmaps data is not critical, and we should not fail the migration (or
> use postcopy recovering) because of dirty-bitmaps migration failure.
> Instead we should just lose unfinished bitmaps.
> 
> Still we have to report io stream violation errors, as they affect the
> whole migration stream.
> 
> Signed-off-by: Vladimir Sementsov-Ogievskiy <vsementsov@virtuozzo.com>
> ---
>   migration/block-dirty-bitmap.c | 148 +++++++++++++++++++++++++--------
>   1 file changed, 113 insertions(+), 35 deletions(-)
> 
> diff --git a/migration/block-dirty-bitmap.c b/migration/block-dirty-bitmap.c
> index 1329db8d7d..aea5326804 100644
> --- a/migration/block-dirty-bitmap.c
> +++ b/migration/block-dirty-bitmap.c
> @@ -145,6 +145,15 @@ typedef struct DBMLoadState {
>   
>       bool before_vm_start_handled; /* set in dirty_bitmap_mig_before_vm_start */
>   
> +    /*
> +     * cancelled
> +     * Incoming migration is cancelled for some reason. That means that we
> +     * still should read our chunks from migration stream, to not affect other
> +     * migration objects (like RAM), but just ignore them and do not touch any
> +     * bitmaps or nodes.
> +     */
> +    bool cancelled;
> +
>       GSList *bitmaps;
>       QemuMutex lock; /* protect bitmaps */
>   } DBMLoadState;
> @@ -545,13 +554,47 @@ void dirty_bitmap_mig_before_vm_start(void)
>       qemu_mutex_unlock(&s->lock);
>   }
>   
> +static void cancel_incoming_locked(DBMLoadState *s)
> +{
> +    GSList *item;
> +
> +    if (s->cancelled) {
> +        return;
> +    }
> +
> +    s->cancelled = true;
> +    s->bs = NULL;
> +    s->bitmap = NULL;
> +
> +    /* Drop all unfinished bitmaps */
> +    for (item = s->bitmaps; item; item = g_slist_next(item)) {
> +        LoadBitmapState *b = item->data;
> +
> +        /*
> +         * Bitmap must be unfinished, as finished bitmaps should already be
> +         * removed from the list.
> +         */
> +        assert(!s->before_vm_start_handled || !b->migrated);
> +        if (bdrv_dirty_bitmap_has_successor(b->bitmap)) {
> +            bdrv_reclaim_dirty_bitmap(b->bitmap, &error_abort);
> +        }
> +        bdrv_release_dirty_bitmap(b->bitmap);
> +    }
> +
> +    g_slist_free_full(s->bitmaps, g_free);
> +    s->bitmaps = NULL;
> +}
> +
>   static void dirty_bitmap_load_complete(QEMUFile *f, DBMLoadState *s)
>   {
>       GSList *item;
>       trace_dirty_bitmap_load_complete();
> -    bdrv_dirty_bitmap_deserialize_finish(s->bitmap);
>   
> -    qemu_mutex_lock(&s->lock);

Why is it safe to remove the critical section?

> +    if (s->cancelled) {
> +        return;
> +    }
> +
> +    bdrv_dirty_bitmap_deserialize_finish(s->bitmap);
>   
>       if (bdrv_dirty_bitmap_has_successor(s->bitmap)) {
>           bdrv_reclaim_dirty_bitmap(s->bitmap, &error_abort);
> @@ -569,8 +612,6 @@ static void dirty_bitmap_load_complete(QEMUFile *f, DBMLoadState *s)
>               break;
>           }
>       }
> -
> -    qemu_mutex_unlock(&s->lock);
>   }
>   
>   static int dirty_bitmap_load_bits(QEMUFile *f, DBMLoadState *s)
> @@ -582,15 +623,32 @@ static int dirty_bitmap_load_bits(QEMUFile *f, DBMLoadState *s)
>   
>       if (s->flags & DIRTY_BITMAP_MIG_FLAG_ZEROES) {
>           trace_dirty_bitmap_load_bits_zeroes();
> -        bdrv_dirty_bitmap_deserialize_zeroes(s->bitmap, first_byte, nr_bytes,
> -                                             false);
> +        if (!s->cancelled) {
> +            bdrv_dirty_bitmap_deserialize_zeroes(s->bitmap, first_byte,
> +                                                 nr_bytes, false);
> +        }
>       } else {
>           size_t ret;
>           uint8_t *buf;
>           uint64_t buf_size = qemu_get_be64(f);
> -        uint64_t needed_size =
> -            bdrv_dirty_bitmap_serialization_size(s->bitmap,
> -                                                 first_byte, nr_bytes);
> +        uint64_t needed_size;
> +
> +        buf = g_malloc(buf_size);
> +        ret = qemu_get_buffer(f, buf, buf_size);
> +        if (ret != buf_size) {
> +            error_report("Failed to read bitmap bits");
> +            g_free(buf);
> +            return -EIO;
> +        }
> +
> +        if (s->cancelled) {
> +            g_free(buf);
> +            return 0;
> +        }
> +
> +        needed_size = bdrv_dirty_bitmap_serialization_size(s->bitmap,
> +                                                           first_byte,
> +                                                           nr_bytes);
>   
>           if (needed_size > buf_size ||
>               buf_size > QEMU_ALIGN_UP(needed_size, 4 * sizeof(long))
> @@ -599,15 +657,8 @@ static int dirty_bitmap_load_bits(QEMUFile *f, DBMLoadState *s)
>               error_report("Migrated bitmap granularity doesn't "
>                            "match the destination bitmap '%s' granularity",
>                            bdrv_dirty_bitmap_name(s->bitmap));
> -            return -EINVAL;
> -        }
> -
> -        buf = g_malloc(buf_size);
> -        ret = qemu_get_buffer(f, buf, buf_size);
> -        if (ret != buf_size) {
> -            error_report("Failed to read bitmap bits");
> -            g_free(buf);
> -            return -EIO;
> +            cancel_incoming_locked(s);

                /* Continue the VM migration as bitmaps data are not 
critical */

> +            return 0;
>           }
>   
>           bdrv_dirty_bitmap_deserialize_part(s->bitmap, buf, first_byte, nr_bytes,
> @@ -632,14 +683,16 @@ static int dirty_bitmap_load_header(QEMUFile *f, DBMLoadState *s)
>               error_report("Unable to read node name string");
>               return -EINVAL;
>           }
> -        s->bs = bdrv_lookup_bs(s->node_name, s->node_name, &local_err);
> -        if (!s->bs) {
> -            error_report_err(local_err);
> -            return -EINVAL;
> +        if (!s->cancelled) {
> +            s->bs = bdrv_lookup_bs(s->node_name, s->node_name, &local_err);
> +            if (!s->bs) {
> +                error_report_err(local_err);

The error message may be supplemented with a report about the canceled 
bitmap migration. Also down there at cancel_incoming_locked(s).

> +                cancel_incoming_locked(s);
> +            }
>           }
> -    } else if (!s->bs && !nothing) {
> +    } else if (!s->bs && !nothing && !s->cancelled) {
>           error_report("Error: block device name is not set");
> -        return -EINVAL;
> +        cancel_incoming_locked(s);
>       }
>   
>       if (s->flags & DIRTY_BITMAP_MIG_FLAG_BITMAP_NAME) {
> @@ -647,24 +700,38 @@ static int dirty_bitmap_load_header(QEMUFile *f, DBMLoadState *s)
>               error_report("Unable to read bitmap name string");
>               return -EINVAL;
>           }
> -        s->bitmap = bdrv_find_dirty_bitmap(s->bs, s->bitmap_name);
> -
> -        /* bitmap may be NULL here, it wouldn't be an error if it is the
> -         * first occurrence of the bitmap */
> -        if (!s->bitmap && !(s->flags & DIRTY_BITMAP_MIG_FLAG_START)) {
> -            error_report("Error: unknown dirty bitmap "
> -                         "'%s' for block device '%s'",
> -                         s->bitmap_name, s->node_name);
> -            return -EINVAL;
> +        if (!s->cancelled) {
> +            s->bitmap = bdrv_find_dirty_bitmap(s->bs, s->bitmap_name);
> +
> +            /*
> +             * bitmap may be NULL here, it wouldn't be an error if it is the
> +             * first occurrence of the bitmap
> +             */
> +            if (!s->bitmap && !(s->flags & DIRTY_BITMAP_MIG_FLAG_START)) {
> +                error_report("Error: unknown dirty bitmap "
> +                             "'%s' for block device '%s'",
> +                             s->bitmap_name, s->node_name);
> +                cancel_incoming_locked(s);
> +            }
>           }
> -    } else if (!s->bitmap && !nothing) {
> +    } else if (!s->bitmap && !nothing && !s->cancelled) {
>           error_report("Error: block device name is not set");
> -        return -EINVAL;
> +        cancel_incoming_locked(s);
>       }
>   
>       return 0;
>   }
>   
> +/*
> + * dirty_bitmap_load
> + *
> + * Load sequence of dirty bitmap chunks. Return error only on fatal io stream
> + * violations. On other errors just cancel bitmaps incoming migration and return
> + * 0.
> + *
> + * Note, than when incoming bitmap migration is canceled, we still must read all
"than (that)" may be omitted

> + * our chunks (and just ignore them), to not affect other migration objects.
> + */
>   static int dirty_bitmap_load(QEMUFile *f, void *opaque, int version_id)
>   {
>       DBMLoadState *s = &((DBMState *)opaque)->load;
> @@ -673,12 +740,19 @@ static int dirty_bitmap_load(QEMUFile *f, void *opaque, int version_id)
>       trace_dirty_bitmap_load_enter();
>   
>       if (version_id != 1) {
> +        qemu_mutex_lock(&s->lock);
> +        cancel_incoming_locked(s);
> +        qemu_mutex_unlock(&s->lock);
>           return -EINVAL;
>       }
>   
>       do {
> +        qemu_mutex_lock(&s->lock);
> +
>           ret = dirty_bitmap_load_header(f, s);
>           if (ret < 0) {
> +            cancel_incoming_locked(s);
> +            qemu_mutex_unlock(&s->lock);
>               return ret;
>           }
>   
> @@ -695,8 +769,12 @@ static int dirty_bitmap_load(QEMUFile *f, void *opaque, int version_id)
>           }
>   
>           if (ret) {
> +            cancel_incoming_locked(s);
> +            qemu_mutex_unlock(&s->lock);
>               return ret;
>           }
> +
> +        qemu_mutex_unlock(&s->lock);
>       } while (!(s->flags & DIRTY_BITMAP_MIG_FLAG_EOS));
>   
>       trace_dirty_bitmap_load_success();
> 

Reviewed-by: Andrey Shinkevich <andrey.shinkevich@virtuozzo.com>
Vladimir Sementsov-Ogievskiy Feb. 19, 2020, 3:34 p.m. UTC | #2
18.02.2020 21:54, Andrey Shinkevich wrote:
> 
> 
> On 17/02/2020 18:02, Vladimir Sementsov-Ogievskiy wrote:
>> Bitmaps data is not critical, and we should not fail the migration (or
>> use postcopy recovering) because of dirty-bitmaps migration failure.
>> Instead we should just lose unfinished bitmaps.
>>
>> Still we have to report io stream violation errors, as they affect the
>> whole migration stream.
>>
>> Signed-off-by: Vladimir Sementsov-Ogievskiy <vsementsov@virtuozzo.com>
>> ---
>>   migration/block-dirty-bitmap.c | 148 +++++++++++++++++++++++++--------
>>   1 file changed, 113 insertions(+), 35 deletions(-)
>>
>> diff --git a/migration/block-dirty-bitmap.c b/migration/block-dirty-bitmap.c
>> index 1329db8d7d..aea5326804 100644
>> --- a/migration/block-dirty-bitmap.c
>> +++ b/migration/block-dirty-bitmap.c
>> @@ -145,6 +145,15 @@ typedef struct DBMLoadState {
>>       bool before_vm_start_handled; /* set in dirty_bitmap_mig_before_vm_start */
>> +    /*
>> +     * cancelled
>> +     * Incoming migration is cancelled for some reason. That means that we
>> +     * still should read our chunks from migration stream, to not affect other
>> +     * migration objects (like RAM), but just ignore them and do not touch any
>> +     * bitmaps or nodes.
>> +     */
>> +    bool cancelled;
>> +
>>       GSList *bitmaps;
>>       QemuMutex lock; /* protect bitmaps */
>>   } DBMLoadState;
>> @@ -545,13 +554,47 @@ void dirty_bitmap_mig_before_vm_start(void)
>>       qemu_mutex_unlock(&s->lock);
>>   }
>> +static void cancel_incoming_locked(DBMLoadState *s)
>> +{
>> +    GSList *item;
>> +
>> +    if (s->cancelled) {
>> +        return;
>> +    }
>> +
>> +    s->cancelled = true;
>> +    s->bs = NULL;
>> +    s->bitmap = NULL;
>> +
>> +    /* Drop all unfinished bitmaps */
>> +    for (item = s->bitmaps; item; item = g_slist_next(item)) {
>> +        LoadBitmapState *b = item->data;
>> +
>> +        /*
>> +         * Bitmap must be unfinished, as finished bitmaps should already be
>> +         * removed from the list.
>> +         */
>> +        assert(!s->before_vm_start_handled || !b->migrated);
>> +        if (bdrv_dirty_bitmap_has_successor(b->bitmap)) {
>> +            bdrv_reclaim_dirty_bitmap(b->bitmap, &error_abort);
>> +        }
>> +        bdrv_release_dirty_bitmap(b->bitmap);
>> +    }
>> +
>> +    g_slist_free_full(s->bitmaps, g_free);
>> +    s->bitmaps = NULL;
>> +}
>> +
>>   static void dirty_bitmap_load_complete(QEMUFile *f, DBMLoadState *s)
>>   {
>>       GSList *item;
>>       trace_dirty_bitmap_load_complete();
>> -    bdrv_dirty_bitmap_deserialize_finish(s->bitmap);
>> -    qemu_mutex_lock(&s->lock);
> 
> Why is it safe to remove the critical section?

It's not removed, it becomes wider in this patch.

> 
>> +    if (s->cancelled) {
>> +        return;
>> +    }
>> +
>> +    bdrv_dirty_bitmap_deserialize_finish(s->bitmap);
>>       if (bdrv_dirty_bitmap_has_successor(s->bitmap)) {
>>           bdrv_reclaim_dirty_bitmap(s->bitmap, &error_abort);
>> @@ -569,8 +612,6 @@ static void dirty_bitmap_load_complete(QEMUFile *f, DBMLoadState *s)
>>               break;
>>           }
>>       }
>> -
>> -    qemu_mutex_unlock(&s->lock);
>>   }
>>   static int dirty_bitmap_load_bits(QEMUFile *f, DBMLoadState *s)
>> @@ -582,15 +623,32 @@ static int dirty_bitmap_load_bits(QEMUFile *f, DBMLoadState *s)
>>       if (s->flags & DIRTY_BITMAP_MIG_FLAG_ZEROES) {
>>           trace_dirty_bitmap_load_bits_zeroes();
>> -        bdrv_dirty_bitmap_deserialize_zeroes(s->bitmap, first_byte, nr_bytes,
>> -                                             false);
>> +        if (!s->cancelled) {
>> +            bdrv_dirty_bitmap_deserialize_zeroes(s->bitmap, first_byte,
>> +                                                 nr_bytes, false);
>> +        }
>>       } else {
>>           size_t ret;
>>           uint8_t *buf;
>>           uint64_t buf_size = qemu_get_be64(f);
>> -        uint64_t needed_size =
>> -            bdrv_dirty_bitmap_serialization_size(s->bitmap,
>> -                                                 first_byte, nr_bytes);
>> +        uint64_t needed_size;
>> +
>> +        buf = g_malloc(buf_size);
>> +        ret = qemu_get_buffer(f, buf, buf_size);
>> +        if (ret != buf_size) {
>> +            error_report("Failed to read bitmap bits");
>> +            g_free(buf);
>> +            return -EIO;
>> +        }
>> +
>> +        if (s->cancelled) {
>> +            g_free(buf);
>> +            return 0;
>> +        }
>> +
>> +        needed_size = bdrv_dirty_bitmap_serialization_size(s->bitmap,
>> +                                                           first_byte,
>> +                                                           nr_bytes);
>>           if (needed_size > buf_size ||
>>               buf_size > QEMU_ALIGN_UP(needed_size, 4 * sizeof(long))
>> @@ -599,15 +657,8 @@ static int dirty_bitmap_load_bits(QEMUFile *f, DBMLoadState *s)
>>               error_report("Migrated bitmap granularity doesn't "
>>                            "match the destination bitmap '%s' granularity",
>>                            bdrv_dirty_bitmap_name(s->bitmap));
>> -            return -EINVAL;
>> -        }
>> -
>> -        buf = g_malloc(buf_size);
>> -        ret = qemu_get_buffer(f, buf, buf_size);
>> -        if (ret != buf_size) {
>> -            error_report("Failed to read bitmap bits");
>> -            g_free(buf);
>> -            return -EIO;
>> +            cancel_incoming_locked(s);
> 
>                 /* Continue the VM migration as bitmaps data are not critical */

Hmm yes it what this patch does.. But I don't think we should add comment to each call of cancel_..()

> 
>> +            return 0;
>>           }
>>           bdrv_dirty_bitmap_deserialize_part(s->bitmap, buf, first_byte, nr_bytes,
>> @@ -632,14 +683,16 @@ static int dirty_bitmap_load_header(QEMUFile *f, DBMLoadState *s)
>>               error_report("Unable to read node name string");
>>               return -EINVAL;
>>           }
>> -        s->bs = bdrv_lookup_bs(s->node_name, s->node_name, &local_err);
>> -        if (!s->bs) {
>> -            error_report_err(local_err);
>> -            return -EINVAL;
>> +        if (!s->cancelled) {
>> +            s->bs = bdrv_lookup_bs(s->node_name, s->node_name, &local_err);
>> +            if (!s->bs) {
>> +                error_report_err(local_err);
> 
> The error message may be supplemented with a report about the canceled bitmap migration. Also down there at cancel_incoming_locked(s).
> 
>> +                cancel_incoming_locked(s);
>> +            }
>>           }
>> -    } else if (!s->bs && !nothing) {
>> +    } else if (!s->bs && !nothing && !s->cancelled) {
>>           error_report("Error: block device name is not set");
>> -        return -EINVAL;
>> +        cancel_incoming_locked(s);
>>       }
>>       if (s->flags & DIRTY_BITMAP_MIG_FLAG_BITMAP_NAME) {
>> @@ -647,24 +700,38 @@ static int dirty_bitmap_load_header(QEMUFile *f, DBMLoadState *s)
>>               error_report("Unable to read bitmap name string");
>>               return -EINVAL;
>>           }
>> -        s->bitmap = bdrv_find_dirty_bitmap(s->bs, s->bitmap_name);
>> -
>> -        /* bitmap may be NULL here, it wouldn't be an error if it is the
>> -         * first occurrence of the bitmap */
>> -        if (!s->bitmap && !(s->flags & DIRTY_BITMAP_MIG_FLAG_START)) {
>> -            error_report("Error: unknown dirty bitmap "
>> -                         "'%s' for block device '%s'",
>> -                         s->bitmap_name, s->node_name);
>> -            return -EINVAL;
>> +        if (!s->cancelled) {
>> +            s->bitmap = bdrv_find_dirty_bitmap(s->bs, s->bitmap_name);
>> +
>> +            /*
>> +             * bitmap may be NULL here, it wouldn't be an error if it is the
>> +             * first occurrence of the bitmap
>> +             */
>> +            if (!s->bitmap && !(s->flags & DIRTY_BITMAP_MIG_FLAG_START)) {
>> +                error_report("Error: unknown dirty bitmap "
>> +                             "'%s' for block device '%s'",
>> +                             s->bitmap_name, s->node_name);
>> +                cancel_incoming_locked(s);
>> +            }
>>           }
>> -    } else if (!s->bitmap && !nothing) {
>> +    } else if (!s->bitmap && !nothing && !s->cancelled) {
>>           error_report("Error: block device name is not set");
>> -        return -EINVAL;
>> +        cancel_incoming_locked(s);
>>       }
>>       return 0;
>>   }
>> +/*
>> + * dirty_bitmap_load
>> + *
>> + * Load sequence of dirty bitmap chunks. Return error only on fatal io stream
>> + * violations. On other errors just cancel bitmaps incoming migration and return
>> + * 0.
>> + *
>> + * Note, than when incoming bitmap migration is canceled, we still must read all
> "than (that)" may be omitted
> 
>> + * our chunks (and just ignore them), to not affect other migration objects.
>> + */
>>   static int dirty_bitmap_load(QEMUFile *f, void *opaque, int version_id)
>>   {
>>       DBMLoadState *s = &((DBMState *)opaque)->load;
>> @@ -673,12 +740,19 @@ static int dirty_bitmap_load(QEMUFile *f, void *opaque, int version_id)
>>       trace_dirty_bitmap_load_enter();
>>       if (version_id != 1) {
>> +        qemu_mutex_lock(&s->lock);
>> +        cancel_incoming_locked(s);
>> +        qemu_mutex_unlock(&s->lock);
>>           return -EINVAL;
>>       }
>>       do {
>> +        qemu_mutex_lock(&s->lock);
>> +
>>           ret = dirty_bitmap_load_header(f, s);
>>           if (ret < 0) {
>> +            cancel_incoming_locked(s);
>> +            qemu_mutex_unlock(&s->lock);
>>               return ret;
>>           }
>> @@ -695,8 +769,12 @@ static int dirty_bitmap_load(QEMUFile *f, void *opaque, int version_id)
>>           }
>>           if (ret) {
>> +            cancel_incoming_locked(s);
>> +            qemu_mutex_unlock(&s->lock);
>>               return ret;
>>           }
>> +
>> +        qemu_mutex_unlock(&s->lock);
>>       } while (!(s->flags & DIRTY_BITMAP_MIG_FLAG_EOS));
>>       trace_dirty_bitmap_load_success();
>>
> 
> Reviewed-by: Andrey Shinkevich <andrey.shinkevich@virtuozzo.com>
Vladimir Sementsov-Ogievskiy July 24, 2020, 7:23 a.m. UTC | #3
19.02.2020 18:34, Vladimir Sementsov-Ogievskiy wrote:
> 18.02.2020 21:54, Andrey Shinkevich wrote:
>>
>>
>> On 17/02/2020 18:02, Vladimir Sementsov-Ogievskiy wrote:
>>> Bitmaps data is not critical, and we should not fail the migration (or
>>> use postcopy recovering) because of dirty-bitmaps migration failure.
>>> Instead we should just lose unfinished bitmaps.
>>>
>>> Still we have to report io stream violation errors, as they affect the
>>> whole migration stream.
>>>
>>> Signed-off-by: Vladimir Sementsov-Ogievskiy <vsementsov@virtuozzo.com>
>>> ---
>>>   migration/block-dirty-bitmap.c | 148 +++++++++++++++++++++++++--------
>>>   1 file changed, 113 insertions(+), 35 deletions(-)
>>>
>>> diff --git a/migration/block-dirty-bitmap.c b/migration/block-dirty-bitmap.c
>>> index 1329db8d7d..aea5326804 100644
>>> --- a/migration/block-dirty-bitmap.c
>>> +++ b/migration/block-dirty-bitmap.c
>>> @@ -145,6 +145,15 @@ typedef struct DBMLoadState {
>>>       bool before_vm_start_handled; /* set in dirty_bitmap_mig_before_vm_start */
>>> +    /*
>>> +     * cancelled
>>> +     * Incoming migration is cancelled for some reason. That means that we
>>> +     * still should read our chunks from migration stream, to not affect other
>>> +     * migration objects (like RAM), but just ignore them and do not touch any
>>> +     * bitmaps or nodes.
>>> +     */
>>> +    bool cancelled;
>>> +
>>>       GSList *bitmaps;
>>>       QemuMutex lock; /* protect bitmaps */
>>>   } DBMLoadState;
>>> @@ -545,13 +554,47 @@ void dirty_bitmap_mig_before_vm_start(void)
>>>       qemu_mutex_unlock(&s->lock);
>>>   }
>>> +static void cancel_incoming_locked(DBMLoadState *s)
>>> +{
>>> +    GSList *item;
>>> +
>>> +    if (s->cancelled) {
>>> +        return;
>>> +    }
>>> +
>>> +    s->cancelled = true;
>>> +    s->bs = NULL;
>>> +    s->bitmap = NULL;
>>> +
>>> +    /* Drop all unfinished bitmaps */
>>> +    for (item = s->bitmaps; item; item = g_slist_next(item)) {
>>> +        LoadBitmapState *b = item->data;
>>> +
>>> +        /*
>>> +         * Bitmap must be unfinished, as finished bitmaps should already be
>>> +         * removed from the list.
>>> +         */
>>> +        assert(!s->before_vm_start_handled || !b->migrated);
>>> +        if (bdrv_dirty_bitmap_has_successor(b->bitmap)) {
>>> +            bdrv_reclaim_dirty_bitmap(b->bitmap, &error_abort);
>>> +        }
>>> +        bdrv_release_dirty_bitmap(b->bitmap);
>>> +    }
>>> +
>>> +    g_slist_free_full(s->bitmaps, g_free);
>>> +    s->bitmaps = NULL;
>>> +}
>>> +
>>>   static void dirty_bitmap_load_complete(QEMUFile *f, DBMLoadState *s)
>>>   {
>>>       GSList *item;
>>>       trace_dirty_bitmap_load_complete();
>>> -    bdrv_dirty_bitmap_deserialize_finish(s->bitmap);
>>> -    qemu_mutex_lock(&s->lock);
>>
>> Why is it safe to remove the critical section?
> 
> It's not removed, it becomes wider in this patch.
> 
>>
>>> +    if (s->cancelled) {
>>> +        return;
>>> +    }
>>> +
>>> +    bdrv_dirty_bitmap_deserialize_finish(s->bitmap);
>>>       if (bdrv_dirty_bitmap_has_successor(s->bitmap)) {
>>>           bdrv_reclaim_dirty_bitmap(s->bitmap, &error_abort);
>>> @@ -569,8 +612,6 @@ static void dirty_bitmap_load_complete(QEMUFile *f, DBMLoadState *s)
>>>               break;
>>>           }
>>>       }
>>> -
>>> -    qemu_mutex_unlock(&s->lock);
>>>   }
>>>   static int dirty_bitmap_load_bits(QEMUFile *f, DBMLoadState *s)
>>> @@ -582,15 +623,32 @@ static int dirty_bitmap_load_bits(QEMUFile *f, DBMLoadState *s)
>>>       if (s->flags & DIRTY_BITMAP_MIG_FLAG_ZEROES) {
>>>           trace_dirty_bitmap_load_bits_zeroes();
>>> -        bdrv_dirty_bitmap_deserialize_zeroes(s->bitmap, first_byte, nr_bytes,
>>> -                                             false);
>>> +        if (!s->cancelled) {
>>> +            bdrv_dirty_bitmap_deserialize_zeroes(s->bitmap, first_byte,
>>> +                                                 nr_bytes, false);
>>> +        }
>>>       } else {
>>>           size_t ret;
>>>           uint8_t *buf;
>>>           uint64_t buf_size = qemu_get_be64(f);
>>> -        uint64_t needed_size =
>>> -            bdrv_dirty_bitmap_serialization_size(s->bitmap,
>>> -                                                 first_byte, nr_bytes);
>>> +        uint64_t needed_size;
>>> +
>>> +        buf = g_malloc(buf_size);
>>> +        ret = qemu_get_buffer(f, buf, buf_size);
>>> +        if (ret != buf_size) {
>>> +            error_report("Failed to read bitmap bits");
>>> +            g_free(buf);
>>> +            return -EIO;
>>> +        }
>>> +
>>> +        if (s->cancelled) {
>>> +            g_free(buf);
>>> +            return 0;
>>> +        }
>>> +
>>> +        needed_size = bdrv_dirty_bitmap_serialization_size(s->bitmap,
>>> +                                                           first_byte,
>>> +                                                           nr_bytes);
>>>           if (needed_size > buf_size ||
>>>               buf_size > QEMU_ALIGN_UP(needed_size, 4 * sizeof(long))
>>> @@ -599,15 +657,8 @@ static int dirty_bitmap_load_bits(QEMUFile *f, DBMLoadState *s)
>>>               error_report("Migrated bitmap granularity doesn't "
>>>                            "match the destination bitmap '%s' granularity",
>>>                            bdrv_dirty_bitmap_name(s->bitmap));
>>> -            return -EINVAL;
>>> -        }
>>> -
>>> -        buf = g_malloc(buf_size);
>>> -        ret = qemu_get_buffer(f, buf, buf_size);
>>> -        if (ret != buf_size) {
>>> -            error_report("Failed to read bitmap bits");
>>> -            g_free(buf);
>>> -            return -EIO;
>>> +            cancel_incoming_locked(s);
>>
>>                 /* Continue the VM migration as bitmaps data are not critical */
> 
> Hmm yes it what this patch does.. But I don't think we should add comment to each call of cancel_..()
> 
>>
>>> +            return 0;
>>>           }
>>>           bdrv_dirty_bitmap_deserialize_part(s->bitmap, buf, first_byte, nr_bytes,
>>> @@ -632,14 +683,16 @@ static int dirty_bitmap_load_header(QEMUFile *f, DBMLoadState *s)
>>>               error_report("Unable to read node name string");
>>>               return -EINVAL;
>>>           }
>>> -        s->bs = bdrv_lookup_bs(s->node_name, s->node_name, &local_err);
>>> -        if (!s->bs) {
>>> -            error_report_err(local_err);
>>> -            return -EINVAL;
>>> +        if (!s->cancelled) {
>>> +            s->bs = bdrv_lookup_bs(s->node_name, s->node_name, &local_err);
>>> +            if (!s->bs) {
>>> +                error_report_err(local_err);
>>
>> The error message may be supplemented with a report about the canceled bitmap migration. Also down there at cancel_incoming_locked(s).

If we want to log something about cancelling, I think it should be done in cancel_incoming_locked(). I'll keep this patch as is.

>>
>>> +                cancel_incoming_locked(s);
>>> +            }
>>>           }
>>> -    } else if (!s->bs && !nothing) {
>>> +    } else if (!s->bs && !nothing && !s->cancelled) {
>>>           error_report("Error: block device name is not set");
>>> -        return -EINVAL;
>>> +        cancel_incoming_locked(s);
>>>       }
>>>       if (s->flags & DIRTY_BITMAP_MIG_FLAG_BITMAP_NAME) {
>>> @@ -647,24 +700,38 @@ static int dirty_bitmap_load_header(QEMUFile *f, DBMLoadState *s)
>>>               error_report("Unable to read bitmap name string");
>>>               return -EINVAL;
>>>           }
>>> -        s->bitmap = bdrv_find_dirty_bitmap(s->bs, s->bitmap_name);
>>> -
>>> -        /* bitmap may be NULL here, it wouldn't be an error if it is the
>>> -         * first occurrence of the bitmap */
>>> -        if (!s->bitmap && !(s->flags & DIRTY_BITMAP_MIG_FLAG_START)) {
>>> -            error_report("Error: unknown dirty bitmap "
>>> -                         "'%s' for block device '%s'",
>>> -                         s->bitmap_name, s->node_name);
>>> -            return -EINVAL;
>>> +        if (!s->cancelled) {
>>> +            s->bitmap = bdrv_find_dirty_bitmap(s->bs, s->bitmap_name);
>>> +
>>> +            /*
>>> +             * bitmap may be NULL here, it wouldn't be an error if it is the
>>> +             * first occurrence of the bitmap
>>> +             */
>>> +            if (!s->bitmap && !(s->flags & DIRTY_BITMAP_MIG_FLAG_START)) {
>>> +                error_report("Error: unknown dirty bitmap "
>>> +                             "'%s' for block device '%s'",
>>> +                             s->bitmap_name, s->node_name);
>>> +                cancel_incoming_locked(s);
>>> +            }
>>>           }
>>> -    } else if (!s->bitmap && !nothing) {
>>> +    } else if (!s->bitmap && !nothing && !s->cancelled) {
>>>           error_report("Error: block device name is not set");
>>> -        return -EINVAL;
>>> +        cancel_incoming_locked(s);
>>>       }
>>>       return 0;
>>>   }
>>> +/*
>>> + * dirty_bitmap_load
>>> + *
>>> + * Load sequence of dirty bitmap chunks. Return error only on fatal io stream
>>> + * violations. On other errors just cancel bitmaps incoming migration and return
>>> + * 0.
>>> + *
>>> + * Note, than when incoming bitmap migration is canceled, we still must read all
>> "than (that)" may be omitted
>>
>>> + * our chunks (and just ignore them), to not affect other migration objects.
>>> + */
>>>   static int dirty_bitmap_load(QEMUFile *f, void *opaque, int version_id)
>>>   {
>>>       DBMLoadState *s = &((DBMState *)opaque)->load;
>>> @@ -673,12 +740,19 @@ static int dirty_bitmap_load(QEMUFile *f, void *opaque, int version_id)
>>>       trace_dirty_bitmap_load_enter();
>>>       if (version_id != 1) {
>>> +        qemu_mutex_lock(&s->lock);
>>> +        cancel_incoming_locked(s);
>>> +        qemu_mutex_unlock(&s->lock);
>>>           return -EINVAL;
>>>       }
>>>       do {
>>> +        qemu_mutex_lock(&s->lock);
>>> +
>>>           ret = dirty_bitmap_load_header(f, s);
>>>           if (ret < 0) {
>>> +            cancel_incoming_locked(s);
>>> +            qemu_mutex_unlock(&s->lock);
>>>               return ret;
>>>           }
>>> @@ -695,8 +769,12 @@ static int dirty_bitmap_load(QEMUFile *f, void *opaque, int version_id)
>>>           }
>>>           if (ret) {
>>> +            cancel_incoming_locked(s);
>>> +            qemu_mutex_unlock(&s->lock);
>>>               return ret;
>>>           }
>>> +
>>> +        qemu_mutex_unlock(&s->lock);
>>>       } while (!(s->flags & DIRTY_BITMAP_MIG_FLAG_EOS));
>>>       trace_dirty_bitmap_load_success();
>>>
>>
>> Reviewed-by: Andrey Shinkevich <andrey.shinkevich@virtuozzo.com>
> 
>
diff mbox series

Patch

diff --git a/migration/block-dirty-bitmap.c b/migration/block-dirty-bitmap.c
index 1329db8d7d..aea5326804 100644
--- a/migration/block-dirty-bitmap.c
+++ b/migration/block-dirty-bitmap.c
@@ -145,6 +145,15 @@  typedef struct DBMLoadState {
 
     bool before_vm_start_handled; /* set in dirty_bitmap_mig_before_vm_start */
 
+    /*
+     * cancelled
+     * Incoming migration is cancelled for some reason. That means that we
+     * still should read our chunks from migration stream, to not affect other
+     * migration objects (like RAM), but just ignore them and do not touch any
+     * bitmaps or nodes.
+     */
+    bool cancelled;
+
     GSList *bitmaps;
     QemuMutex lock; /* protect bitmaps */
 } DBMLoadState;
@@ -545,13 +554,47 @@  void dirty_bitmap_mig_before_vm_start(void)
     qemu_mutex_unlock(&s->lock);
 }
 
+static void cancel_incoming_locked(DBMLoadState *s)
+{
+    GSList *item;
+
+    if (s->cancelled) {
+        return;
+    }
+
+    s->cancelled = true;
+    s->bs = NULL;
+    s->bitmap = NULL;
+
+    /* Drop all unfinished bitmaps */
+    for (item = s->bitmaps; item; item = g_slist_next(item)) {
+        LoadBitmapState *b = item->data;
+
+        /*
+         * Bitmap must be unfinished, as finished bitmaps should already be
+         * removed from the list.
+         */
+        assert(!s->before_vm_start_handled || !b->migrated);
+        if (bdrv_dirty_bitmap_has_successor(b->bitmap)) {
+            bdrv_reclaim_dirty_bitmap(b->bitmap, &error_abort);
+        }
+        bdrv_release_dirty_bitmap(b->bitmap);
+    }
+
+    g_slist_free_full(s->bitmaps, g_free);
+    s->bitmaps = NULL;
+}
+
 static void dirty_bitmap_load_complete(QEMUFile *f, DBMLoadState *s)
 {
     GSList *item;
     trace_dirty_bitmap_load_complete();
-    bdrv_dirty_bitmap_deserialize_finish(s->bitmap);
 
-    qemu_mutex_lock(&s->lock);
+    if (s->cancelled) {
+        return;
+    }
+
+    bdrv_dirty_bitmap_deserialize_finish(s->bitmap);
 
     if (bdrv_dirty_bitmap_has_successor(s->bitmap)) {
         bdrv_reclaim_dirty_bitmap(s->bitmap, &error_abort);
@@ -569,8 +612,6 @@  static void dirty_bitmap_load_complete(QEMUFile *f, DBMLoadState *s)
             break;
         }
     }
-
-    qemu_mutex_unlock(&s->lock);
 }
 
 static int dirty_bitmap_load_bits(QEMUFile *f, DBMLoadState *s)
@@ -582,15 +623,32 @@  static int dirty_bitmap_load_bits(QEMUFile *f, DBMLoadState *s)
 
     if (s->flags & DIRTY_BITMAP_MIG_FLAG_ZEROES) {
         trace_dirty_bitmap_load_bits_zeroes();
-        bdrv_dirty_bitmap_deserialize_zeroes(s->bitmap, first_byte, nr_bytes,
-                                             false);
+        if (!s->cancelled) {
+            bdrv_dirty_bitmap_deserialize_zeroes(s->bitmap, first_byte,
+                                                 nr_bytes, false);
+        }
     } else {
         size_t ret;
         uint8_t *buf;
         uint64_t buf_size = qemu_get_be64(f);
-        uint64_t needed_size =
-            bdrv_dirty_bitmap_serialization_size(s->bitmap,
-                                                 first_byte, nr_bytes);
+        uint64_t needed_size;
+
+        buf = g_malloc(buf_size);
+        ret = qemu_get_buffer(f, buf, buf_size);
+        if (ret != buf_size) {
+            error_report("Failed to read bitmap bits");
+            g_free(buf);
+            return -EIO;
+        }
+
+        if (s->cancelled) {
+            g_free(buf);
+            return 0;
+        }
+
+        needed_size = bdrv_dirty_bitmap_serialization_size(s->bitmap,
+                                                           first_byte,
+                                                           nr_bytes);
 
         if (needed_size > buf_size ||
             buf_size > QEMU_ALIGN_UP(needed_size, 4 * sizeof(long))
@@ -599,15 +657,8 @@  static int dirty_bitmap_load_bits(QEMUFile *f, DBMLoadState *s)
             error_report("Migrated bitmap granularity doesn't "
                          "match the destination bitmap '%s' granularity",
                          bdrv_dirty_bitmap_name(s->bitmap));
-            return -EINVAL;
-        }
-
-        buf = g_malloc(buf_size);
-        ret = qemu_get_buffer(f, buf, buf_size);
-        if (ret != buf_size) {
-            error_report("Failed to read bitmap bits");
-            g_free(buf);
-            return -EIO;
+            cancel_incoming_locked(s);
+            return 0;
         }
 
         bdrv_dirty_bitmap_deserialize_part(s->bitmap, buf, first_byte, nr_bytes,
@@ -632,14 +683,16 @@  static int dirty_bitmap_load_header(QEMUFile *f, DBMLoadState *s)
             error_report("Unable to read node name string");
             return -EINVAL;
         }
-        s->bs = bdrv_lookup_bs(s->node_name, s->node_name, &local_err);
-        if (!s->bs) {
-            error_report_err(local_err);
-            return -EINVAL;
+        if (!s->cancelled) {
+            s->bs = bdrv_lookup_bs(s->node_name, s->node_name, &local_err);
+            if (!s->bs) {
+                error_report_err(local_err);
+                cancel_incoming_locked(s);
+            }
         }
-    } else if (!s->bs && !nothing) {
+    } else if (!s->bs && !nothing && !s->cancelled) {
         error_report("Error: block device name is not set");
-        return -EINVAL;
+        cancel_incoming_locked(s);
     }
 
     if (s->flags & DIRTY_BITMAP_MIG_FLAG_BITMAP_NAME) {
@@ -647,24 +700,38 @@  static int dirty_bitmap_load_header(QEMUFile *f, DBMLoadState *s)
             error_report("Unable to read bitmap name string");
             return -EINVAL;
         }
-        s->bitmap = bdrv_find_dirty_bitmap(s->bs, s->bitmap_name);
-
-        /* bitmap may be NULL here, it wouldn't be an error if it is the
-         * first occurrence of the bitmap */
-        if (!s->bitmap && !(s->flags & DIRTY_BITMAP_MIG_FLAG_START)) {
-            error_report("Error: unknown dirty bitmap "
-                         "'%s' for block device '%s'",
-                         s->bitmap_name, s->node_name);
-            return -EINVAL;
+        if (!s->cancelled) {
+            s->bitmap = bdrv_find_dirty_bitmap(s->bs, s->bitmap_name);
+
+            /*
+             * bitmap may be NULL here, it wouldn't be an error if it is the
+             * first occurrence of the bitmap
+             */
+            if (!s->bitmap && !(s->flags & DIRTY_BITMAP_MIG_FLAG_START)) {
+                error_report("Error: unknown dirty bitmap "
+                             "'%s' for block device '%s'",
+                             s->bitmap_name, s->node_name);
+                cancel_incoming_locked(s);
+            }
         }
-    } else if (!s->bitmap && !nothing) {
+    } else if (!s->bitmap && !nothing && !s->cancelled) {
         error_report("Error: block device name is not set");
-        return -EINVAL;
+        cancel_incoming_locked(s);
     }
 
     return 0;
 }
 
+/*
+ * dirty_bitmap_load
+ *
+ * Load sequence of dirty bitmap chunks. Return error only on fatal io stream
+ * violations. On other errors just cancel bitmaps incoming migration and return
+ * 0.
+ *
+ * Note, than when incoming bitmap migration is canceled, we still must read all
+ * our chunks (and just ignore them), to not affect other migration objects.
+ */
 static int dirty_bitmap_load(QEMUFile *f, void *opaque, int version_id)
 {
     DBMLoadState *s = &((DBMState *)opaque)->load;
@@ -673,12 +740,19 @@  static int dirty_bitmap_load(QEMUFile *f, void *opaque, int version_id)
     trace_dirty_bitmap_load_enter();
 
     if (version_id != 1) {
+        qemu_mutex_lock(&s->lock);
+        cancel_incoming_locked(s);
+        qemu_mutex_unlock(&s->lock);
         return -EINVAL;
     }
 
     do {
+        qemu_mutex_lock(&s->lock);
+
         ret = dirty_bitmap_load_header(f, s);
         if (ret < 0) {
+            cancel_incoming_locked(s);
+            qemu_mutex_unlock(&s->lock);
             return ret;
         }
 
@@ -695,8 +769,12 @@  static int dirty_bitmap_load(QEMUFile *f, void *opaque, int version_id)
         }
 
         if (ret) {
+            cancel_incoming_locked(s);
+            qemu_mutex_unlock(&s->lock);
             return ret;
         }
+
+        qemu_mutex_unlock(&s->lock);
     } while (!(s->flags & DIRTY_BITMAP_MIG_FLAG_EOS));
 
     trace_dirty_bitmap_load_success();