qemu-devel
[Top][All Lists]
Advanced

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

Re: [Qemu-devel] [PATCH 1/1] Stop reinit of XBZRLE.lock


From: Markus Armbruster
Subject: Re: [Qemu-devel] [PATCH 1/1] Stop reinit of XBZRLE.lock
Date: Wed, 19 Mar 2014 13:07:34 +0100
User-agent: Gnus/5.13 (Gnus v5.13) Emacs/24.2 (gnu/linux)

"Dr. David Alan Gilbert" <address@hidden> writes:

> * Markus Armbruster (address@hidden) wrote:
>> "Dr. David Alan Gilbert" <address@hidden> writes:
>> 
>> > * Markus Armbruster (address@hidden) wrote:
>> >> "Dr. David Alan Gilbert (git)" <address@hidden> writes:
>> >
>> > <snip>
>> >
>> >> > diff --git a/arch_init.c b/arch_init.c
>> >> > index 60c975d..16474b5 100644
>> >> > --- a/arch_init.c
>> >> > +++ b/arch_init.c
>> >> > @@ -167,10 +167,13 @@ static struct {
>> >> >      /* Cache for XBZRLE, Protected by lock. */
>> >> >      PageCache *cache;
>> >> >      QemuMutex lock;
>> >> > +    bool lock_init; /* True once we've init'd lock */
>> >> >  } XBZRLE = {
>> >> >      .encoded_buf = NULL,
>> >> >      .current_buf = NULL,
>> >> >      .cache = NULL,
>> >> > +    /* .lock is initialised in ram_save_setup */
>> >> > +    .lock_init = false
>> >> >  };
>> >> 
>> >> Redundant initializers.
>> >
>> > Given how subtle lock stuff is, I'll take making it obvious as
>> > more important.
>> 
>> That a static variable starts out zeroed is plenty obvious.  More
>> obvious in fact than a bunch of explicit initializers I actually have to
>> read to see what they mean.
>> 
>> We rely on implicit zero-initialization of static variables all over the
>> place.
>> 
>> >> >  /* buffer used for XBZRLE decoding */
>> >> >  static uint8_t *xbzrle_decoded_buf;
>> >> > @@ -187,6 +190,11 @@ static void XBZRLE_cache_unlock(void)
>> >> >          qemu_mutex_unlock(&XBZRLE.lock);
>> >> >  }
>> >> >  
>> >> > +/* called from qmp_migrate_set_cache_size in main thread, possibly 
>> >> > while
>> >> > + * a migration is in progress.
>> >> > + * A running migration maybe using the cache and might finish during 
>> >> > this
>> >> 
>> >> may be
>> >> 
>> >> > + * call, hence changes to the cache are protected by XBZRLE.lock().
>> >> > + */
>> >> 
>> >> Style nit, since I'm nitpicking spelling already: our winged comments
>> >> usually look like
>> >> 
>> >> /*
>> >>  * Text
>> >>  */
>> >
>> > Oops, yes; if I need to respin I'll fix that (hmm I wonder if the check
>> > script could be tweaked to find those).
>> >
>> >> >  int64_t xbzrle_cache_resize(int64_t new_size)
>> >> >  {
>> >> >      PageCache *new_cache, *cache_to_free;
>> >> > @@ -195,9 +203,12 @@ int64_t xbzrle_cache_resize(int64_t new_size)
>> >> >          return -1;
>> >> >      }
>> >> >  
>> >> > -    /* no need to lock, the current thread holds qemu big lock */
>> >> > +    /* The current thread holds qemu big lock, and we hold it while 
>> >> > creating
>> >> > +     * the cache in ram_save_setup, thus it's safe to test if the
>> >> > +     * cache exists yet without it's own lock (which might not have 
>> >> > been
>> >> > +     * init'd yet)
>> >> > +     */
>> >> >      if (XBZRLE.cache != NULL) {
>> >> > -        /* check XBZRLE.cache again later */
>> >> >          if (pow2floor(new_size) == migrate_xbzrle_cache_size()) {
>> >> >              return pow2floor(new_size);
>> >> >          }
>> >> > @@ -209,7 +220,10 @@ int64_t xbzrle_cache_resize(int64_t new_size)
>> >> >          }
>> >> >  
>> >> >          XBZRLE_cache_lock();
>> >> > -        /* the XBZRLE.cache may have be destroyed, check it again */
>> >> > +        /* the migration might have finished between the check above 
>> >> > and us
>> >> > +         * taking the lock,  causing XBZRLE.cache to be destroyed
>> >> > +         *   check it again
>> >> > +         */
>> >> >          if (XBZRLE.cache != NULL) {
>> >> >              cache_to_free = XBZRLE.cache;
>> >> >              XBZRLE.cache = new_cache;
>> >> > @@ -744,7 +758,15 @@ static int ram_save_setup(QEMUFile *f, void 
>> >> > *opaque)
>> >> >              DPRINTF("Error creating cache\n");
>> >> >              return -1;
>> >> >          }
>> >> > -        qemu_mutex_init(&XBZRLE.lock);
>> >> > +        /* mutex's can't be reinit'd without destroying them
>> >> > +         * and we've not got a good place to destroy it that
>> >> > +         * we can guarantee isn't being called when we might want
>> >> > +         * to hold the lock.
>> >> > +         */
>> >> > +        if (!XBZRLE.lock_init) {
>> >> > +            XBZRLE.lock_init = true;
>> >> > +            qemu_mutex_init(&XBZRLE.lock);
>> >> > +        }
>> >> >          qemu_mutex_unlock_iothread();
>> >> >  
>> >> >          /* We prefer not to abort if there is no memory */
>> >> 
>> >> I detest how the locking works in xbzrle_cache_resize().
>> >
>> > Yeh, it's tricky - I think the way to think about it is that the 
>> > lock protects the cache and it's contents, not the pointer.
>> > Except that's not really true when it swaps the new one in - hmm.
>> 
>> Yup, and that's what I call confusing and way too subtle.
>
> Yes, agreed - this shouldn't be a hard problem that needs subtlety.
>
>> >> The first XBZRLE.cache != NULL is not under XBZRLE.lock.  As you explain
>> >> in the comment, this is required, because we foolishly delay lock
>> >> initialization until a migration starts, and it's safe, because cache
>> >> creation is also under the BQL.
>> >
>> > Some of this is down to the interfaces between migration generally,
>> > the devices being migrated and the specials for RAM/iterative migration.
>> >
>> > 1) A lot of the ram migration data, including XBZRLE, is global data in
>> > arch_init - this is bad.
>> 
>> As long as there can only be one migration active at a time, it's not
>> really bad, isn't it?
>
> I find it quite messy; there is state associated with migration all
> over, I couldn't honestly tell you all the things that need to be
> initialised, updated, etc when a migration is in progress.
> I'd rather hang stuff off MigrationState.

Point taken.

>> > 2) The management of these is generally glued onto the migration
>> > setup/iterate/complete set of methods used during migration, however
>> > they don't have any calls that correspond to the actual start/end of
>> > migration, or the like that could be sanely used to do any init
>> > that tied up with the actual start end of migration - this is bad.
>> 
>> Maybe.  But why delay the initialization of XBZRLE.lock to start of
>> migration?  All the other members of XBZRLE are initialized on start of
>> program!
>
> That's just fall out from the shock that you couldn't staticly init
> the lock, the original patch version statically init'd the lock in the
> same way as the other members.

Heh :)

>> > 3) Migration is in a separate thread and could finish at any time - this
>> > is expected but complicates life, especially when (2) means that
>> > the data structures for RAMs migration etc are cleared up when migration
>> > finishes.
>> 
>> Four activities: initialization, cache setup, cache use, cache teardown.
>> 
>> Initialization can be done during program startup, where we don't have
>> to worry about threads and locks.  The other three can then be simply
>> put under the lock, and the comment "Protected by lock" becomes actually
>> true.  Sketch appended.  It passes "make check", it must be purrfect!
>
> I'm mostly OK with this, with one small change at the bottom.
>
>> > I don't know the history but (1) seems to arrise from the
>> > semi-arbitrary split between arch_init.c, memory.c, savevm.c, and probably
>> > 3 or 4 other files I've failed to mention.
>> 
>> [...]
>> 
>> 
>> From 6ab76b05789de80d4e0919404429fadcbc0ea403 Mon Sep 17 00:00:00 2001
>> From: Markus Armbruster <address@hidden>
>> Date: Wed, 19 Mar 2014 08:46:51 +0100
>> Subject: [PATCH] XBZRLE: Fix and simplify locking of cache WIP
>> 
>> ---
>>  arch_init.c                | 49 
>> ++++++++++++++++++++++------------------------
>>  include/sysemu/arch_init.h |  1 +
>>  vl.c                       |  1 +
>>  3 files changed, 25 insertions(+), 26 deletions(-)
>> 
>> diff --git a/arch_init.c b/arch_init.c
>> index 60c975d..e41e9dd 100644
>> --- a/arch_init.c
>> +++ b/arch_init.c
>> @@ -167,14 +167,16 @@ static struct {
>>      /* Cache for XBZRLE, Protected by lock. */
>>      PageCache *cache;
>>      QemuMutex lock;
>> -} XBZRLE = {
>> -    .encoded_buf = NULL,
>> -    .current_buf = NULL,
>> -    .cache = NULL,
>> -};
>> +} XBZRLE;
>> +
>>  /* buffer used for XBZRLE decoding */
>>  static uint8_t *xbzrle_decoded_buf;
>>  
>> +void XBZRLE_init(void)
>> +{
>> +    qemu_mutex_init(&XBZRLE.lock);
>> +}
>> +
>>  static void XBZRLE_cache_lock(void)
>>  {
>>      if (migrate_use_xbzrle())
>> @@ -189,39 +191,35 @@ static void XBZRLE_cache_unlock(void)
>>  
>>  int64_t xbzrle_cache_resize(int64_t new_size)
>>  {
>> -    PageCache *new_cache, *cache_to_free;
>> +    PageCache *new_cache;
>> +    int64_t ret;
>>  
>>      if (new_size < TARGET_PAGE_SIZE) {
>>          return -1;
>>      }
>>  
>> -    /* no need to lock, the current thread holds qemu big lock */
>> +    XBZRLE_cache_lock();
>> +
>>      if (XBZRLE.cache != NULL) {
>> -        /* check XBZRLE.cache again later */
>>          if (pow2floor(new_size) == migrate_xbzrle_cache_size()) {
>> -            return pow2floor(new_size);
>> +            goto out_new_size;
>>          }
>>          new_cache = cache_init(new_size / TARGET_PAGE_SIZE,
>>                                          TARGET_PAGE_SIZE);
>>          if (!new_cache) {
>>              DPRINTF("Error creating cache\n");
>> -            return -1;
>> +            goto out;
>>          }
>>  
>> -        XBZRLE_cache_lock();
>> -        /* the XBZRLE.cache may have be destroyed, check it again */
>> -        if (XBZRLE.cache != NULL) {
>> -            cache_to_free = XBZRLE.cache;
>> -            XBZRLE.cache = new_cache;
>> -        } else {
>> -            cache_to_free = new_cache;
>> -        }
>> -        XBZRLE_cache_unlock();
>> -
>> -        cache_fini(cache_to_free);
>> +        cache_fini(XBZRLE.cache);
>> +        XBZRLE.cache = new_cache;
>>      }
>>  
>> -    return pow2floor(new_size);
>> +out_new_size:
>> +    ret = pow2floor(new_size);
>> +out:
>> +    XBZRLE_cache_unlock();
>> +    return ret;
>>  }
>>  
>>  /* accounting for migration statistics */
>> @@ -735,17 +733,16 @@ static int ram_save_setup(QEMUFile *f, void *opaque)
>>      dirty_rate_high_cnt = 0;
>>  
>>      if (migrate_use_xbzrle()) {
>> -        qemu_mutex_lock_iothread();
>> +        XBZRLE_cache_lock();
>>          XBZRLE.cache = cache_init(migrate_xbzrle_cache_size() /
>>                                    TARGET_PAGE_SIZE,
>>                                    TARGET_PAGE_SIZE);
>>          if (!XBZRLE.cache) {
>> -            qemu_mutex_unlock_iothread();
>> +            XBZRLE_cache_unlock();
>>              DPRINTF("Error creating cache\n");
>>              return -1;
>>          }
>> -        qemu_mutex_init(&XBZRLE.lock);
>> -        qemu_mutex_unlock_iothread();
>> +        XBZRLE_cache_unlock();
>>  
>>          /* We prefer not to abort if there is no memory */
>>          XBZRLE.encoded_buf = g_try_malloc0(TARGET_PAGE_SIZE);
>> diff --git a/include/sysemu/arch_init.h b/include/sysemu/arch_init.h
>> index be71bca..fdc3423 100644
>> --- a/include/sysemu/arch_init.h
>> +++ b/include/sysemu/arch_init.h
>> @@ -26,6 +26,7 @@ enum {
>>  
>>  extern const uint32_t arch_type;
>>  
>> +void XBZRLE_init(void);
>>  void select_soundhw(const char *optarg);
>>  void do_acpitable_option(const QemuOpts *opts);
>>  void do_smbios_option(QemuOpts *opts);
>> diff --git a/vl.c b/vl.c
>> index f0fe48b..24433c8 100644
>> --- a/vl.c
>> +++ b/vl.c
>> @@ -3008,6 +3008,7 @@ int main(int argc, char **argv, char **envp)
>>  
>>      qemu_init_auxval(envp);
>>      qemu_cache_utils_init();
>> +    XBZRLE_init();
>>  
>>      QLIST_INIT (&vm_change_state_head);
>>      os_setup_early_signal_handling();
>> -- 
>> 1.8.1.4
>
> I noticed that in main.c there is a call to a blk_mig_init() - so
> how about (not even build tested):
>
> diff --git a/vl.c b/vl.c
> index 842e897..ab8cbf0 100644
> --- a/vl.c
> +++ b/vl.c
> @@ -4247,6 +4247,7 @@ int main(int argc, char **argv, char **envp)
>      cpu_exec_init_all();
>  
>      blk_mig_init();
> +    ram_mig_init();
>  
>      /* open the virtual block devices */
>      if (snapshot)

I figure this is still early eanough.

> @@ -4261,8 +4262,6 @@ int main(int argc, char **argv, char **envp)
>      default_drive(default_floppy, snapshot, IF_FLOPPY, 0, FD_OPTS);
>      default_drive(default_sdcard, snapshot, IF_SD, 0, SD_OPTS);
>  
> -    register_savevm_live(NULL, "ram", 0, 4, &savevm_ram_handlers, NULL);
> -
>      if (nb_numa_nodes > 0) {
>          int i;
> diff --git a/arch_init.c b/arch_init.c
> index 16474b5..4f8376d 100644
> --- a/arch_init.c
> +++ b/arch_init.c
> @@ -190,6 +190,12 @@ static void XBZRLE_cache_unlock(void)
>          qemu_mutex_unlock(&XBZRLE.lock);
>  }
>  
> +void ram_mig_init(void)
> +{
> +    qemu_mutex_init(&XBZRLE.lock);
> +    register_savevm_live(NULL, "ram", 0, 4, &savevm_ram_handlers, NULL);
> +}
> +
>  /* called from qmp_migrate_set_cache_size in main thread, possibly while
>   * a migration is in progress.
>   * A running migration maybe using the cache and might finish during this
> @@ -1117,7 +1123,7 @@ done:
>      return ret;
>  }
>  
> -SaveVMHandlers savevm_ram_handlers = {
> +static SaveVMHandlers savevm_ram_handlers = {
>      .save_live_setup = ram_save_setup,
>      .save_live_iterate = ram_save_iterate,
>      .save_live_complete = ram_save_complete,
>
> diff --git a/include/migration/migration.h b/include/migration/migration.h
> index 3e1e6c7..31fbf17 100644
> --- a/include/migration/migration.h
> +++ b/include/migration/migration.h
> @@ -113,8 +113,6 @@ void free_xbzrle_decoded_buf(void);                       
>                               
>  
>  void acct_update_position(QEMUFile *f, size_t size, bool zero);
>  
> -extern SaveVMHandlers savevm_ram_handlers;
> -
>  uint64_t dup_mig_bytes_transferred(void);
>  uint64_t dup_mig_pages_transferred(void);
>  uint64_t skipped_mig_bytes_transferred(void);
>
> and that way we're simplifying vl.c rather than teaching it even
> more about the innards of ram migration.

Yes, that's better.  Suggest to split into two patches, one to hide
savevm_ram_handlers behind ram_mig_init(), and another one to fix and
simplify the locking.

Care to test it and post?



reply via email to

[Prev in Thread] Current Thread [Next in Thread]