Browse Source

stop using gc.DEBUG_SAVEALL (not necessary), GarbageReport output readability improvements

Darren Ranalli 17 years ago
parent
commit
fb1aa31b78

+ 0 - 4
direct/src/distributed/ConnectionRepository.py

@@ -98,10 +98,6 @@ class ConnectionRepository(
 
 
         self._serverAddress = ''
         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 generateGlobalObject(self, doId, dcname, values=None):
         def applyFieldValues(distObj, dclass, values):
         def applyFieldValues(distObj, dclass, values):
             for i in range(dclass.getNumInheritedFields()):
             for i in range(dclass.getNumInheritedFields()):

+ 67 - 31
direct/src/showbase/GarbageReport.py

@@ -3,14 +3,15 @@
 __all__ = ['FakeObject', '_createGarbage', 'GarbageReport', 'GarbageLogger']
 __all__ = ['FakeObject', '_createGarbage', 'GarbageReport', 'GarbageLogger']
 
 
 from direct.directnotify.DirectNotifyGlobal import directNotify
 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.PythonUtil import AlphabetCounter
 from direct.showbase.Job import Job
 from direct.showbase.Job import Job
 import gc
 import gc
 import types
 import types
 
 
 class FakeObject:
 class FakeObject:
-    pass
+    def __del__(self):
+        pass
 
 
 def _createGarbage(num=1):
 def _createGarbage(num=1):
     for i in xrange(num):
     for i in xrange(num):
@@ -46,12 +47,39 @@ class GarbageReport(Job):
 
 
     def run(self):
     def run(self):
         # do the garbage collection
         # do the garbage collection
-        wasOn = gcDebugOn()
         oldFlags = gc.get_debug()
         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()
+        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()
         gc.collect()
-        self.garbage = list(gc.garbage)
+        self.garbage = gc.garbage[:]
+        del gc.garbage[:]
         # only yield if there's more time-consuming work to do,
         # only yield if there's more time-consuming work to do,
         # if there's no garbage, give instant feedback
         # if there's no garbage, give instant feedback
         if len(self.garbage) > 0:
         if len(self.garbage) > 0:
@@ -59,9 +87,7 @@ class GarbageReport(Job):
         # don't repr the garbage list if we don't have to
         # don't repr the garbage list if we don't have to
         if self.notify.getDebug():
         if self.notify.getDebug():
             self.notify.debug('self.garbage == %s' % fastRepr(self.garbage))
             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)
         self.numGarbage = len(self.garbage)
         # only yield if there's more time-consuming work to do,
         # only yield if there's more time-consuming work to do,
@@ -70,7 +96,7 @@ class GarbageReport(Job):
             yield None
             yield None
 
 
         if self._args.verbose:
         if self._args.verbose:
-            self.notify.info('found %s garbage items' % self.numGarbage)
+            self.notify.info('found %s garbage instances' % self.numGarbageInstances)
 
 
         """ spammy
         """ spammy
         # print the types of the garbage first, in case the repr of an object
         # print the types of the garbage first, in case the repr of an object
@@ -102,7 +128,7 @@ class GarbageReport(Job):
                 yield None
                 yield None
 
 
         # grab the referrers (pointing to garbage)
         # 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:
             if self._args.verbose:
                 self.notify.info('getting referrers...')
                 self.notify.info('getting referrers...')
             for i in xrange(self.numGarbage):
             for i in xrange(self.numGarbage):
@@ -114,7 +140,7 @@ class GarbageReport(Job):
                 self.referrersByReference[i] = byRef
                 self.referrersByReference[i] = byRef
 
 
         # grab the referents (pointed to by garbage)
         # grab the referents (pointed to by garbage)
-        if self.numGarbage > 0:
+        if self.numGarbageInstances > 0:
             if self._args.verbose:
             if self._args.verbose:
                 self.notify.info('getting referents...')
                 self.notify.info('getting referents...')
             for i in xrange(self.numGarbage):
             for i in xrange(self.numGarbage):
@@ -126,7 +152,7 @@ class GarbageReport(Job):
                 self.referentsByReference[i] = byRef
                 self.referentsByReference[i] = byRef
 
 
         # find the cycles
         # find the cycles
-        if self._args.findCycles and self.numGarbage > 0:
+        if self._args.findCycles and self.numGarbageInstances > 0:
             if self._args.verbose:
             if self._args.verbose:
                 self.notify.info('detecting cycles...')
                 self.notify.info('detecting cycles...')
             for i in xrange(self.numGarbage):
             for i in xrange(self.numGarbage):
@@ -221,12 +247,13 @@ class GarbageReport(Job):
                         self.cycleIds.update(set(cycle))
                         self.cycleIds.update(set(cycle))
 
 
         if self._args.findCycles:
         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:
         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
             # make a list of the ids we will actually be printing
             if self._args.fullReport:
             if self._args.fullReport:
                 garbageIndices = range(self.numGarbage)
                 garbageIndices = range(self.numGarbage)
@@ -277,14 +304,14 @@ class GarbageReport(Job):
                 s.append(format % (idx, itype(self.garbage[idx]), objStr))
                 s.append(format % (idx, itype(self.garbage[idx]), objStr))
 
 
             if self._args.findCycles:
             if self._args.findCycles:
-                s.append('===== Garbage Cycles By Index =====')
+                s.append('===== Garbage Cycles (Garbage Item Numbers) =====')
                 ac = AlphabetCounter()
                 ac = AlphabetCounter()
                 for i in xrange(len(self.cycles)):
                 for i in xrange(len(self.cycles)):
                     yield None
                     yield None
                     s.append('%s:%s' % (ac.next(), self.cycles[i]))
                     s.append('%s:%s' % (ac.next(), self.cycles[i]))
 
 
             if self._args.findCycles:
             if self._args.findCycles:
-                s.append('===== Garbage Cycles By Python Syntax =====')
+                s.append('===== Garbage Cycles (Python Syntax) =====')
                 ac = AlphabetCounter()
                 ac = AlphabetCounter()
                 for i in xrange(len(self.cyclesBySyntax)):
                 for i in xrange(len(self.cyclesBySyntax)):
                     yield None
                     yield None
@@ -314,7 +341,7 @@ class GarbageReport(Job):
         if self._args.log:
         if self._args.log:
             self.printingBegin()
             self.printingBegin()
             for i in xrange(len(self._report)):
             for i in xrange(len(self._report)):
-                if self.numGarbage > 0:
+                if self.numGarbageInstances > 0:
                     yield None
                     yield None
                 self.notify.info(self._report[i])
                 self.notify.info(self._report[i])
             self.printingEnd()
             self.printingEnd()
@@ -418,15 +445,18 @@ class GarbageReport(Job):
             uniqueCycleSets = set()
             uniqueCycleSets = set()
         stateStack = Stack()
         stateStack = Stack()
         rootId = index
         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:
         while True:
             yield None
             yield None
             if len(stateStack) == 0:
             if len(stateStack) == 0:
                 break
                 break
-            candidateCycle, curId, resumeIndex = stateStack.pop()
+            candidateCycle, curId, numDelInstances, resumeIndex = stateStack.pop()
             if self.notify.getDebug():
             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])):
             for index in xrange(resumeIndex, len(self.referentsByNumber[curId])):
                 yield None
                 yield None
                 refId = self.referentsByNumber[curId][index]
                 refId = self.referentsByNumber[curId][index]
@@ -437,18 +467,24 @@ class GarbageReport(Job):
                     normCandidateCycle = self._getNormalizedCycle(candidateCycle)
                     normCandidateCycle = self._getNormalizedCycle(candidateCycle)
                     normCandidateCycleTuple = tuple(normCandidateCycle)
                     normCandidateCycleTuple = tuple(normCandidateCycle)
                     if not normCandidateCycleTuple in uniqueCycleSets:
                     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:
                 elif refId in candidateCycle:
                     pass
                     pass
                 elif refId is not None:
                 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
                     # this refId does not complete a cycle. Mark down
                     # where we are in this list of referents, then
                     # where we are in this list of referents, then
                     # start looking through the referents of the new refId
                     # 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
                     break
         yield cycles
         yield cycles
 
 

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

@@ -1,7 +1,6 @@
 # objects that report different types of leaks to the ContainerLeakDetector
 # objects that report different types of leaks to the ContainerLeakDetector
 
 
 from pandac.PandaModules import *
 from pandac.PandaModules import *
-from direct.showbase.PythonUtil import gcDebugOn
 import __builtin__, gc
 import __builtin__, gc
 
 
 class LeakDetector:
 class LeakDetector:
@@ -24,15 +23,12 @@ class GarbageLeakDetector(LeakDetector):
     # are we accumulating Python garbage?
     # are we accumulating Python garbage?
     def __len__(self):
     def __len__(self):
         # do a garbage collection
         # do a garbage collection
-        wasOn = gcDebugOn()
         oldFlags = gc.get_debug()
         oldFlags = gc.get_debug()
-        if not wasOn:
-            gc.set_debug(gc.DEBUG_SAVEALL)
+        gc.set_debug(0)
         gc.collect()
         gc.collect()
         numGarbage = len(gc.garbage)
         numGarbage = len(gc.garbage)
         del gc.garbage[:]
         del gc.garbage[:]
-        if not wasOn:
-            gc.set_debug(oldFlags)
+        gc.set_debug(oldFlags)
         return numGarbage
         return numGarbage
 
 
 class SceneGraphLeakDetector(LeakDetector):
 class SceneGraphLeakDetector(LeakDetector):

+ 1 - 5
direct/src/showbase/PythonUtil.py

@@ -18,7 +18,7 @@ __all__ = ['enumerate', 'unique', 'indent', 'nonRepeatingRandomList',
 'clampScalar', 'weightedChoice', 'randFloat', 'normalDistrib',
 'clampScalar', 'weightedChoice', 'randFloat', 'normalDistrib',
 'weightedRand', 'randUint31', 'randInt32', 'randUint32',
 'weightedRand', 'randUint31', 'randInt32', 'randUint32',
 'SerialNumGen', 'serialNum', 'uniqueName', 'Enum', 'Singleton',
 'SerialNumGen', 'serialNum', 'uniqueName', 'Enum', 'Singleton',
-'SingletonError', 'printListEnum', 'gcDebugOn', 'safeRepr',
+'SingletonError', 'printListEnum', 'safeRepr',
 'fastRepr', 'tagRepr', 'tagWithCaller', 'isDefaultValue', 'set_trace', 'pm',
 'fastRepr', 'tagRepr', 'tagWithCaller', 'isDefaultValue', 'set_trace', 'pm',
 'ScratchPad', 'Sync', 'RefCounter', 'itype', 'getNumberedTypedString',
 'ScratchPad', 'Sync', 'RefCounter', 'itype', 'getNumberedTypedString',
 'getNumberedTypedSortedString', 'getNumberedTypedSortedStringWithReferrers',
 'getNumberedTypedSortedString', 'getNumberedTypedSortedStringWithReferrers',
@@ -2444,10 +2444,6 @@ def printListEnum(l):
     for result in printListEnumGen(l):
     for result in printListEnumGen(l):
         pass
         pass
 
 
-def gcDebugOn():
-    import gc
-    return (gc.get_debug() & gc.DEBUG_SAVEALL) == gc.DEBUG_SAVEALL
-
 # base class for all Panda C++ objects
 # base class for all Panda C++ objects
 # libdtoolconfig doesn't seem to have this, grab it off of PandaNode
 # libdtoolconfig doesn't seem to have this, grab it off of PandaNode
 dtoolSuperBase = None
 dtoolSuperBase = None