Tcl Source Code

Artifact [ca8a1b75d1]
Login

Artifact ca8a1b75d1fee780f27e02d4aded25b4c176b787:

Attachment "tcl-thread-syncobj-leak.patch" to ticket [1726873fff] added by twylite 2007-06-19 14:27:39.
diff -u -r tcl8.5a6\generic/tclThread.c tcl\generic/tclThread.c
--- tcl8.5a6\generic/tclThread.c	2006-11-09 00:53:39.000000000 +0200
+++ tcl\generic/tclThread.c	2007-06-05 14:40:01.262125000 +0200
@@ -23,23 +23,22 @@
  * TclRememberThreadData, e.g., TclpThreadDataKeyInit
  */
 
+#define SYNC_OBJ_LIST_ELEMS 16
+ 
 typedef struct {
-    int num;		/* Number of objects remembered */
-    int max;		/* Max size of the array */
-    char **list;	/* List of pointers */
+    char **list; /* List of pointers */
 } SyncObjRecord;
-
-static SyncObjRecord keyRecord = {0, 0, NULL};
-static SyncObjRecord mutexRecord = {0, 0, NULL};
-static SyncObjRecord condRecord = {0, 0, NULL};
+ 
+static SyncObjRecord keyRecord = {NULL};
+static SyncObjRecord mutexRecord = {NULL};
+static SyncObjRecord condRecord = {NULL};
 
 /*
  * Prototypes of functions used only in this file.
  */
 
 static void		ForgetSyncObject(char *objPtr, SyncObjRecord *recPtr);
-static void		RememberSyncObject(char *objPtr,
-			    SyncObjRecord *recPtr);
+static void		RememberSyncObject(char *objPtr, SyncObjRecord *recPtr);
 
 /*
  * Several functions are #defined to nothing in tcl.h if TCL_THREADS is not
@@ -153,34 +152,56 @@
 
 static void
 RememberSyncObject(
-    char *objPtr,		/* Pointer to sync object */
-    SyncObjRecord *recPtr)	/* Record of sync objects */
+    char *objPtr,  /* Pointer to sync object */
+    SyncObjRecord *recPtr) /* Record of sync objects */
 {
-    char **newList;
-    int i, j;
-
-    /*
-     * Save the pointer to the allocated object so it can be finalized. Grow
-     * the list of pointers if necessary, copying only non-NULL pointers to
-     * the new list.
-     */
-
-    if (recPtr->num >= recPtr->max) {
-	recPtr->max += 8;
-	newList = (char **) ckalloc(recPtr->max * sizeof(char *));
-	for (i=0,j=0 ; i<recPtr->num ; i++) {
-	    if (recPtr->list[i] != NULL) {
-		newList[j++] = recPtr->list[i];
-	    }
-	}
-	if (recPtr->list != NULL) {
-	    ckfree((char *) recPtr->list);
-	}
-	recPtr->list = newList;
-	recPtr->num = j;
-    }
-    recPtr->list[recPtr->num] = objPtr;
-    recPtr->num++;
+    int i;
+    char** localList = NULL;
+    char*** listPtr = &(recPtr->list);
+ 
+    /* How this works: recPtr->list is an array of SYNC_OBJ_LIST_ELEMS pointers
+     * plus one extra pointer.  The extra pointer is to a chained list (char**)
+     * of SYNC_OBJ_LIST_ELEMS pointers, plus an extra pointer.  Etc.
+     * We find the first empty (NULL) slot in the chain of lists and stick 
+     * objPtr into it. */     
+    
+    do {
+        /* This code is not thread safe -- it must be done under a lock.
+         * Usually the caller holds the MASTER_LOCK. 
+         * If two threads were to enter this logic at the same time, they
+         * could use the same empty slot (losing one of the objPtrs) or
+         * allocate two new chained lists, one of which will be lost.
+         * It IS safe to call ForgetSyncObject at the same time as this 
+         * logic though, as long as:
+         *   1. we zeroize the newList before assigning it to the *listPtr. 
+         *   2. we never shrink the list by ckfree'ing a chained list.  If you
+         *      do that we're back to the race condition that required this fix
+         *      in the first place.                  
+         */         
+         
+        if ( *listPtr == NULL ) {
+            size_t listSize = (SYNC_OBJ_LIST_ELEMS + 1) * sizeof(char *);
+            
+            localList = (char **) ckalloc( listSize );
+            memset ( localList, 0, listSize );
+            *listPtr = localList;
+        }
+        
+        localList = *listPtr;
+        
+        for ( i = 0; i < SYNC_OBJ_LIST_ELEMS; ++i ) {
+            if ( localList[i] == NULL ) {
+                localList[i] = objPtr;
+                break;
+            }
+        }
+        
+        if ( i == SYNC_OBJ_LIST_ELEMS ) {
+            listPtr = &((char **)localList[i]);
+        }
+        
+    } while (i == SYNC_OBJ_LIST_ELEMS);
+ 
 }
 
 /*
@@ -201,17 +222,29 @@
 
 static void
 ForgetSyncObject(
-    char *objPtr,		/* Pointer to sync object */
-    SyncObjRecord *recPtr)	/* Record of sync objects */
+    char *objPtr,  /* Pointer to sync object */
+    SyncObjRecord *recPtr) /* Record of sync objects */
 {
     int i;
-
-    for (i=0 ; i<recPtr->num ; i++) {
-	if (objPtr == recPtr->list[i]) {
-	    recPtr->list[i] = NULL;
-	    return;
-	}
-    }
+    char** localList = recPtr->list;
+ 
+    do {
+        /* Refer to comments in RememberSyncObject before altering this code.
+         * You have been warned. */
+         
+        for ( i = 0; i < SYNC_OBJ_LIST_ELEMS; ++i ) {
+            if ( localList[i] == objPtr ) {
+                localList[i] = NULL;
+                break;
+            }
+        }
+        
+        if ( i == SYNC_OBJ_LIST_ELEMS ) {
+            localList = (char **)localList[i];
+        }
+        
+    } while (i == SYNC_OBJ_LIST_ELEMS);
+ 
 }
 
 /*
@@ -366,54 +399,78 @@
 
     TclpMasterLock();
 
-    /*
-     * If we're running unthreaded, the TSD blocks are simply stored inside
-     * their thread data keys. Free them here.
-     */
+    /* Refer to comments in RememberSyncObject before altering this code.
+     * You have been warned. */
 
-    for (i=0 ; i<keyRecord.num ; i++) {
-	keyPtr = (Tcl_ThreadDataKey *) keyRecord.list[i];
-	blockPtr = (void *) *keyPtr;
-	ckfree(blockPtr);
-    }
-    if (keyRecord.list != NULL) {
-	ckfree((char *) keyRecord.list);
-	keyRecord.list = NULL;
+    /*
+    * If we're running unthreaded, the TSD blocks are simply stored inside
+    * their thread data keys. Free them here.
+    */
+    while (keyRecord.list != NULL) {
+    
+        for ( i = 0; i < SYNC_OBJ_LIST_ELEMS; ++i ) {
+            if ( keyRecord.list[i] != NULL ) {
+                keyPtr = (Tcl_ThreadDataKey *) keyRecord.list[i];
+                blockPtr = (void *) *keyPtr;
+                ckfree(blockPtr);
+            }
+        }
+        
+        if ( keyRecord.list[i] != NULL ) {
+            char *tmp = (char *) keyRecord.list;
+            keyRecord.list = (char **)keyRecord.list[i];
+            ckfree(tmp);
+        } else {
+            ckfree((char *) keyRecord.list);
+            keyRecord.list = NULL;
+        }
+        
     }
-    keyRecord.max = 0;
-    keyRecord.num = 0;
-
+    
     /*
-     * Call thread storage master cleanup.
-     */
-
+    * Call thread storage master cleanup.
+    */
+    
     TclFinalizeThreadStorage();
-
-    for (i=0 ; i<mutexRecord.num ; i++) {
-	mutexPtr = (Tcl_Mutex *)mutexRecord.list[i];
-	if (mutexPtr != NULL) {
-	    TclpFinalizeMutex(mutexPtr);
-	}
-    }
-    if (mutexRecord.list != NULL) {
-	ckfree((char *) mutexRecord.list);
-	mutexRecord.list = NULL;
-    }
-    mutexRecord.max = 0;
-    mutexRecord.num = 0;
-
-    for (i=0 ; i<condRecord.num ; i++) {
-	condPtr = (Tcl_Condition *) condRecord.list[i];
-	if (condPtr != NULL) {
-	    TclpFinalizeCondition(condPtr);
-	}
-    }
-    if (condRecord.list != NULL) {
-	ckfree((char *) condRecord.list);
-	condRecord.list = NULL;
+    
+    while (mutexRecord.list != NULL) {
+        
+        for ( i = 0; i < SYNC_OBJ_LIST_ELEMS; ++i ) {
+            mutexPtr = (Tcl_Mutex *)mutexRecord.list[i];
+            if ( mutexPtr != NULL ) {
+                TclpFinalizeMutex(mutexPtr);
+            }
+        }
+        
+        if ( mutexRecord.list[i] != NULL ) {
+            char *tmp = (char *) mutexRecord.list;
+            mutexRecord.list = (char **)mutexRecord.list[i];
+            ckfree(tmp);
+        } else {
+            ckfree((char *) mutexRecord.list);
+            mutexRecord.list = NULL;
+        }
+        
+    }
+    
+    while (condRecord.list != NULL) {
+    
+        for ( i = 0; i < SYNC_OBJ_LIST_ELEMS; ++i ) {
+            condPtr = (Tcl_Condition *) condRecord.list[i];
+            if ( condPtr != NULL ) {
+                TclpFinalizeCondition(condPtr);
+            }
+        }
+        
+        if ( condRecord.list[i] != NULL ) {
+            char *tmp = (char *) condRecord.list;
+            condRecord.list = (char **)condRecord.list[i];
+            ckfree(tmp);
+        } else {
+            ckfree((char *) condRecord.list);
+            condRecord.list = NULL;
+        }
     }
-    condRecord.max = 0;
-    condRecord.num = 0;
 
     TclpMasterUnlock();
 #else /* TCL_THREADS */
@@ -421,8 +478,6 @@
 	ckfree((char *) keyRecord.list);
 	keyRecord.list = NULL;
     }
-    keyRecord.max = 0;
-    keyRecord.num = 0;
 #endif /* TCL_THREADS */
 }