From fb1aa31b7836ed79f8fb85ed49847fab1ed7e2c4 Mon Sep 17 00:00:00 2001 From: Darren Ranalli Date: Fri, 5 Dec 2008 01:50:55 +0000 Subject: [PATCH] stop using gc.DEBUG_SAVEALL (not necessary), GarbageReport output readability improvements --- .../src/distributed/ConnectionRepository.py | 4 - direct/src/showbase/GarbageReport.py | 98 +++++++++++++------ direct/src/showbase/LeakDetectors.py | 8 +- direct/src/showbase/PythonUtil.py | 6 +- 4 files changed, 70 insertions(+), 46 deletions(-) diff --git a/direct/src/distributed/ConnectionRepository.py b/direct/src/distributed/ConnectionRepository.py index 98eefd7680..2832785322 100644 --- a/direct/src/distributed/ConnectionRepository.py +++ b/direct/src/distributed/ConnectionRepository.py @@ -98,10 +98,6 @@ class ConnectionRepository( self._serverAddress = '' - if self.config.GetBool('want-debug-leak', 1): - import gc - gc.set_debug(gc.DEBUG_SAVEALL) - def generateGlobalObject(self, doId, dcname, values=None): def applyFieldValues(distObj, dclass, values): for i in range(dclass.getNumInheritedFields()): diff --git a/direct/src/showbase/GarbageReport.py b/direct/src/showbase/GarbageReport.py index 5e5f684fe9..78a6524cd6 100755 --- a/direct/src/showbase/GarbageReport.py +++ b/direct/src/showbase/GarbageReport.py @@ -3,14 +3,15 @@ __all__ = ['FakeObject', '_createGarbage', 'GarbageReport', 'GarbageLogger'] from direct.directnotify.DirectNotifyGlobal import directNotify -from direct.showbase.PythonUtil import gcDebugOn, safeRepr, fastRepr, printListEnumGen, printNumberedTypesGen +from direct.showbase.PythonUtil import safeRepr, fastRepr, printListEnumGen, printNumberedTypesGen from direct.showbase.PythonUtil import AlphabetCounter from direct.showbase.Job import Job import gc import types class FakeObject: - pass + def __del__(self): + pass def _createGarbage(num=1): for i in xrange(num): @@ -46,12 +47,39 @@ class GarbageReport(Job): def run(self): # do the garbage collection - wasOn = gcDebugOn() oldFlags = gc.get_debug() - if not wasOn: - gc.set_debug(gc.DEBUG_SAVEALL) + + # do a collect without SAVEALL, to get the instances that define __del__ + # cycles that do not involve any instances that define __del__ are cleaned up + # automatically by Python, but they also appear in gc.garbage when SAVEALL is set + gc.set_debug(0) gc.collect() - self.garbage = list(gc.garbage) + garbageInstances = gc.garbage[:] + del gc.garbage[:] + # only yield if there's more time-consuming work to do, + # if there's no garbage, give instant feedback + if len(garbageInstances) > 0: + yield None + # don't repr the garbage list if we don't have to + if self.notify.getDebug(): + self.notify.debug('garbageInstances == %s' % fastRepr(garbageInstances)) + + self.numGarbageInstances = len(garbageInstances) + # grab the ids of the garbage instances (objects with __del__) + self.garbageInstanceIds = set() + for i in xrange(len(garbageInstances)): + self.garbageInstanceIds.add(id(garbageInstances[i])) + if not (i % 20): + yield None + # then release the list of instances so that it doesn't interfere with the gc.collect() below + del garbageInstances + + # do a SAVEALL pass so that we have all of the objects involved in legitimate garbage cycles + # without SAVEALL, gc.garbage only contains objects with __del__ methods + gc.set_debug(gc.DEBUG_SAVEALL) + gc.collect() + self.garbage = gc.garbage[:] + del gc.garbage[:] # only yield if there's more time-consuming work to do, # if there's no garbage, give instant feedback if len(self.garbage) > 0: @@ -59,9 +87,7 @@ class GarbageReport(Job): # don't repr the garbage list if we don't have to if self.notify.getDebug(): self.notify.debug('self.garbage == %s' % fastRepr(self.garbage)) - del gc.garbage[:] - if not wasOn: - gc.set_debug(oldFlags) + gc.set_debug(oldFlags) self.numGarbage = len(self.garbage) # only yield if there's more time-consuming work to do, @@ -70,7 +96,7 @@ class GarbageReport(Job): yield None if self._args.verbose: - self.notify.info('found %s garbage items' % self.numGarbage) + self.notify.info('found %s garbage instances' % self.numGarbageInstances) """ spammy # print the types of the garbage first, in case the repr of an object @@ -102,7 +128,7 @@ class GarbageReport(Job): yield None # grab the referrers (pointing to garbage) - if self._args.fullReport and (self.numGarbage != 0): + if self._args.fullReport and (self.numGarbageInstances != 0): if self._args.verbose: self.notify.info('getting referrers...') for i in xrange(self.numGarbage): @@ -114,7 +140,7 @@ class GarbageReport(Job): self.referrersByReference[i] = byRef # grab the referents (pointed to by garbage) - if self.numGarbage > 0: + if self.numGarbageInstances > 0: if self._args.verbose: self.notify.info('getting referents...') for i in xrange(self.numGarbage): @@ -126,7 +152,7 @@ class GarbageReport(Job): self.referentsByReference[i] = byRef # find the cycles - if self._args.findCycles and self.numGarbage > 0: + if self._args.findCycles and self.numGarbageInstances > 0: if self._args.verbose: self.notify.info('detecting cycles...') for i in xrange(self.numGarbage): @@ -221,12 +247,13 @@ class GarbageReport(Job): self.cycleIds.update(set(cycle)) if self._args.findCycles: - s = ['===== GarbageReport: \'%s\' (%s items, %s cycles) =====' % ( - self._args.name, self.numGarbage, len(self.cycles))] + s = ['===== GarbageReport: \'%s\' (%s %s) =====' % ( + self._args.name, len(self.cycles), + choice(len(self.cycles) == 1, 'cycle', 'cycles'))] else: - s = ['===== GarbageReport: \'%s\' (%s items) =====' % ( - self._args.name, self.numGarbage)] - if self.numGarbage > 0: + s = ['===== GarbageReport: \'%s\' =====' % ( + self._args.name)] + if self.numGarbageInstances > 0: # make a list of the ids we will actually be printing if self._args.fullReport: garbageIndices = range(self.numGarbage) @@ -277,14 +304,14 @@ class GarbageReport(Job): s.append(format % (idx, itype(self.garbage[idx]), objStr)) if self._args.findCycles: - s.append('===== Garbage Cycles By Index =====') + s.append('===== Garbage Cycles (Garbage Item Numbers) =====') ac = AlphabetCounter() for i in xrange(len(self.cycles)): yield None s.append('%s:%s' % (ac.next(), self.cycles[i])) if self._args.findCycles: - s.append('===== Garbage Cycles By Python Syntax =====') + s.append('===== Garbage Cycles (Python Syntax) =====') ac = AlphabetCounter() for i in xrange(len(self.cyclesBySyntax)): yield None @@ -314,7 +341,7 @@ class GarbageReport(Job): if self._args.log: self.printingBegin() for i in xrange(len(self._report)): - if self.numGarbage > 0: + if self.numGarbageInstances > 0: yield None self.notify.info(self._report[i]) self.printingEnd() @@ -418,15 +445,18 @@ class GarbageReport(Job): uniqueCycleSets = set() stateStack = Stack() rootId = index - stateStack.push(([rootId], rootId, 0)) + # check if the root object is one of the garbage instances (has __del__) + objId = id(self.garbage[rootId]) + numDelInstances = choice(objId in self.garbageInstanceIds, 1, 0) + stateStack.push(([rootId], rootId, numDelInstances, 0)) while True: yield None if len(stateStack) == 0: break - candidateCycle, curId, resumeIndex = stateStack.pop() + candidateCycle, curId, numDelInstances, resumeIndex = stateStack.pop() if self.notify.getDebug(): - print 'restart: %s root=%s cur=%s resume=%s' % ( - candidateCycle, rootId, curId, resumeIndex) + print 'restart: %s root=%s cur=%s numDelInstances=%s resume=%s' % ( + candidateCycle, rootId, curId, numDelInstances, resumeIndex) for index in xrange(resumeIndex, len(self.referentsByNumber[curId])): yield None refId = self.referentsByNumber[curId][index] @@ -437,18 +467,24 @@ class GarbageReport(Job): normCandidateCycle = self._getNormalizedCycle(candidateCycle) normCandidateCycleTuple = tuple(normCandidateCycle) if not normCandidateCycleTuple in uniqueCycleSets: - if self.notify.getDebug(): - print ' FOUND: ', normCandidateCycle + [normCandidateCycle[0],] - cycles.append(normCandidateCycle + [normCandidateCycle[0],]) - uniqueCycleSets.add(normCandidateCycleTuple) + # cycles with no instances that define __del__ will be + # cleaned up by Python + if numDelInstances >= 1: + if self.notify.getDebug(): + print ' FOUND: ', normCandidateCycle + [normCandidateCycle[0],] + cycles.append(normCandidateCycle + [normCandidateCycle[0],]) + uniqueCycleSets.add(normCandidateCycleTuple) elif refId in candidateCycle: pass elif refId is not None: + # check if this object is one of the garbage instances (has __del__) + objId = id(self.garbage[refId]) + numDelInstances += choice(objId in self.garbageInstanceIds, 1, 0) # this refId does not complete a cycle. Mark down # where we are in this list of referents, then # start looking through the referents of the new refId - stateStack.push((list(candidateCycle), curId, index+1)) - stateStack.push((list(candidateCycle) + [refId], refId, 0)) + stateStack.push((list(candidateCycle), curId, numDelInstances, index+1)) + stateStack.push((list(candidateCycle) + [refId], refId, numDelInstances, 0)) break yield cycles diff --git a/direct/src/showbase/LeakDetectors.py b/direct/src/showbase/LeakDetectors.py index 19030bf44c..f2fda9a74f 100755 --- a/direct/src/showbase/LeakDetectors.py +++ b/direct/src/showbase/LeakDetectors.py @@ -1,7 +1,6 @@ # objects that report different types of leaks to the ContainerLeakDetector from pandac.PandaModules import * -from direct.showbase.PythonUtil import gcDebugOn import __builtin__, gc class LeakDetector: @@ -24,15 +23,12 @@ class GarbageLeakDetector(LeakDetector): # are we accumulating Python garbage? def __len__(self): # do a garbage collection - wasOn = gcDebugOn() oldFlags = gc.get_debug() - if not wasOn: - gc.set_debug(gc.DEBUG_SAVEALL) + gc.set_debug(0) gc.collect() numGarbage = len(gc.garbage) del gc.garbage[:] - if not wasOn: - gc.set_debug(oldFlags) + gc.set_debug(oldFlags) return numGarbage class SceneGraphLeakDetector(LeakDetector): diff --git a/direct/src/showbase/PythonUtil.py b/direct/src/showbase/PythonUtil.py index 53b7030e7a..859a88069b 100644 --- a/direct/src/showbase/PythonUtil.py +++ b/direct/src/showbase/PythonUtil.py @@ -18,7 +18,7 @@ __all__ = ['enumerate', 'unique', 'indent', 'nonRepeatingRandomList', 'clampScalar', 'weightedChoice', 'randFloat', 'normalDistrib', 'weightedRand', 'randUint31', 'randInt32', 'randUint32', 'SerialNumGen', 'serialNum', 'uniqueName', 'Enum', 'Singleton', -'SingletonError', 'printListEnum', 'gcDebugOn', 'safeRepr', +'SingletonError', 'printListEnum', 'safeRepr', 'fastRepr', 'tagRepr', 'tagWithCaller', 'isDefaultValue', 'set_trace', 'pm', 'ScratchPad', 'Sync', 'RefCounter', 'itype', 'getNumberedTypedString', 'getNumberedTypedSortedString', 'getNumberedTypedSortedStringWithReferrers', @@ -2444,10 +2444,6 @@ def printListEnum(l): for result in printListEnumGen(l): pass -def gcDebugOn(): - import gc - return (gc.get_debug() & gc.DEBUG_SAVEALL) == gc.DEBUG_SAVEALL - # base class for all Panda C++ objects # libdtoolconfig doesn't seem to have this, grab it off of PandaNode dtoolSuperBase = None