From 6f5a7d0bc6fb63b936500ff7523f5ea5f1805707 Mon Sep 17 00:00:00 2001 From: Darren Ranalli Date: Sat, 7 Feb 2009 00:00:02 +0000 Subject: [PATCH] more leak detection --- direct/src/showbase/LeakDetectors.py | 178 ++++++++++++++++++++++++++- direct/src/showbase/Messenger.py | 14 ++- direct/src/showbase/PythonUtil.py | 10 +- 3 files changed, 199 insertions(+), 3 deletions(-) diff --git a/direct/src/showbase/LeakDetectors.py b/direct/src/showbase/LeakDetectors.py index f2fda9a74f..595aa87c64 100755 --- a/direct/src/showbase/LeakDetectors.py +++ b/direct/src/showbase/LeakDetectors.py @@ -1,6 +1,8 @@ # objects that report different types of leaks to the ContainerLeakDetector from pandac.PandaModules import * +from direct.showbase.DirectObject import DirectObject +from direct.showbase.Job import Job import __builtin__, gc class LeakDetector: @@ -10,6 +12,8 @@ class LeakDetector: if not hasattr(__builtin__, "leakDetectors"): __builtin__.leakDetectors = {} self._leakDetectorsKey = self.getLeakDetectorKey() + if __dev__: + assert self._leakDetectorsKey not in leakDetectors leakDetectors[self._leakDetectorsKey] = self def destroy(self): del leakDetectors[self._leakDetectorsKey] @@ -89,7 +93,7 @@ class _TaskNamePatternLeakDetector(LeakDetector, TaskLeakDetectorBase): return numTasks def getLeakDetectorKey(self): - return '%s-%s' % (self._taskNamePattern, LeakDetector.getLeakDetectorKey(self)) + return '%s-%s' % (self._taskNamePattern, self.__class__.__name__) class TaskLeakDetector(LeakDetector, TaskLeakDetectorBase): # tracks the number task 'types' and creates leak detectors for each task type @@ -110,6 +114,7 @@ class TaskLeakDetector(LeakDetector, TaskLeakDetectorBase): self._taskName2collector[namePattern] = _TaskNamePatternLeakDetector(namePattern) def __len__(self): + self._taskName2collector = {} # update our table of task leak detectors for task in taskMgr.getTasks(): self._processTaskName(task.name) @@ -117,3 +122,174 @@ class TaskLeakDetector(LeakDetector, TaskLeakDetectorBase): self._processTaskName(task.name) # are we leaking task types? return len(self._taskName2collector) + +class MessageLeakDetectorBase: + def _getMessageNamePattern(self, msgName): + # get a generic string pattern from a message name by removing numeric characters + for i in xrange(10): + msgName = msgName.replace('%s' % i, '') + return msgName + +class _MessageTypeLeakDetector(LeakDetector, MessageLeakDetectorBase): + # tracks the number of objects that are listening to each message + def __init__(self, msgNamePattern): + self._msgNamePattern = msgNamePattern + self._msgNames = set() + LeakDetector.__init__(self) + + def addMsgName(self, msgName): + # for efficiency, we keep the actual message names around + # for queries on the messenger + self._msgNames.add(msgName) + + def __len__(self): + toRemove = set() + num = 0 + for msgName in self._msgNames: + n = messenger._getNumListeners(msgName) + if n == 0: + toRemove.add(msgName) + else: + num += n + # remove message names that are no longer in the messenger + self._msgNames.difference_update(toRemove) + return num + + def getLeakDetectorKey(self): + return '%s-%s' % (self._msgNamePattern, self.__class__.__name__) + +class _MessageTypeLeakDetectorCreator(Job): + def __init__(self, creator): + Job.__init__(self, uniqueName(typeName(self))) + self._creator = creator + + def destroy(self): + self._creator = None + Job.destroy(self) + + def finished(self): + Job.finished(self) + + def run(self): + for msgName in messenger._getEvents(): + yield None + namePattern = self._creator._getMessageNamePattern(msgName) + if namePattern not in self._creator._msgName2detector: + self._creator._msgName2detector[namePattern] = _MessageTypeLeakDetector(namePattern) + self._creator._msgName2detector[namePattern].addMsgName(msgName) + yield Job.Done + +class MessageTypesLeakDetector(LeakDetector, MessageLeakDetectorBase): + def __init__(self): + LeakDetector.__init__(self) + self._msgName2detector = {} + self._createJob = None + if config.GetBool('leak-message-types', 0): + self._leakers = [] + self._leakTaskName = uniqueName('leak-message-types') + taskMgr.add(self._leak, self._leakTaskName) + + def _leak(self, task): + self._leakers.append(DirectObject()) + self._leakers[-1].accept('leak-msg', self._leak) + return task.cont + + def destroy(self): + if hasattr(self, '_leakTaskName'): + taskMgr.remove(self._leakTaskName) + for leaker in self._leakers: + leaker.ignoreAll() + self._leakers = None + if self._createJob: + self._createJob.destroy() + self._createJob = None + for msgName, detector in self._msgName2detector.iteritems(): + detector.destroy() + del self._msgName2detector + LeakDetector.destroy(self) + + def __len__(self): + if self._createJob: + if self._createJob.isFinished(): + self._createJob.destroy() + self._createJob = None + self._createJob = _MessageTypeLeakDetectorCreator(self) + jobMgr.add(self._createJob) + # are we leaking message types? + return len(self._msgName2detector) + +class _MessageListenerTypeLeakDetector(LeakDetector): + # tracks the number of each object type that is listening for events + def __init__(self, typeName): + self._typeName = typeName + LeakDetector.__init__(self) + + def __len__(self): + numObjs = 0 + for obj in messenger._getObjects(): + if typeName(obj) == self._typeName: + numObjs += 1 + return numObjs + + def getLeakDetectorKey(self): + return '%s-%s' % (self._typeName, self.__class__.__name__) + +class _MessageListenerTypeLeakDetectorCreator(Job): + def __init__(self, creator): + Job.__init__(self, uniqueName(typeName(self))) + self._creator = creator + + def destroy(self): + self._creator = None + Job.destroy(self) + + def finished(self): + Job.finished(self) + + def run(self): + for obj in messenger._getObjects(): + yield None + tName = typeName(obj) + if tName not in self._creator._typeName2detector: + self._creator._typeName2detector[tName] = ( + _MessageListenerTypeLeakDetector(tName)) + yield Job.Done + +class MessageListenerTypesLeakDetector(LeakDetector): + def __init__(self): + LeakDetector.__init__(self) + self._typeName2detector = {} + self._createJob = None + if config.GetBool('leak-message-listeners', 0): + self._leakers = [] + self._leakTaskName = uniqueName('leak-message-listeners') + taskMgr.add(self._leak, self._leakTaskName) + + def _leak(self, task): + self._leakers.append(DirectObject()) + self._leakers[-1].accept(uniqueName('leak-msg-listeners'), self._leak) + return task.cont + + def destroy(self): + if hasattr(self, '_leakTaskName'): + taskMgr.remove(self._leakTaskName) + for leaker in self._leakers: + leaker.ignoreAll() + self._leakers = None + if self._createJob: + self._createJob.destroy() + self._createJob = None + for typeName, detector in self._typeName2detector.iteritems(): + detector.destroy() + del self._typeName2detector + LeakDetector.destroy(self) + + def __len__(self): + if self._createJob: + if self._createJob.isFinished(): + self._createJob.destroy() + self._createJob = None + self._createJob = _MessageListenerTypeLeakDetectorCreator(self) + jobMgr.add(self._createJob) + # are we leaking listener types? + return len(self._typeName2detector) diff --git a/direct/src/showbase/Messenger.py b/direct/src/showbase/Messenger.py index e3780646c7..af29c4a38f 100644 --- a/direct/src/showbase/Messenger.py +++ b/direct/src/showbase/Messenger.py @@ -61,7 +61,7 @@ class Messenger: # accept/ignore more than once over their lifetime) # get unique messenger id for this object if not hasattr(object, '_messengerId'): - object._messengerId = self._messengerIdGen + object._messengerId = (object.__class__.__name__, self._messengerIdGen) self._messengerIdGen += 1 return object._messengerId @@ -77,6 +77,18 @@ class Messenger: def _getObject(self, id): return self._id2object[id][1] + def _getObjects(self): + objs = [] + for refCount, obj in self._id2object.itervalues(): + objs.append(obj) + return objs + + def _getNumListeners(self, event): + return len(self.__callbacks.get(event, {})) + + def _getEvents(self): + return self.__callbacks.keys() + def _releaseObject(self, object): # assumes lock is held. id = self._getMessengerId(object) diff --git a/direct/src/showbase/PythonUtil.py b/direct/src/showbase/PythonUtil.py index d7bb3ca517..7122e6c093 100644 --- a/direct/src/showbase/PythonUtil.py +++ b/direct/src/showbase/PythonUtil.py @@ -31,7 +31,8 @@ __all__ = ['enumerate', 'unique', 'indent', 'nonRepeatingRandomList', 'printStack', 'printReverseStack', 'listToIndex2item', 'listToItem2index', 'pandaBreak','pandaTrace','formatTimeCompact','DestructiveScratchPad', 'deeptype','getProfileResultString','StdoutCapture','StdoutPassthrough', -'Averager', 'getRepository', 'formatTimeExact', 'startSuperLog', 'endSuperLog' ] +'Averager', 'getRepository', 'formatTimeExact', 'startSuperLog', 'endSuperLog', +'typeName', ] import types import string @@ -3852,6 +3853,12 @@ def configIsToday(configName): return True return False +def typeName(o): + if hasattr(o, '__class__'): + return o.__class__.__name__ + else: + return o.__name__ + import __builtin__ __builtin__.Functor = Functor __builtin__.Stack = Stack @@ -3905,3 +3912,4 @@ __builtin__.deeptype = deeptype __builtin__.Default = Default __builtin__.isInteger = isInteger __builtin__.configIsToday = configIsToday +__builtin__.typeName = typeName