Browse Source

added task leak detection to ContainerLeakDetector

Darren Ranalli 17 years ago
parent
commit
ded092dc32

+ 20 - 1
direct/src/showbase/ContainerLeakDetector.py

@@ -27,6 +27,22 @@ def _createContainerLeak():
             return task.done
     leakContainer()
 
+def _createTaskLeak():
+    leakTaskName = uniqueName('leakedTask')
+    leakDoLaterName = uniqueName('leakedDoLater')
+    def nullTask(task=None):
+        return task.cont
+    def nullDoLater(task=None):
+        return task.done
+    def leakTask(task=None, leakTaskName=leakTaskName):
+        base = getBase()
+        taskMgr.add(nullTask, uniqueName(leakTaskName))
+        taskMgr.doMethodLater(1 << 31, nullDoLater, uniqueName(leakDoLaterName))
+        taskMgr.doMethodLater(10, leakTask, 'doLeakTask-%s' % serialNum())
+        if task:
+            return task.done
+    leakTask()
+
 class NoDictKey:
     pass
 
@@ -336,7 +352,8 @@ class FindContainers(Job):
         # if it's an internal object, ignore it
         if id(obj) in ContainerLeakDetector.PrivateIds:
             return True
-        if objName in ('im_self', 'im_class'):
+        # prevent crashes in objects that define __cmp__ and don't handle strings
+        if type(objName) == types.StringType and objName in ('im_self', 'im_class'):
             return True
         try:
             className = obj.__class__.__name__
@@ -818,6 +835,8 @@ class ContainerLeakDetector(Job):
 
         if config.GetBool('leak-container', 0):
             _createContainerLeak()
+        if config.GetBool('leak-tasks', 0):
+            _createTaskLeak()
 
         # don't check our own tables for leaks
         ContainerLeakDetector.addPrivateObj(ContainerLeakDetector.PrivateIds)

+ 63 - 2
direct/src/showbase/LeakDetectors.py

@@ -10,9 +10,15 @@ class LeakDetector:
         # ContainerLeakDetector will find it quickly
         if not hasattr(__builtin__, "leakDetectors"):
             __builtin__.leakDetectors = {}
-        leakDetectors[id(self)] = self
+        self._leakDetectorsKey = self.getLeakDetectorKey()
+        leakDetectors[self._leakDetectorsKey] = self
     def destroy(self):
-        del leakDetectors[id(self)]
+        del leakDetectors[self._leakDetectorsKey]
+
+    def getLeakDetectorKey(self):
+        # this string will be shown to the end user and should ideally contain enough information to
+        # point to what is leaking
+        return '%s-%s' % (self.__class__.__name__, id(self))
 
 class GarbageLeakDetector(LeakDetector):
     # are we accumulating Python garbage?
@@ -60,3 +66,58 @@ class CppMemoryUsage(LeakDetector):
             return int(MemoryUsage.getCppSize())
         else:
             return 0
+
+class TaskLeakDetectorBase:
+    def _getTaskNamePattern(self, taskName):
+        # get a generic string pattern from a task name by removing numeric characters
+        for i in xrange(10):
+            taskName = taskName.replace('%s' % i, '')
+        return taskName
+    
+class _TaskNamePatternLeakDetector(LeakDetector, TaskLeakDetectorBase):
+    # tracks the number of each individual task type
+    # e.g. are we leaking 'examine-<doId>' tasks
+    def __init__(self, taskNamePattern):
+        self._taskNamePattern = taskNamePattern
+        LeakDetector.__init__(self)
+
+    def __len__(self):
+        # count the number of tasks that match our task name pattern
+        numTasks = 0
+        for task in taskMgr.getTasks():
+            if self._getTaskNamePattern(task.name) == self._taskNamePattern:
+                numTasks += 1
+        for task in taskMgr.getDoLaters():
+            if self._getTaskNamePattern(task.name) == self._taskNamePattern:
+                numTasks += 1
+        return numTasks
+
+    def getLeakDetectorKey(self):
+        return '%s-%s' % (self._taskNamePattern, LeakDetector.getLeakDetectorKey(self))
+
+class TaskLeakDetector(LeakDetector, TaskLeakDetectorBase):
+    # tracks the number task 'types' and creates leak detectors for each task type
+    def __init__(self):
+        LeakDetector.__init__(self)
+        self._taskName2collector = {}
+
+    def destroy(self):
+        for taskName, collector in self._taskName2collector.iteritems():
+            collector.destroy()
+        del self._taskName2collector
+        LeakDetector.destroy(self)
+
+    def _processTaskName(self, taskName):
+        # if this is a new task name pattern, create a leak detector for that pattern
+        namePattern = self._getTaskNamePattern(taskName)
+        if namePattern not in self._taskName2collector:
+            self._taskName2collector[namePattern] = _TaskNamePatternLeakDetector(namePattern)
+
+    def __len__(self):
+        # update our table of task leak detectors
+        for task in taskMgr.getTasks():
+            self._processTaskName(task.name)
+        for task in taskMgr.getDoLaters():
+            self._processTaskName(task.name)
+        # are we leaking task types?
+        return len(self._taskName2collector)

+ 16 - 0
direct/src/task/Task.py

@@ -1105,6 +1105,22 @@ class TaskManager:
         str += "End of taskMgr info\n"
         return str
 
+    def getTasks(self):
+        # returns list of all tasks in arbitrary order
+        tasks = []
+        for taskPriList in self.taskList:
+            for task in taskPriList:
+                if task is not None:
+                    tasks.append(task)
+        for pri, taskList in self.pendingTaskDict.iteritems():
+            for task in taskList:
+                tasks.append(task)
+        return tasks
+
+    def getDoLaters(self):
+        # returns list of all doLaters in arbitrary order
+        return self.__doLaterList[:]
+
     def resetStats(self):
         # WARNING: this screws up your do-later timings
         if self.taskTimerVerbose: