diff --git a/direct/src/showbase/ContainerLeakDetector.py b/direct/src/showbase/ContainerLeakDetector.py index cb4fc3194e..6d36dc206a 100755 --- a/direct/src/showbase/ContainerLeakDetector.py +++ b/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) diff --git a/direct/src/showbase/LeakDetectors.py b/direct/src/showbase/LeakDetectors.py index 3afd880ae8..19030bf44c 100755 --- a/direct/src/showbase/LeakDetectors.py +++ b/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-' 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) diff --git a/direct/src/task/Task.py b/direct/src/task/Task.py index 40f07c8ccd..6e9d15777e 100644 --- a/direct/src/task/Task.py +++ b/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: