diff mbox

[v2] XBZRLE: Fix qemu crash when resize the xbzrle cache

Message ID 33183CC9F5247A488A2544077AF19020815C7EB9@SZXEMA503-MBS.china.huawei.com
State New
Headers show

Commit Message

Gonglei (Arei) Feb. 21, 2014, 2:14 a.m. UTC
Resizing the xbzrle cache during migration causes qemu-crash,
because the main-thread and migration-thread modify the xbzrle
cache size concurrently without lock-protection.

Signed-off-by: ChenLiang <chenliang88@huawei.com>
Signed-off-by: Gonglei <arei.gonglei@huawei.com>
---
Changes against the previous version:
 *Remove function cache_max_num_items and cache_page_size
 *Protect migration_end and ram_save_setup by XBZRLE.lock

 arch_init.c |   61 ++++++++++++++++++++++++++++++++++++++++++++++++++++++----
 1 files changed, 56 insertions(+), 5 deletions(-)

Comments

Dr. David Alan Gilbert Feb. 21, 2014, 11:03 a.m. UTC | #1
* Gonglei (Arei) (arei.gonglei@huawei.com) wrote:
> Resizing the xbzrle cache during migration causes qemu-crash,
> because the main-thread and migration-thread modify the xbzrle
> cache size concurrently without lock-protection.
> 
> Signed-off-by: ChenLiang <chenliang88@huawei.com>
> Signed-off-by: Gonglei <arei.gonglei@huawei.com>
> ---
> Changes against the previous version:
>  *Remove function cache_max_num_items and cache_page_size
>  *Protect migration_end and ram_save_setup by XBZRLE.lock
> 
>  arch_init.c |   61 ++++++++++++++++++++++++++++++++++++++++++++++++++++++----
>  1 files changed, 56 insertions(+), 5 deletions(-)
> 
> diff --git a/arch_init.c b/arch_init.c
> index 80574a0..d295237 100644
> --- a/arch_init.c
> +++ b/arch_init.c
> @@ -164,26 +164,69 @@ static struct {
>      uint8_t *encoded_buf;
>      /* buffer for storing page content */
>      uint8_t *current_buf;
> -    /* Cache for XBZRLE */
> +    /* Cache for XBZRLE, Protected by lock. */
>      PageCache *cache;
> +    QemuMutex lock;
>  } XBZRLE = {
>      .encoded_buf = NULL,
>      .current_buf = NULL,
>      .cache = NULL,
> +    /* use the lock carefully */
> +    .lock = {PTHREAD_MUTEX_INITIALIZER},
>  };
>  /* buffer used for XBZRLE decoding */
>  static uint8_t *xbzrle_decoded_buf;
>  
> +static void XBZRLE_cache_lock(void)
> +{
> +    qemu_mutex_lock(&XBZRLE.lock);
> +}
> +
> +static void XBZRLE_cache_unlock(void)
> +{
> +    qemu_mutex_unlock(&XBZRLE.lock);
> +}
> +

You might want to make these only bother with the lock if xbzrle is enabled 
- however actually, I think it's probably just best to keep them as is,
and simple.

>  int64_t xbzrle_cache_resize(int64_t new_size)
>  {
> +    PageCache *new_cache, *old_cache, *cache;
> +
>      if (new_size < TARGET_PAGE_SIZE) {
>          return -1;
>      }
>  
> -    if (XBZRLE.cache != NULL) {
> -        return cache_resize(XBZRLE.cache, new_size / TARGET_PAGE_SIZE) *
> -            TARGET_PAGE_SIZE;
> +    XBZRLE_cache_lock();
> +    /* check XBZRLE.cache again later */
> +    cache = XBZRLE.cache;
> +    XBZRLE_cache_unlock();

That's a bit of an odd way of atomically reading a pointer;
I think an 
   smp_rmb();
   cache = atomic_read(&XBZRLE.cache);

would have done the trick.

Personally I would have just kept the lock at the top and not released
it until the end of the following section.

> +    if (cache != NULL) {
> +        if (pow2floor(new_size) == migrate_xbzrle_cache_size()) {
> +            goto ret;

Hmm goto not really needed there.

> +        }
> +        new_cache = cache_init(new_size / TARGET_PAGE_SIZE,
> +                                        TARGET_PAGE_SIZE);
> +        if (!new_cache) {
> +            DPRINTF("Error creating cache\n");
> +            return -1;
> +        }
> +
> +        XBZRLE_cache_lock();
> +        /* the XBZRLE.cache may have be destroyed, check it again */
> +        if (XBZRLE.cache != NULL) {
> +            old_cache = XBZRLE.cache;
> +            XBZRLE.cache = new_cache;
> +            new_cache = NULL;
> +        }
> +        XBZRLE_cache_unlock();
> +


> +        if (NULL == new_cache) {
> +            cache_fini(old_cache);
> +        } else {
> +            cache_fini(new_cache);
> +        }

Yes, it took me a few readings to understand that; i.e. if new_cache=NULL then
it's actually now in use in XBZRLE.cache so only the old_cache needs
freeing.

>      }
> +ret:
>      return pow2floor(new_size);
>  }
>  
> @@ -522,6 +565,8 @@ static int ram_save_block(QEMUFile *f, bool last_stage)
>              ret = ram_control_save_page(f, block->offset,
>                                 offset, TARGET_PAGE_SIZE, &bytes_sent);
>  
> +            XBZRLE_cache_lock();
> +
>              if (ret != RAM_SAVE_CONTROL_NOT_SUPP) {
>                  if (ret != RAM_SAVE_CONTROL_DELAYED) {
>                      if (bytes_sent > 0) {
> @@ -553,6 +598,8 @@ static int ram_save_block(QEMUFile *f, bool last_stage)
>                  acct_info.norm_pages++;
>              }
>  
> +            XBZRLE_cache_unlock();
> +
>              /* if page is unmodified, continue to the next */
>              if (bytes_sent > 0) {
>                  last_sent_block = block;

Note that's not actually safe until my other xbzrle patch lands, because
the cache data is currently passed into put_buffer_async that will read it
later (possibly after you've reallocated) - but that's ok, that should
get fixed in the mean time.

> @@ -620,6 +667,7 @@ static void migration_end(void)
>          migration_bitmap = NULL;
>      }
>  
> +    XBZRLE_cache_lock();
>      if (XBZRLE.cache) {
>          cache_fini(XBZRLE.cache);
>          g_free(XBZRLE.cache);
> @@ -629,6 +677,7 @@ static void migration_end(void)
>          XBZRLE.encoded_buf = NULL;
>          XBZRLE.current_buf = NULL;
>      }
> +    XBZRLE_cache_unlock();
>  }
>  
>  static void ram_migration_cancel(void *opaque)
> @@ -659,13 +708,16 @@ static int ram_save_setup(QEMUFile *f, void *opaque)
>      dirty_rate_high_cnt = 0;
>  
>      if (migrate_use_xbzrle()) {
> +        XBZRLE_cache_lock();
>          XBZRLE.cache = cache_init(migrate_xbzrle_cache_size() /
>                                    TARGET_PAGE_SIZE,
>                                    TARGET_PAGE_SIZE);
>          if (!XBZRLE.cache) {
> +            XBZRLE_cache_unlock();
>              DPRINTF("Error creating cache\n");
>              return -1;
>          }
> +        XBZRLE_cache_unlock();
>  
>          /* We prefer not to abort if there is no memory */
>          XBZRLE.encoded_buf = g_try_malloc0(TARGET_PAGE_SIZE);
> @@ -681,7 +733,6 @@ static int ram_save_setup(QEMUFile *f, void *opaque)
>              XBZRLE.encoded_buf = NULL;
>              return -1;
>          }
> -
>          acct_clear();
>      }
>  
> -- 
> 1.6.0.2
> 
> Best regards,
> -Gonglei

Thanks,

Reviewed-by: Dr. David Alan Gilbert <dgilbert@redhat.com>

--
Dr. David Alan Gilbert / dgilbert@redhat.com / Manchester, UK
Gonglei (Arei) Feb. 21, 2014, 11:59 a.m. UTC | #2
Hi,

> -----Original Message-----
> From: Dr. David Alan Gilbert [mailto:dgilbert@redhat.com]
> Sent: Friday, February 21, 2014 7:04 PM
> To: Gonglei (Arei)
> Cc: qemu-devel@nongnu.org; Juan Quintela; owasserm@redhat.com;
> chenliang (T)
> Subject: Re: [PATCH v2] XBZRLE: Fix qemu crash when resize the xbzrle cache
> 
> * Gonglei (Arei) (arei.gonglei@huawei.com) wrote:
> > Resizing the xbzrle cache during migration causes qemu-crash,
> > because the main-thread and migration-thread modify the xbzrle
> > cache size concurrently without lock-protection.
> >
> > Signed-off-by: ChenLiang <chenliang88@huawei.com>
> > Signed-off-by: Gonglei <arei.gonglei@huawei.com>
> > ---
> > Changes against the previous version:
> >  *Remove function cache_max_num_items and cache_page_size
> >  *Protect migration_end and ram_save_setup by XBZRLE.lock
> >
> >  arch_init.c |   61
> ++++++++++++++++++++++++++++++++++++++++++++++++++++++----
> >  1 files changed, 56 insertions(+), 5 deletions(-)
> >
> > diff --git a/arch_init.c b/arch_init.c
> > index 80574a0..d295237 100644
> > --- a/arch_init.c
> > +++ b/arch_init.c
> > @@ -164,26 +164,69 @@ static struct {
> >      uint8_t *encoded_buf;
> >      /* buffer for storing page content */
> >      uint8_t *current_buf;
> > -    /* Cache for XBZRLE */
> > +    /* Cache for XBZRLE, Protected by lock. */
> >      PageCache *cache;
> > +    QemuMutex lock;
> >  } XBZRLE = {
> >      .encoded_buf = NULL,
> >      .current_buf = NULL,
> >      .cache = NULL,
> > +    /* use the lock carefully */
> > +    .lock = {PTHREAD_MUTEX_INITIALIZER},
> >  };
> >  /* buffer used for XBZRLE decoding */
> >  static uint8_t *xbzrle_decoded_buf;
> >
> > +static void XBZRLE_cache_lock(void)
> > +{
> > +    qemu_mutex_lock(&XBZRLE.lock);
> > +}
> > +
> > +static void XBZRLE_cache_unlock(void)
> > +{
> > +    qemu_mutex_unlock(&XBZRLE.lock);
> > +}
> > +
> 
> You might want to make these only bother with the lock if xbzrle is enabled
> - however actually, I think it's probably just best to keep them as is,
> and simple.
To be honest, we can't follow your meaning. Can you explain it in detail. 

> 
> >  int64_t xbzrle_cache_resize(int64_t new_size)
> >  {
> > +    PageCache *new_cache, *old_cache, *cache;
> > +
> >      if (new_size < TARGET_PAGE_SIZE) {
> >          return -1;
> >      }
> >
> > -    if (XBZRLE.cache != NULL) {
> > -        return cache_resize(XBZRLE.cache, new_size /
> TARGET_PAGE_SIZE) *
> > -            TARGET_PAGE_SIZE;
> > +    XBZRLE_cache_lock();
> > +    /* check XBZRLE.cache again later */
> > +    cache = XBZRLE.cache;
> > +    XBZRLE_cache_unlock();
> 
> That's a bit of an odd way of atomically reading a pointer;
> I think an
>    smp_rmb();
>    cache = atomic_read(&XBZRLE.cache);
> 
> would have done the trick.
We will realize the function without the temporary variable cache.

> 
> Personally I would have just kept the lock at the top and not released
> it until the end of the following section.
> 
Yes, you are right. But the cache_init function maybe consume time, we think it will be better that unlock the
Xbzrle.cache before cache_init and relock it later. 

> > +    if (cache != NULL) {
> > +        if (pow2floor(new_size) == migrate_xbzrle_cache_size()) {
> > +            goto ret;
> 
> Hmm goto not really needed there.
Check.

> 
> > +        }
> > +        new_cache = cache_init(new_size / TARGET_PAGE_SIZE,
> > +                                        TARGET_PAGE_SIZE);
> > +        if (!new_cache) {
> > +            DPRINTF("Error creating cache\n");
> > +            return -1;
> > +        }
> > +
> > +        XBZRLE_cache_lock();
> > +        /* the XBZRLE.cache may have be destroyed, check it again */
> > +        if (XBZRLE.cache != NULL) {
> > +            old_cache = XBZRLE.cache;
> > +            XBZRLE.cache = new_cache;
> > +            new_cache = NULL;
> > +        }
> > +        XBZRLE_cache_unlock();
> > +
> 
> 
> > +        if (NULL == new_cache) {
> > +            cache_fini(old_cache);
> > +        } else {
> > +            cache_fini(new_cache);
> > +        }
> 
> Yes, it took me a few readings to understand that; i.e. if new_cache=NULL then
> it's actually now in use in XBZRLE.cache so only the old_cache needs
> freeing.
> 
> >      }
> > +ret:
> >      return pow2floor(new_size);
> >  }
> >
> > @@ -522,6 +565,8 @@ static int ram_save_block(QEMUFile *f, bool
> last_stage)
> >              ret = ram_control_save_page(f, block->offset,
> >                                 offset, TARGET_PAGE_SIZE,
> &bytes_sent);
> >
> > +            XBZRLE_cache_lock();
> > +
> >              if (ret != RAM_SAVE_CONTROL_NOT_SUPP) {
> >                  if (ret != RAM_SAVE_CONTROL_DELAYED) {
> >                      if (bytes_sent > 0) {
> > @@ -553,6 +598,8 @@ static int ram_save_block(QEMUFile *f, bool
> last_stage)
> >                  acct_info.norm_pages++;
> >              }
> >
> > +            XBZRLE_cache_unlock();
> > +
> >              /* if page is unmodified, continue to the next */
> >              if (bytes_sent > 0) {
> >                  last_sent_block = block;
> 
> Note that's not actually safe until my other xbzrle patch lands, because
> the cache data is currently passed into put_buffer_async that will read it
> later (possibly after you've reallocated) - but that's ok, that should
> get fixed in the mean time.
> 
> > @@ -620,6 +667,7 @@ static void migration_end(void)
> >          migration_bitmap = NULL;
> >      }
> >
> > +    XBZRLE_cache_lock();
> >      if (XBZRLE.cache) {
> >          cache_fini(XBZRLE.cache);
> >          g_free(XBZRLE.cache);
> > @@ -629,6 +677,7 @@ static void migration_end(void)
> >          XBZRLE.encoded_buf = NULL;
> >          XBZRLE.current_buf = NULL;
> >      }
> > +    XBZRLE_cache_unlock();
> >  }
> >
> >  static void ram_migration_cancel(void *opaque)
> > @@ -659,13 +708,16 @@ static int ram_save_setup(QEMUFile *f, void
> *opaque)
> >      dirty_rate_high_cnt = 0;
> >
> >      if (migrate_use_xbzrle()) {
> > +        XBZRLE_cache_lock();
> >          XBZRLE.cache = cache_init(migrate_xbzrle_cache_size() /
> >                                    TARGET_PAGE_SIZE,
> >                                    TARGET_PAGE_SIZE);
> >          if (!XBZRLE.cache) {
> > +            XBZRLE_cache_unlock();
> >              DPRINTF("Error creating cache\n");
> >              return -1;
> >          }
> > +        XBZRLE_cache_unlock();
> >
> >          /* We prefer not to abort if there is no memory */
> >          XBZRLE.encoded_buf = g_try_malloc0(TARGET_PAGE_SIZE);
> > @@ -681,7 +733,6 @@ static int ram_save_setup(QEMUFile *f, void
> *opaque)
> >              XBZRLE.encoded_buf = NULL;
> >              return -1;
> >          }
> > -
> >          acct_clear();
> >      }
> >
> > --
> > 1.6.0.2
> >
> > Best regards,
> > -Gonglei
> 
> Thanks,
> 
> Reviewed-by: Dr. David Alan Gilbert <dgilbert@redhat.com>
> 
> --
> Dr. David Alan Gilbert / dgilbert@redhat.com / Manchester, UK

Best regards,
-Gonglei
Dr. David Alan Gilbert Feb. 21, 2014, 12:10 p.m. UTC | #3
* Gonglei (Arei) (arei.gonglei@huawei.com) wrote:
> Hi,

<snip>

> > > +static void XBZRLE_cache_lock(void)
> > > +{
> > > +    qemu_mutex_lock(&XBZRLE.lock);
> > > +}
> > > +
> > > +static void XBZRLE_cache_unlock(void)
> > > +{
> > > +    qemu_mutex_unlock(&XBZRLE.lock);
> > > +}
> > > +
> > 
> > You might want to make these only bother with the lock if xbzrle is enabled
> > - however actually, I think it's probably just best to keep them as is,
> > and simple.
> To be honest, we can't follow your meaning. Can you explain it in detail. 

These two functions are called from a few places, including ram_save_block
even if xbzrle isn't enabled; I was just suggesting you might not want
to check the lock if xbzrle is disabled; however, I think it's simpler
to leave it as is, and doubt the overhead is worth the complexity.

Dave
--
Dr. David Alan Gilbert / dgilbert@redhat.com / Manchester, UK
Gonglei (Arei) Feb. 21, 2014, 1:32 p.m. UTC | #4
> -----Original Message-----
> From: Dr. David Alan Gilbert [mailto:dgilbert@redhat.com]
> Sent: Friday, February 21, 2014 8:10 PM
> To: Gonglei (Arei)
> Cc: qemu-devel@nongnu.org; Juan Quintela; owasserm@redhat.com;
> chenliang (T)
> Subject: Re: [PATCH v2] XBZRLE: Fix qemu crash when resize the xbzrle cache
> 
> * Gonglei (Arei) (arei.gonglei@huawei.com) wrote:
> > Hi,
> 
> <snip>
> 
> > > > +static void XBZRLE_cache_lock(void)
> > > > +{
> > > > +    qemu_mutex_lock(&XBZRLE.lock);
> > > > +}
> > > > +
> > > > +static void XBZRLE_cache_unlock(void)
> > > > +{
> > > > +    qemu_mutex_unlock(&XBZRLE.lock);
> > > > +}
> > > > +
> > >
> > > You might want to make these only bother with the lock if xbzrle is enabled
> > > - however actually, I think it's probably just best to keep them as is,
> > > and simple.
> > To be honest, we can't follow your meaning. Can you explain it in detail.
> 
> These two functions are called from a few places, including ram_save_block
> even if xbzrle isn't enabled; I was just suggesting you might not want
> to check the lock if xbzrle is disabled; however, I think it's simpler
> to leave it as is, and doubt the overhead is worth the complexity.

I see, thank you so much, Dave.
> 
> Dave
> --
> Dr. David Alan Gilbert / dgilbert@redhat.com / Manchester, UK

Best regards,
-Gonglei
diff mbox

Patch

diff --git a/arch_init.c b/arch_init.c
index 80574a0..d295237 100644
--- a/arch_init.c
+++ b/arch_init.c
@@ -164,26 +164,69 @@  static struct {
     uint8_t *encoded_buf;
     /* buffer for storing page content */
     uint8_t *current_buf;
-    /* Cache for XBZRLE */
+    /* Cache for XBZRLE, Protected by lock. */
     PageCache *cache;
+    QemuMutex lock;
 } XBZRLE = {
     .encoded_buf = NULL,
     .current_buf = NULL,
     .cache = NULL,
+    /* use the lock carefully */
+    .lock = {PTHREAD_MUTEX_INITIALIZER},
 };
 /* buffer used for XBZRLE decoding */
 static uint8_t *xbzrle_decoded_buf;
 
+static void XBZRLE_cache_lock(void)
+{
+    qemu_mutex_lock(&XBZRLE.lock);
+}
+
+static void XBZRLE_cache_unlock(void)
+{
+    qemu_mutex_unlock(&XBZRLE.lock);
+}
+
 int64_t xbzrle_cache_resize(int64_t new_size)
 {
+    PageCache *new_cache, *old_cache, *cache;
+
     if (new_size < TARGET_PAGE_SIZE) {
         return -1;
     }
 
-    if (XBZRLE.cache != NULL) {
-        return cache_resize(XBZRLE.cache, new_size / TARGET_PAGE_SIZE) *
-            TARGET_PAGE_SIZE;
+    XBZRLE_cache_lock();
+    /* check XBZRLE.cache again later */
+    cache = XBZRLE.cache;
+    XBZRLE_cache_unlock();
+
+    if (cache != NULL) {
+        if (pow2floor(new_size) == migrate_xbzrle_cache_size()) {
+            goto ret;
+        }
+        new_cache = cache_init(new_size / TARGET_PAGE_SIZE,
+                                        TARGET_PAGE_SIZE);
+        if (!new_cache) {
+            DPRINTF("Error creating cache\n");
+            return -1;
+        }
+
+        XBZRLE_cache_lock();
+        /* the XBZRLE.cache may have be destroyed, check it again */
+        if (XBZRLE.cache != NULL) {
+            old_cache = XBZRLE.cache;
+            XBZRLE.cache = new_cache;
+            new_cache = NULL;
+        }
+        XBZRLE_cache_unlock();
+
+        if (NULL == new_cache) {
+            cache_fini(old_cache);
+        } else {
+            cache_fini(new_cache);
+        }
     }
+ret:
     return pow2floor(new_size);
 }
 
@@ -522,6 +565,8 @@  static int ram_save_block(QEMUFile *f, bool last_stage)
             ret = ram_control_save_page(f, block->offset,
                                offset, TARGET_PAGE_SIZE, &bytes_sent);
 
+            XBZRLE_cache_lock();
+
             if (ret != RAM_SAVE_CONTROL_NOT_SUPP) {
                 if (ret != RAM_SAVE_CONTROL_DELAYED) {
                     if (bytes_sent > 0) {
@@ -553,6 +598,8 @@  static int ram_save_block(QEMUFile *f, bool last_stage)
                 acct_info.norm_pages++;
             }
 
+            XBZRLE_cache_unlock();
+
             /* if page is unmodified, continue to the next */
             if (bytes_sent > 0) {
                 last_sent_block = block;
@@ -620,6 +667,7 @@  static void migration_end(void)
         migration_bitmap = NULL;
     }
 
+    XBZRLE_cache_lock();
     if (XBZRLE.cache) {
         cache_fini(XBZRLE.cache);
         g_free(XBZRLE.cache);
@@ -629,6 +677,7 @@  static void migration_end(void)
         XBZRLE.encoded_buf = NULL;
         XBZRLE.current_buf = NULL;
     }
+    XBZRLE_cache_unlock();
 }
 
 static void ram_migration_cancel(void *opaque)
@@ -659,13 +708,16 @@  static int ram_save_setup(QEMUFile *f, void *opaque)
     dirty_rate_high_cnt = 0;
 
     if (migrate_use_xbzrle()) {
+        XBZRLE_cache_lock();
         XBZRLE.cache = cache_init(migrate_xbzrle_cache_size() /
                                   TARGET_PAGE_SIZE,
                                   TARGET_PAGE_SIZE);
         if (!XBZRLE.cache) {
+            XBZRLE_cache_unlock();
             DPRINTF("Error creating cache\n");
             return -1;
         }
+        XBZRLE_cache_unlock();
 
         /* We prefer not to abort if there is no memory */
         XBZRLE.encoded_buf = g_try_malloc0(TARGET_PAGE_SIZE);
@@ -681,7 +733,6 @@  static int ram_save_setup(QEMUFile *f, void *opaque)
             XBZRLE.encoded_buf = NULL;
             return -1;
         }
-
         acct_clear();
     }