ToDo/LockGuards

From QEMU
Revision as of 11:09, 19 March 2020 by Stefanha (talk | contribs)
(diff) ← Older revision | Latest revision (diff) | Newer revision → (diff)

Lock guards are a safe alternative to manual lock()/unlock() calls. They take a lock and automatically unlock it at the end of the scope or when the function returns. This prevents common bugs when locks are not released in error code paths.

Lock guards were added to QEMU in March 2020. Much of the codebase does not take advantage of them yet. This page explains how to use them and when to convert existing code.

Lock guard macros

Two lock guard macros are defined in "qemu/lockable.h":

 /**
  * WITH_QEMU_LOCK_GUARD - Lock a lock object for scope
  *
  * @x: a lock object (currently one of QemuMutex, CoMutex, QemuSpin).
  *
  * This macro defines a lock scope such that entering the scope takes the lock
  * and leaving the scope releases the lock.  Return statements are allowed
  * within the scope and release the lock.  Break and continue statements leave
  * the scope early and release the lock.
  *
  *   WITH_QEMU_LOCK_GUARD(&mutex) {
  *       ...
  *       if (error) {
  *           return; <-- mutex is automatically unlocked
  *       }
  *
  *       if (early_exit) {
  *           break;  <-- leave this scope early
  *       }
  *       ...
  *   }
  */
 #define WITH_QEMU_LOCK_GUARD(x)

and

 /**
  * QEMU_LOCK_GUARD - Lock an object until the end of the scope
  *
  * @x: a lock object (currently one of QemuMutex, CoMutex, QemuSpin).
  *
  * This macro takes a lock until the end of the scope.  Return statements
  * release the lock.
  *
  *   ... <-- mutex not locked
  *   QEMU_LOCK_GUARD(&mutex); <-- mutex locked from here onwards
  *   ...
  *   if (error) {
  *       return; <-- mutex is automatically unlocked
  *   }
  */
 #define QEMU_LOCK_GUARD(x)

How to use lock guards

Use WITH_QEMU_LOCK_GUARD() when the lock must be released before the end of the function. Use QEMU_LOCK_GUARD() when the lock must be held until the end of the function.

Here is an example conversion from manual lock()/unlock() calls to WITH_QEMU_LOCK_GUARD(). Before:

 qemu_mutex_lock(&mutex);
 if (data == NULL) {
     qemu_mutex_unlock(&mutex);
     return false;
 }
 ...use data...
 qemu_mutex_unlock(&mutex);
 return true;

After:

 WITH_QEMU_LOCK_GUARD(&mutex) {
     if (data == NULL) {
         return false;
     }
     ...use data...
 }

Notice that it is no longer necessary to manually call qemu_mutex_unlock().

Which types of locks are supported

QemuMutex, QemuRecMutex, CoMutex, and QemuSpin are supported by WITH_QEMU_LOCK_GUARD() and QEMU_LOCK_GUARD().

The RCU read lock has its own WITH_RCU_READ_LOCK_GUARD() and RCU_READ_LOCK_GUARD() macros in "qemu/rcu.h" that can replace manual rcu_read_lock()/rcu_read_unlock() calls.

When to convert existing code to lock guards

Lock guards usually make code easier to read by eliminating unlock() calls and gotos. However, there are some exceptions where it is either not worth it or when lock guards cannot handle complex cases.

They do not provide a significant advantage for straight-line code without conditional statements:

 qemu_mutex_lock(&mutex);
 data++;
 qemu_mutex_unlock(&mutex);

Leave simple code alone because it is not worth developing and reviewing patches that have no benefit.

They also do not handle complex control flow such as loops that temporarily drop a lock:

 qemu_mutex_lock(&mutex);
 while (running) {
     ...
     qemu_mutex_unlock(&mutex);
     wait();
     qemu_mutex_lock(&mutex);
     ...
 }
 qemu_mutex_unlock(&mutex);

Although lock guard macros could be mixed with manual lock()/unlock() calls to handle some complex control flow cases, this can be subtle and may lead to bugs. Do not try to convert these cases to use lock guards.