]> git.ipfire.org Git - thirdparty/open-vm-tools.git/commitdiff
vmblock: consolidate refcounting and freeing of block objects
authorVMware, Inc <>
Mon, 26 Sep 2011 19:55:04 +0000 (12:55 -0700)
committerMarcelo Vanzin <mvanzin@vmware.com>
Mon, 26 Sep 2011 19:55:04 +0000 (12:55 -0700)
Combine refcounting and memory management by providing
BlockGrabReferenec() and BlockDropReference() and have
BlockDropReference() free the block object once last reference
is dropped instead of callers explicitly checking refount
and freeing the object manually.

Signed-off-by: Marcelo Vanzin <mvanzin@vmware.com>
open-vm-tools/modules/shared/vmblock/block.c

index c611cfe0ae4eef46e6267638753a8ca1dc5d90ca..613ebafccf68adbc32201bd7252c15d5536d3955 100644 (file)
@@ -90,14 +90,7 @@ typedef struct BlockInfo {
 /* XXX: Is it worth turning this into a hash table? */
 static DblLnkLst_Links blockedFiles;
 static os_rwlock_t blockedFilesLock;
-static os_kmem_cache_t *blockInfoCache = NULL;
-
-/* Utility functions */
-static Bool BlockExists(const char *filename);
-static BlockInfo *GetBlock(const char *filename, const os_blocker_id_t blocker);
-static BlockInfo *AllocBlock(os_kmem_cache_t *cache,
-                             const char *filename, const os_blocker_id_t blocker);
-static void FreeBlock(os_kmem_cache_t *cache, BlockInfo *block);
+static os_kmem_cache_t *blockInfoCache;
 
 
 /*
@@ -163,6 +156,224 @@ BlockCleanup(void)
 }
 
 
+/*
+ *----------------------------------------------------------------------------
+ *
+ * AllocBlock --
+ *
+ *    Allocates and initializes a new block structure.
+ *
+ * Results:
+ *    Pointer to the struct on success, NULL on failure.
+ *
+ * Side effects:
+ *    None.
+ *
+ *----------------------------------------------------------------------------
+ */
+
+static BlockInfo *
+AllocBlock(os_kmem_cache_t *cache,        // IN: cache to allocate from
+           const char *filename,          // IN: filname of block
+           const os_blocker_id_t blocker) // IN: blocker id
+{
+   BlockInfo *block;
+   size_t ret;
+
+   /* Initialize this file's block structure. */
+   block = os_kmem_cache_alloc(blockInfoCache);
+   if (!block) {
+      return NULL;
+   }
+
+   ret = strlcpy(block->filename, filename, sizeof block->filename);
+   if (ret >= sizeof block->filename) {
+      Warning("BlockAddFileBlock: filename is too large\n");
+      os_kmem_cache_free(blockInfoCache, block);
+      return NULL;
+   }
+
+   DblLnkLst_Init(&block->links);
+   os_atomic_set(&block->refcount, 1);
+   os_completion_init(&block->completion);
+   block->blocker = blocker;
+
+   return block;
+}
+
+
+/*
+ *----------------------------------------------------------------------------
+ *
+ * FreeBlock --
+ *
+ *    Frees the provided block structure.
+ *
+ * Results:
+ *    None.
+ *
+ * Side effects:
+ *    None.
+ *
+ *----------------------------------------------------------------------------
+ */
+
+static void
+FreeBlock(os_kmem_cache_t *cache,       // IN: cache block was allocated from
+          BlockInfo *block)             // IN: block to free
+{
+   ASSERT(cache);
+   ASSERT(block);
+
+   if (DblLnkLst_IsLinked(&block->links)) {
+      Warning("Block on file [%s] is still in the list, "
+              "not freeing, leaking memory\n", block->filename);
+      return;
+   }
+
+   os_completion_destroy(&block->completion);
+   os_kmem_cache_free(cache, block);
+}
+
+
+/*
+ *----------------------------------------------------------------------------
+ *
+ * BlockGrabReference --
+ *
+ *    Increments reference count in the provided block structure.
+ *
+ * Results:
+ *    None.
+ *
+ * Side effects:
+ *    None.
+ *
+ *----------------------------------------------------------------------------
+ */
+
+static BlockInfo *
+BlockGrabReference(BlockInfo *block)
+{
+   ASSERT(block);
+
+   os_atomic_inc(&block->refcount);
+
+   return block;
+}
+
+
+/*
+ *----------------------------------------------------------------------------
+ *
+ * BlockDropReference --
+ *
+ *    Increments reference count in the provided block structure.
+ *
+ * Results:
+ *    None.
+ *
+ * Side effects:
+ *    When reference count reaches 0 disposes of the block structure by
+ *    calling FreeBlock().
+ *
+ *----------------------------------------------------------------------------
+ */
+
+static void
+BlockDropReference(BlockInfo *block)
+{
+   if (os_atomic_dec_and_test(&block->refcount)) {
+      LOG(4, "Dropped last reference for block on [%s]\n", filename);
+      FreeBlock(blockInfoCache, block);
+   }
+}
+
+
+/*
+ *----------------------------------------------------------------------------
+ *
+ * GetBlock --
+ *
+ *    Searches for a block on the provided filename by the provided blocker.
+ *    If blocker is NULL, it is ignored and any matching filename is returned.
+ *
+ *    Note that this assumes the proper locking has been done on the data
+ *    structure holding the blocked files.
+ *
+ * Results:
+ *    A pointer to the corresponding BlockInfo if found, NULL otherwise.
+ *
+ * Side effects:
+ *    None.
+ *
+ *----------------------------------------------------------------------------
+ */
+
+static BlockInfo *
+GetBlock(const char *filename,          // IN: file to find block for
+         const os_blocker_id_t blocker) // IN: blocker associated with this block
+{
+   struct DblLnkLst_Links *curr;
+
+   /*
+    * On FreeBSD we have a mechanism to assert (but not simply check)
+    * that a lock is held. Since semantic is different (panic that
+    * happens if assertion fails can not be suppressed) we are using
+    * different name.
+    */
+#ifdef os_assert_rwlock_held
+   os_assert_rwlock_held(&blockedFilesLock);
+#else
+   ASSERT(os_rwlock_held(&blockedFilesLock));
+#endif
+
+   DblLnkLst_ForEach(curr, &blockedFiles) {
+      BlockInfo *currBlock = DblLnkLst_Container(curr, BlockInfo, links);
+      if ((blocker == OS_UNKNOWN_BLOCKER || currBlock->blocker == blocker) &&
+          strcmp(currBlock->filename, filename) == 0) {
+         return currBlock;
+      }
+   }
+
+   return NULL;
+}
+
+
+/*
+ *----------------------------------------------------------------------------
+ *
+ * BlockDoRemoveBlock --
+ *
+ *    Removes given block from the block list and notifies waiters that block
+ *    is gone.
+ *
+ * Results:
+ *    None.
+ *
+ * Side effects:
+ *    Block structure will be freed if there were no waiters.
+ *
+ *----------------------------------------------------------------------------
+ */
+
+static void
+BlockDoRemoveBlock(BlockInfo *block)
+{
+   ASSERT(block);
+
+   DblLnkLst_Unlink1(&block->links);
+
+   /* Wake up waiters, if any */
+   LOG(4, "Completing block on [%s] (%d waiters)\n",
+       filename, block->refcount - 1);
+   os_complete_all(&block->completion);
+
+   /* Now drop our reference */
+   BlockDropReference(block);
+}
+
+
 /*
  *----------------------------------------------------------------------------
  *
@@ -192,35 +403,31 @@ BlockAddFileBlock(const char *filename,           // IN: name of file to block
                   const os_blocker_id_t blocker)  // IN: blocker adding the block
 {
    BlockInfo *block;
+   int retval;
 
    ASSERT(filename);
 
-   /* Create a new block. */
+   os_write_lock(&blockedFilesLock);
+
+   if (GetBlock(filename, OS_UNKNOWN_BLOCKER)) {
+      retval = OS_EEXIST;
+      goto out;
+   }
+
    block = AllocBlock(blockInfoCache, filename, blocker);
    if (!block) {
       Warning("BlockAddFileBlock: out of memory\n");
-      return OS_ENOMEM;
-   }
-   os_write_lock(&blockedFilesLock);
-
-   /*
-    * Prevent duplicate blocks of any filename.  Done under same lock as list
-    * addition to ensure check for and adding of file are atomic.
-    */
-   if (BlockExists(filename)) {
-      Warning("BlockAddFileBlock: block already exists for [%s]\n", filename);
-      os_write_unlock(&blockedFilesLock);
-      FreeBlock(blockInfoCache, block);
-      return OS_EEXIST;
+      retval = OS_ENOMEM;
+      goto out;
    }
 
    DblLnkLst_LinkLast(&blockedFiles, &block->links);
-
-   os_write_unlock(&blockedFilesLock);
-
    LOG(4, "added block for [%s]\n", filename);
+   retval = 0;
 
-   return 0;
+out:
+   os_write_unlock(&blockedFilesLock);
+   return retval;
 }
 
 
@@ -247,6 +454,7 @@ BlockRemoveFileBlock(const char *filename,          // IN: block to remove
                      const os_blocker_id_t blocker) // IN: blocker removing this block
 {
    BlockInfo *block;
+   int retval;
 
    ASSERT(filename);
 
@@ -254,31 +462,16 @@ BlockRemoveFileBlock(const char *filename,          // IN: block to remove
 
    block = GetBlock(filename, blocker);
    if (!block) {
-      os_write_unlock(&blockedFilesLock);
-      return OS_ENOENT;
+      retval = OS_ENOENT;
+      goto out;
    }
 
-   DblLnkLst_Unlink1(&block->links);
-   os_write_unlock(&blockedFilesLock);
-
-   /* Undo GetBlock's refcount increment first. */
-   os_atomic_dec(&block->refcount);
+   BlockDoRemoveBlock(block);
+   retval = 0;
 
-   /*
-    * Now remove /our/ reference.  (As opposed to references by waiting
-    * threads.)
-    */
-   if (os_atomic_dec_and_test(&block->refcount)) {
-      /* No threads are waiting, so clean up ourself. */
-      LOG(4, "Freeing block with no waiters on [%s]\n", filename);
-      FreeBlock(blockInfoCache, block);
-   } else {
-      /* Wake up waiters; the last one will free the BlockInfo */
-      LOG(4, "Completing block on [%s]\n", filename);
-      os_complete_all(&block->completion);
-   }
-
-   return 0;
+out:
+   os_write_unlock(&blockedFilesLock);
+   return retval;
 }
 
 
@@ -311,30 +504,13 @@ BlockRemoveAllBlocks(const os_blocker_id_t blocker)  // IN: blocker to remove bl
       BlockInfo *currBlock = DblLnkLst_Container(curr, BlockInfo, links);
       if (currBlock->blocker == blocker || blocker == OS_UNKNOWN_BLOCKER) {
 
-         DblLnkLst_Unlink1(&currBlock->links);
+         BlockDoRemoveBlock(currBlock);
 
          /*
           * We count only entries removed from the -list-, regardless of whether
           * or not other waiters exist.
           */
          ++removed;
-
-         /*
-          * BlockInfos, as the result of placing a block on a file or directory,
-          * reference themselves.  When the block is lifted, we need to remove
-          * this self-reference and handle the result appropriately.
-          */
-         if (os_atomic_dec_and_test(&currBlock->refcount)) {
-            /* Free blocks without any waiters ... */
-            LOG(4, "Freeing block with no waiters for blocker [%p] (%s)\n",
-                blocker, currBlock->filename);
-            FreeBlock(blockInfoCache, currBlock);
-         } else {
-            /* ... or wakeup the waiting threads */
-            LOG(4, "Completing block for blocker [%p] (%s)\n",
-                blocker, currBlock->filename);
-            os_complete_all(&currBlock->completion);
-         }
       }
    }
 
@@ -381,6 +557,9 @@ BlockWaitOnFile(const char *filename,   // IN: file to block on
    if (cookie == NULL) {
       os_read_lock(&blockedFilesLock);
       block = GetBlock(filename, OS_UNKNOWN_BLOCKER);
+      if (block) {
+         BlockGrabReference(block);
+      }
       os_read_unlock(&blockedFilesLock);
 
       if (!block) {
@@ -400,23 +579,7 @@ BlockWaitOnFile(const char *filename,   // IN: file to block on
    error = os_wait_for_completion(&block->completion);
    LOG(4, "(%"OS_FMTTID") Wokeup from block on [%s]\n", os_threadid, filename);
 
-   /*
-    * The assumptions here are as follows:
-    *   1.  The BlockInfo holds a reference to itself.  (BlockInfo's refcount
-    *       is initialized to 1.)
-    *   2.  BlockInfo's self reference is deleted only when BlockInfo is
-    *       /also/ removed removed from the block list.
-    *
-    * Therefore, if the reference count hits zero, it's because the block is
-    * no longer in the list, and there is no chance of another thread finding
-    * and referencing this block between our dec_and_test and freeing it.
-    */
-   if (os_atomic_dec_and_test(&block->refcount)) {
-      /* We were the last thread, so clean up */
-      LOG(4, "(%"OS_FMTTID") I am the last to wakeup, freeing the block on [%s]\n",
-          os_threadid, filename);
-      FreeBlock(blockInfoCache, block);
-   }
+   BlockDropReference(block);
 
    return error;
 }
@@ -449,6 +612,9 @@ BlockLookup(const char *filename,               // IN: pathname to test for
    os_read_lock(&blockedFilesLock);
 
    block = GetBlock(filename, blocker);
+   if (block) {
+      BlockGrabReference(block);
+   }
 
    os_read_unlock(&blockedFilesLock);
 
@@ -495,164 +661,3 @@ BlockListFileBlocks(void)
 }
 #endif
 
-
-/* Utility functions */
-
-/*
- *----------------------------------------------------------------------------
- *
- * BlockExists --
- *
- *    Checks if a block already exists for the provided filename.
- *
- *    Note that this assumes the proper locking has been done on the data
- *    structure holding the blocked files (including ensuring the atomic_dec()
- *    without a kmem_cache_free() is safe).
- *
- * Results:
- *    TRUE if a block exists, FALSE otherwise.
- *
- * Side effects:
- *    If a block exists, its refcount is incremented and decremented.
- *
- *----------------------------------------------------------------------------
- */
-
-static Bool
-BlockExists(const char *filename)
-{
-   BlockInfo *block = GetBlock(filename, OS_UNKNOWN_BLOCKER);
-
-   if (block) {
-      os_atomic_dec(&block->refcount);
-      return TRUE;
-   }
-
-   return FALSE;
-}
-
-
-/*
- *----------------------------------------------------------------------------
- *
- * GetBlock --
- *
- *    Searches for a block on the provided filename by the provided blocker.
- *    If blocker is NULL, it is ignored and any matching filename is returned.
- *    If a block is found, the refcount is incremented.
- *
- *    Note that this assumes the proper locking has been done on the data
- *    structure holding the blocked files.
- *
- * Results:
- *    A pointer to the corresponding BlockInfo if found, NULL otherwise.
- *
- * Side effects:
- *    None.
- *
- *----------------------------------------------------------------------------
- */
-
-static BlockInfo *
-GetBlock(const char *filename,          // IN: file to find block for
-         const os_blocker_id_t blocker) // IN: blocker associated with this block
-{
-   struct DblLnkLst_Links *curr;
-
-   /*
-    * On FreeBSD we have a mechanism to assert (but not simply check)
-    * that a lock is held. Since semantic is different (panic that
-    * happens if assertion fails can not be suppressed) we are using
-    * different name.
-    */
-#ifdef os_assert_rwlock_held
-   os_assert_rwlock_held(&blockedFilesLock);
-#else
-   ASSERT(os_rwlock_held(&blockedFilesLock));
-#endif
-
-   DblLnkLst_ForEach(curr, &blockedFiles) {
-      BlockInfo *currBlock = DblLnkLst_Container(curr, BlockInfo, links);
-      if ((blocker == OS_UNKNOWN_BLOCKER || currBlock->blocker == blocker) &&
-          strcmp(currBlock->filename, filename) == 0) {
-         os_atomic_inc(&currBlock->refcount);
-         return currBlock;
-      }
-   }
-
-   return NULL;
-}
-
-
-/*
- *----------------------------------------------------------------------------
- *
- * AllocBlock --
- *
- *    Allocates and initializes a new block structure.
- *
- * Results:
- *    Pointer to the struct on success, NULL on failure.
- *
- * Side effects:
- *    None.
- *
- *----------------------------------------------------------------------------
- */
-
-BlockInfo *
-AllocBlock(os_kmem_cache_t *cache,        // IN: cache to allocate from
-           const char *filename,          // IN: filname of block
-           const os_blocker_id_t blocker) // IN: blocker id
-{
-   BlockInfo *block;
-   size_t ret;
-
-   /* Initialize this file's block structure. */
-   block = os_kmem_cache_alloc(blockInfoCache);
-   if (!block) {
-      return NULL;
-   }
-
-   ret = strlcpy(block->filename, filename, sizeof block->filename);
-   if (ret >= sizeof block->filename) {
-      Warning("BlockAddFileBlock: filename is too large\n");
-      os_kmem_cache_free(blockInfoCache, block);
-      return NULL;
-   }
-
-   DblLnkLst_Init(&block->links);
-   os_atomic_set(&block->refcount, 1);
-   os_completion_init(&block->completion);
-   block->blocker = blocker;
-
-   return block;
-}
-
-
-/*
- *----------------------------------------------------------------------------
- *
- * FreeBlock --
- *
- *    Frees the provided block structure.
- *
- * Results:
- *    None.
- *
- * Side effects:
- *    None.
- *
- *----------------------------------------------------------------------------
- */
-
-static void
-FreeBlock(os_kmem_cache_t *cache,       // IN: cache block was allocated from
-          BlockInfo *block)             // IN: block to free
-{
-   ASSERT(cache);
-   ASSERT(block);
-
-   os_completion_destroy(&block->completion);
-   os_kmem_cache_free(cache, block);
-}