/* 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;
/*
}
+/*
+ *----------------------------------------------------------------------------
+ *
+ * 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);
+}
+
+
/*
*----------------------------------------------------------------------------
*
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;
}
const os_blocker_id_t blocker) // IN: blocker removing this block
{
BlockInfo *block;
+ int retval;
ASSERT(filename);
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;
}
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);
- }
}
}
if (cookie == NULL) {
os_read_lock(&blockedFilesLock);
block = GetBlock(filename, OS_UNKNOWN_BLOCKER);
+ if (block) {
+ BlockGrabReference(block);
+ }
os_read_unlock(&blockedFilesLock);
if (!block) {
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;
}
os_read_lock(&blockedFilesLock);
block = GetBlock(filename, blocker);
+ if (block) {
+ BlockGrabReference(block);
+ }
os_read_unlock(&blockedFilesLock);
}
#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);
-}