]> git.ipfire.org Git - thirdparty/open-vm-tools.git/commitdiff
lib/lock: perThread tracking information fixes
authorVMware, Inc <>
Thu, 22 Dec 2011 00:20:11 +0000 (16:20 -0800)
committerMarcelo Vanzin <mvanzin@vmware.com>
Thu, 22 Dec 2011 00:20:11 +0000 (16:20 -0800)
Signed-off-by: Marcelo Vanzin <mvanzin@vmware.com>
open-vm-tools/lib/lock/ul.c
open-vm-tools/lib/lock/ulInt.h

index a3024a40fd2580ad80544c2039c22a5c21373f35..5ec2abdfb5459a9dde7a2846bafb18c5c65921c0 100644 (file)
@@ -25,7 +25,6 @@
 #include "hashTable.h"
 #include "random.h"
 
-
 static Bool mxInPanic = FALSE;  // track when involved in a panic
 
 Bool (*MXUserTryAcquireForceFail)() = NULL;
@@ -399,12 +398,159 @@ MXUserInstallMxHooks(void (*theLockListFunc)(void),
 #if defined(MXUSER_DEBUG)
 #define MXUSER_MAX_LOCKS_PER_THREAD (2 * MXUSER_MAX_REC_DEPTH)
 
-typedef struct {
-   uint32         locksHeld;
-   MXUserHeader  *lockArray[MXUSER_MAX_LOCKS_PER_THREAD];
+typedef struct MXUserPerThread {
+   struct MXUserPerThread  *next;
+   uint32                   locksHeld;
+   MXUserHeader            *lockArray[MXUSER_MAX_LOCKS_PER_THREAD];
 } MXUserPerThread;
 
+static Atomic_Ptr perThreadLockMem;
+static MXUserPerThread *perThreadFreeList = NULL;
+
+#if defined(_WIN32)
+static Atomic_uint32 MXUserTlsMem = { TLS_OUT_OF_INDEXES };
+#else
 static Atomic_Ptr hashTableMem;
+#endif
+
+
+/*
+ *-----------------------------------------------------------------------------
+ *
+ * MXUserAllocPerThread --
+ *
+ *     Allocate a perThread structure.
+ *
+ *     Memory is allocated for the specified thread as necessary. Use a
+ *     victim cache in front of malloc to provide a slight performance
+ *     advantage. The lock here is equivalent to the lock buried inside
+ *     malloc but no complex calculations are necessary to perform an
+ *     allocation most of the time.
+ *
+ *     The maximum size of the list will be roughly the maximum number of
+ *     threads having taken locks at the same time - a bounded number less
+ *     than or equal to the maximum of threads created.
+ *
+ * Results:
+ *     As above.
+ *
+ * Side effects:
+ *      Memory may be allocated.
+ *
+ *-----------------------------------------------------------------------------
+ */
+
+static MXUserPerThread *
+MXUserAllocPerThread(void)
+{
+   MXUserPerThread *perThread;
+   MXRecLock *perThreadLock = MXUserInternalSingleton(&perThreadLockMem);
+
+   ASSERT(perThreadLock);
+
+   MXRecLockAcquire(perThreadLock);
+
+   if (perThreadFreeList == NULL) {
+      perThread = Util_SafeMalloc(sizeof *perThread);
+   } else {
+      perThread = perThreadFreeList;
+      perThreadFreeList = perThread->next;
+   }
+
+   MXRecLockRelease(perThreadLock);
+
+   ASSERT(perThread);
+
+   memset(perThread, 0, sizeof *perThread);  // ensure all zeros
+
+   return perThread;
+}
+
+
+/*
+ *-----------------------------------------------------------------------------
+ *
+ * MXUserFreePerThread --
+ *
+ *     Free a perThread structure.
+ *
+ *     The structure is placed on the free list -- for "later".
+ *
+ * Results:
+ *     As above.
+ *
+ * Side effects:
+ *      None
+ *
+ *-----------------------------------------------------------------------------
+ */
+
+static void
+MXUserFreePerThread(MXUserPerThread *perThread)  // IN:
+{
+   MXRecLock *perThreadLock;
+
+   ASSERT(perThread);
+   ASSERT(perThread->next == NULL);
+
+   perThreadLock = MXUserInternalSingleton(&perThreadLockMem);
+   ASSERT(perThreadLock);
+
+   MXRecLockAcquire(perThreadLock);
+   perThread->next = perThreadFreeList;
+   perThreadFreeList = perThread;
+   MXRecLockRelease(perThreadLock);
+}
+
+
+#if defined(_WIN32)
+/*
+ *-----------------------------------------------------------------------------
+ *
+ * MXUserObtainTlsIndex --
+ *
+ *      Allocate a TLS slot, if necessary, for the MXUser functionality.
+ *
+ * Results:
+ *      A TLS slot index
+ *
+ * Side effects:
+ *      One TLS slot will be consumed.
+ *
+ *-----------------------------------------------------------------------------
+ */
+
+static DWORD
+MXUserObtainTlsIndex(void)
+{
+   DWORD index = Atomic_Read(&MXUserTlsMem);
+
+   if (index == TLS_OUT_OF_INDEXES) {
+      DWORD newIndex = TlsAlloc();
+
+      ASSERT_NOT_IMPLEMENTED(newIndex != TLS_OUT_OF_INDEXES);
+
+      /*
+       * The memory associated with the index is NULL in all existing threads
+       * and will be NULL in any future threads. It can only become not NULL
+       * via an explicit write (via TlsSetValue).
+       */
+
+      index = Atomic_ReadIfEqualWrite(&MXUserTlsMem, TLS_OUT_OF_INDEXES,
+                                      newIndex);
+
+      if (index != TLS_OUT_OF_INDEXES) {
+         TlsFree(newIndex);
+      }
+
+      index = Atomic_Read(&MXUserTlsMem);
+   }
+
+   ASSERT(index != TLS_OUT_OF_INDEXES);
+
+   return index;
+}
+#endif
 
 
 /*
@@ -414,9 +560,7 @@ static Atomic_Ptr hashTableMem;
  *
  *      Return a pointer to the per thread data for the specified thread.
  *
- *      Memory is allocated for the specified thread as necessary. This memory
- *      is never released since it it is highly likely a thread will use a
- *      lock and need to record data in the perThread.
+ *      Memory is allocated for the specified thread as necessary.
  *
  * Results:
  *      NULL   mayAlloc was FALSE and the thread doesn't have a perThread
@@ -429,11 +573,35 @@ static Atomic_Ptr hashTableMem;
  */
 
 static MXUserPerThread *
-MXUserGetPerThread(void *tid,      // IN: thread ID
-                   Bool mayAlloc)  // IN: alloc perThread if not present?
+MXUserGetPerThread(Bool mayAlloc)  // IN: alloc perThread if not present?
 {
+   MXUserPerThread *perThread;
+
+#if defined(_WIN32)
+   DWORD index = MXUserObtainTlsIndex();
+
+   /*
+    * Using TLS ensures that each thread has its own area without the
+    * need of MXUser having tracking structures - the native thread
+    * implemenation does this for us.
+    */
+
+   perThread = TlsGetValue(index);
+   ASSERT_NOT_IMPLEMENTED(GetLastError() == ERROR_SUCCESS);
+
+   if ((perThread == NULL) && mayAlloc) {
+      BOOL ok;
+
+      perThread = MXUserAllocPerThread();
+
+      ok = TlsSetValue(index, (LPVOID) perThread);
+      ASSERT_NOT_IMPLEMENTED(ok);
+   }
+#else
    HashTable *hash;
-   MXUserPerThread *perThread = NULL;
+   void *tid = MXUserCastedThreadID();
+
+   perThread = NULL;
 
    hash = HashTable_AllocOnce(&hashTableMem, 1024,
                               HASH_INT_KEY | HASH_FLAG_ATOMIC, NULL);
@@ -442,8 +610,7 @@ MXUserGetPerThread(void *tid,      // IN: thread ID
       /* No entry for this tid was found, allocate one? */
 
       if (mayAlloc) {
-         MXUserPerThread *newEntry = Util_SafeCalloc(1,
-                                                     sizeof(MXUserPerThread));
+         MXUserPerThread *newEntry = MXUserAllocPerThread();
 
          /*
           * Attempt to (racey) insert a perThread on behalf of the specified
@@ -455,12 +622,13 @@ MXUserGetPerThread(void *tid,      // IN: thread ID
          ASSERT(perThread);
 
          if (perThread != newEntry) {
-            free(newEntry);
+            MXUserFreePerThread(newEntry);
          }
       } else {
          perThread = NULL;
       }
    }
+#endif
 
    return perThread;
 }
@@ -487,8 +655,7 @@ MXUserGetPerThread(void *tid,      // IN: thread ID
 void
 MXUserListLocks(void)
 {
-   MXUserPerThread *perThread = MXUserGetPerThread(MXUserCastedThreadID(),
-                                                   FALSE);
+   MXUserPerThread *perThread = MXUserGetPerThread(FALSE);
 
    if (perThread != NULL) {
       uint32 i;
@@ -523,8 +690,7 @@ MXUserListLocks(void)
 Bool
 MXUser_IsCurThreadHoldingLocks(void)
 {
-   MXUserPerThread *perThread = MXUserGetPerThread(MXUserCastedThreadID(),
-                                                   FALSE);
+   MXUserPerThread *perThread = MXUserGetPerThread(FALSE);
 
    return (perThread == NULL) ? FALSE : (perThread->locksHeld != 0);
 }
@@ -600,8 +766,7 @@ MX_Rank
 MXUserCurrentRank(void)
 {
    MX_Rank maxRank;
-   MXUserPerThread *perThread = MXUserGetPerThread(MXUserCastedThreadID(),
-                                                   FALSE);
+   MXUserPerThread *perThread = MXUserGetPerThread(FALSE);
 
    if (perThread == NULL) {
       maxRank = RANK_UNRANKED;
@@ -634,8 +799,7 @@ void
 MXUserAcquisitionTracking(MXUserHeader *header,  // IN:
                           Bool checkRank)        // IN:
 {
-   MXUserPerThread *perThread = MXUserGetPerThread(MXUserCastedThreadID(),
-                                                   TRUE);
+   MXUserPerThread *perThread = MXUserGetPerThread(TRUE);
 
    ASSERT_NOT_IMPLEMENTED(perThread->locksHeld < MXUSER_MAX_LOCKS_PER_THREAD);
 
@@ -714,13 +878,12 @@ MXUserReleaseTracking(MXUserHeader *header)  // IN: lock, via its header
 {
    uint32 i;
    uint32 lastEntry;
-   void *tid = MXUserCastedThreadID();
-   MXUserPerThread *perThread = MXUserGetPerThread(tid, FALSE);
+   MXUserPerThread *perThread = MXUserGetPerThread(FALSE);
 
    /* MXUserAcquisitionTracking should have already created a perThread */
    if (UNLIKELY(perThread == NULL)) {
       MXUserDumpAndPanic(header, "%s: perThread not found! (thread 0x%p)\n",
-                         __FUNCTION__, tid);
+                         __FUNCTION__, MXUserCastedThreadID());
    }
 
    /* Search the perThread for the argument lock */
@@ -734,7 +897,8 @@ MXUserReleaseTracking(MXUserHeader *header)  // IN: lock, via its header
    if (UNLIKELY(i >= perThread->locksHeld)) {
       MXUserDumpAndPanic(header,
                          "%s: lock not found! (thread 0x%p; count %u)\n",
-                         __FUNCTION__, tid, perThread->locksHeld);
+                         __FUNCTION__, MXUserCastedThreadID(),
+                         perThread->locksHeld);
    }
 
    /* Remove the argument lock from the perThread */
@@ -746,6 +910,33 @@ MXUserReleaseTracking(MXUserHeader *header)  // IN: lock, via its header
 
    perThread->lockArray[lastEntry] = NULL;  // tidy up memory
    perThread->locksHeld--;
+
+#if defined(_WIN32)
+   /*
+    * On Windows thread IDs aren't greedily recycled. If a process creates and
+    * destroys many threads this can cause a memory leak of perThread data
+    * (and its overhead). We avoid this by releasing the perThread memory
+    * and cleaning up the TLS entry when the number of outstanding locks
+    * for the thread goes to zero. If a thread dies/exits with locks held
+    * that's a serious problem that doesn't need to be solved within the
+    * MXUser Facility.
+    *
+    * Yes, this is a performance cost but it only affects Windows debug
+    * builds and then not by very much - we tend to run for a long time
+    * with either no locks held or at least one lock held.
+    */
+
+   if (perThread->locksHeld == 0) {
+      BOOL ok;
+      DWORD index = Atomic_Read(&MXUserTlsMem);
+
+      ASSERT(index != TLS_OUT_OF_INDEXES);
+
+      ok = TlsSetValue(index, (LPVOID) NULL);
+      ASSERT_NOT_IMPLEMENTED(ok);
+      MXUserFreePerThread(perThread);
+   }
+#endif
 }
 
 
index 48f09b9d1c284b20434937e700d3070d4fe4dd24..42d10cf0687de39e4e8041909df24f61a0a26780 100644 (file)
@@ -404,7 +404,22 @@ MXRecLockRelease(MXRecLock *lock)  // IN/OUT:
 static INLINE void *
 MXUserCastedThreadID(void)
 {
+#if defined(_WIN32)
+   /*
+    * On Windows there is a problem with using VThread_CurID() - it doesn't
+    * maintain unique thread ID values (PR 780775). Native thread ID values
+    * and special handling are used to resolve issues.
+    */
+
+   return (void *) (uintptr_t) GetCurrentThreadId();  // DWORD
+#else
+   /*
+    * Outside of Windows there are no known issues with using VThread_CurID
+    * so that is what is used.
+    */
+
    return (void *) (uintptr_t) VThread_CurID();  // unsigned
+#endif
 }
 
 /*