Browse Source

don't use gc.get_referents (it's slow), more fine-grained CPU usage

Darren Ranalli 18 years ago
parent
commit
786af32d44
1 changed files with 160 additions and 96 deletions
  1. 160 96
      direct/src/showbase/ContainerLeakDetector.py

+ 160 - 96
direct/src/showbase/ContainerLeakDetector.py

@@ -1,16 +1,29 @@
+from pandac.PandaModules import PStatCollector
 from direct.directnotify.DirectNotifyGlobal import directNotify
 from direct.directnotify.DirectNotifyGlobal import directNotify
 from direct.showbase.PythonUtil import Queue, invertDictLossless
 from direct.showbase.PythonUtil import Queue, invertDictLossless
 from direct.showbase.PythonUtil import itype, serialNum, safeRepr
 from direct.showbase.PythonUtil import itype, serialNum, safeRepr
 from direct.showbase.Job import Job
 from direct.showbase.Job import Job
-import types, weakref, gc, random, __builtin__
+import types, weakref, random, __builtin__
 
 
 def _createContainerLeak():
 def _createContainerLeak():
     def leakContainer(task):
     def leakContainer(task):
-        if not hasattr(simbase, 'leakContainer'):
-            simbase.leakContainer = []
-        simbase.leakContainer.append(1)
+        base = getBase()
+        if not hasattr(base, 'leakContainer'):
+            base.leakContainer = {}
+        # use tuples as keys since they can't be weakref'd, and use an instance
+        # since it can't be repr/eval'd
+        # that will force the leak detector to hold a normal 'non-weak' reference
+        class LeakKey:
+            pass
+        base.leakContainer[(LeakKey(),)] = {}
+        # test the non-weakref object reference handling
+        if random.random() < .01:
+            key = random.choice(base.leakContainer.keys())
+            ContainerLeakDetector.notify.debug(
+                'removing reference to leakContainer key %s so it will be garbage-collected' % key)
+            del base.leakContainer[key]
         return task.cont
         return task.cont
-    taskMgr.add(leakContainer, 'leakContainer-%s' % serialNum())
+    task = taskMgr.add(leakContainer, 'leakContainer-%s' % serialNum())
 
 
 class CheckContainers(Job):
 class CheckContainers(Job):
     """
     """
@@ -41,13 +54,14 @@ class CheckContainers(Job):
                 container = self._leakDetector.getContainerById(id)
                 container = self._leakDetector.getContainerById(id)
             except Exception, e:
             except Exception, e:
                 # this container no longer exists
                 # this container no longer exists
-                self.notify.debug('container %s no longer exists; caught exception in getContainerById (%s)' % (
+                self.notify.debug(
+                    '%s no longer exists; caught exception in getContainerById (%s)' % (
                     self._leakDetector.getContainerNameById(id), e))
                     self._leakDetector.getContainerNameById(id), e))
                 self._leakDetector.removeContainerById(id)
                 self._leakDetector.removeContainerById(id)
                 continue
                 continue
             if container is None:
             if container is None:
                 # this container no longer exists
                 # this container no longer exists
-                self.notify.debug('container %s no longer exists; getContainerById returned None' %
+                self.notify.debug('%s no longer exists; getContainerById returned None' %
                                   self._leakDetector.getContainerNameById(id))
                                   self._leakDetector.getContainerNameById(id))
                 self._leakDetector.removeContainerById(id)
                 self._leakDetector.removeContainerById(id)
                 continue
                 continue
@@ -55,8 +69,9 @@ class CheckContainers(Job):
                 cLen = len(container)
                 cLen = len(container)
             except Exception, e:
             except Exception, e:
                 # this container no longer exists
                 # this container no longer exists
-                self.notify.debug('%s is no longer a container, it is now %s (%s)' %
-                                  (self._leakDetector.getContainerNameById(id), safeRepr(container), e))
+                self.notify.debug(
+                    '%s is no longer a container, it is now %s (%s)' %
+                    (self._leakDetector.getContainerNameById(id), safeRepr(container), e))
                 self._leakDetector.removeContainerById(id)
                 self._leakDetector.removeContainerById(id)
                 continue
                 continue
             self._leakDetector._index2containerId2len[self._index][id] = cLen
             self._leakDetector._index2containerId2len[self._index][id] = cLen
@@ -72,7 +87,12 @@ class CheckContainers(Job):
                             minutes = (self._leakDetector._index2delay[self._index] -
                             minutes = (self._leakDetector._index2delay[self._index] -
                                        self._leakDetector._index2delay[self._index-1]) / 60.
                                        self._leakDetector._index2delay[self._index-1]) / 60.
                             name = self._leakDetector.getContainerNameById(id)
                             name = self._leakDetector.getContainerNameById(id)
-                            self.notify.warning('container %s grew > 200%% in %s minutes' % (name, minutes))
+                            if idx2id2len[self._index-1][id] != 0:
+                                percent = int(100. * (float(diff) / idx2id2len[self._index-1][id]))
+                                self.notify.warning(
+                                    '%s grew %s%% in %s minutes (currently %s items)' % (
+                                    name, percent, minutes, idx2id2len[self._index][id]))
+                                yield None
                     if (self._index > 3 and
                     if (self._index > 3 and
                         id in idx2id2len[self._index-2] and
                         id in idx2id2len[self._index-2] and
                         id in idx2id2len[self._index-3]):
                         id in idx2id2len[self._index-3]):
@@ -81,32 +101,36 @@ class CheckContainers(Job):
                         if self._index <= 5:
                         if self._index <= 5:
                             if diff > 0 and diff2 > 0 and diff3 > 0:
                             if diff > 0 and diff2 > 0 and diff3 > 0:
                                 name = self._leakDetector.getContainerNameById(id)
                                 name = self._leakDetector.getContainerNameById(id)
-                                msg = ('%s consistently increased in length over the last 3 periods (currently %s items)' %
+                                msg = ('%s consistently increased in length over the last '
+                                       '3 periods (currently %s items)' %
                                        (name, idx2id2len[self._index][id]))
                                        (name, idx2id2len[self._index][id]))
                                 self.notify.warning(msg)
                                 self.notify.warning(msg)
+                                yield None
                         elif (id in idx2id2len[self._index-4] and
                         elif (id in idx2id2len[self._index-4] and
                               id in idx2id2len[self._index-5]):
                               id in idx2id2len[self._index-5]):
-                            # if size has consistently increased over the last 5 checks, send out a warning
+                            # if size has consistently increased over the last 5 checks,
+                            # send out a warning
                             diff4 = idx2id2len[self._index-3][id] - idx2id2len[self._index-4][id]
                             diff4 = idx2id2len[self._index-3][id] - idx2id2len[self._index-4][id]
                             diff5 = idx2id2len[self._index-4][id] - idx2id2len[self._index-5][id]
                             diff5 = idx2id2len[self._index-4][id] - idx2id2len[self._index-5][id]
                             if diff > 0 and diff2 > 0 and diff3 > 0 and diff4 > 0 and diff5 > 0:
                             if diff > 0 and diff2 > 0 and diff3 > 0 and diff4 > 0 and diff5 > 0:
                                 name = self._leakDetector.getContainerNameById(id)
                                 name = self._leakDetector.getContainerNameById(id)
-                                msg = ('%s consistently increased in length over the last 5 periods (currently %s items), notifying system' %
+                                msg = ('%s consistently increased in length over the last '
+                                       '5 periods (currently %s items)' %
                                        (name, idx2id2len[self._index][id]))
                                        (name, idx2id2len[self._index][id]))
-                                self.notify.warning(msg)
+                                self.notify.warning('%s, sending notification' % msg)
+                                yield None
                                 messenger.send(self._leakDetector.getLeakEvent(), [msg])
                                 messenger.send(self._leakDetector.getLeakEvent(), [msg])
         yield Job.Done
         yield Job.Done
 
 
 class PruneContainerRefs(Job):
 class PruneContainerRefs(Job):
     """
     """
-    Job to destroy any container refs that have Indirections that are holding references
-    to objects that should be garbage-collected
+    Job to destroy any container refs that are no longer valid.
+    Checks validity by asking for each container
     """
     """
     def __init__(self, name, leakDetector):
     def __init__(self, name, leakDetector):
         Job.__init__(self, name)
         Job.__init__(self, name)
         self._leakDetector = leakDetector
         self._leakDetector = leakDetector
         self.notify = self._leakDetector.notify
         self.notify = self._leakDetector.notify
-        self._index = index
         ContainerLeakDetector.addPrivateId(self.__dict__)
         ContainerLeakDetector.addPrivateId(self.__dict__)
 
 
     def destroy(self):
     def destroy(self):
@@ -114,23 +138,29 @@ class PruneContainerRefs(Job):
         Job.destroy(self)
         Job.destroy(self)
 
 
     def getPriority(self):
     def getPriority(self):
-        return Job.Priorities.Normal
+        return Job.Priorities.Normal-1
     
     
     def run(self):
     def run(self):
         ids = self._leakDetector._id2ref.keys()
         ids = self._leakDetector._id2ref.keys()
         for id in ids:
         for id in ids:
             yield None
             yield None
-            ref = self._leakDetector._id2ref[id]
-            ref.destroyIfGarbageDictKey()
+            try:
+                container = self._leakDetector.getContainerById(id)
+            except:
+                # reference is invalid, remove it
+                self._leakDetector.removeContainerById(id)
         yield Job.Done
         yield Job.Done
 
 
 class NoDictKey:
 class NoDictKey:
     pass
     pass
 
 
 class Indirection:
 class Indirection:
-    # represents the indirection that brings you from a container to an element of the container
-    # stored as a string to be used as part of an eval
-    # each dictionary dereference is individually eval'd since the dict key might have been garbage-collected
+    """
+    Represents the indirection that brings you from a container to an element of the container.
+    Stored as a string to be used as part of an eval, or as a key to be looked up in a dict.
+    Each dictionary dereference is individually eval'd since the dict key might have been
+    garbage-collected
+    """
 
 
     def __init__(self, evalStr=None, dictKey=NoDictKey):
     def __init__(self, evalStr=None, dictKey=NoDictKey):
         # if this is a dictionary lookup, pass dictKey instead of evalStr
         # if this is a dictionary lookup, pass dictKey instead of evalStr
@@ -161,11 +191,13 @@ class Indirection:
                     self.dictKey = weakref.ref(dictKey)
                     self.dictKey = weakref.ref(dictKey)
                     self._isWeakRef = True
                     self._isWeakRef = True
                 except TypeError, e:
                 except TypeError, e:
+                    ContainerLeakDetector.notify.debug('could not weakref dict key %s' % dictKey)
                     self.dictKey = dictKey
                     self.dictKey = dictKey
                     self._isWeakRef = False
                     self._isWeakRef = False
 
 
     def destroy(self):
     def destroy(self):
-        del self.dictKey
+        # re-entrant
+        self.dictKey = NoDictKey
 
 
     def acquire(self):
     def acquire(self):
         self._refCount += 1
         self._refCount += 1
@@ -177,13 +209,6 @@ class Indirection:
     def isDictKey(self):
     def isDictKey(self):
         # is this an indirection through a dictionary?
         # is this an indirection through a dictionary?
         return self.dictKey is not NoDictKey
         return self.dictKey is not NoDictKey
-    def isGarbageDictKey(self):
-        # are we holding a non-weak reference to an object that should be
-        # garbage-collected?
-        if self.isDictKey() and not self._isWeakRef:
-            referrers = gc.get_referrers(self.dictKey)
-            print referrers
-            import pdb;pdb.set_trace()
 
 
     def _getNonWeakDictKey(self):
     def _getNonWeakDictKey(self):
         if not self._isWeakRef:
         if not self._isWeakRef:
@@ -216,8 +241,6 @@ class Indirection:
             return self.evalStr
             return self.evalStr
 
 
         # we're stored as a dict key
         # we're stored as a dict key
-        # this might not eval, but that's OK, we're not using this string to find
-        # the object, we dereference the parent dict
         keyRepr = safeRepr(self._getNonWeakDictKey())
         keyRepr = safeRepr(self._getNonWeakDictKey())
         # if the previous indirection was an instance dict, change our syntax from ['key'] to .key
         # if the previous indirection was an instance dict, change our syntax from ['key'] to .key
         if prevIndirection is not None and prevIndirection.evalStr is not None:
         if prevIndirection is not None and prevIndirection.evalStr is not None:
@@ -231,7 +254,7 @@ class Indirection:
 class ContainerRef:
 class ContainerRef:
     """
     """
     stores a reference to a container in a way that does not prevent garbage
     stores a reference to a container in a way that does not prevent garbage
-    collection of the container
+    collection of the container if possible
     stored as a series of 'indirections' (obj.foo -> '.foo', dict[key] -> '[key]', etc.)
     stored as a series of 'indirections' (obj.foo -> '.foo', dict[key] -> '[key]', etc.)
     """
     """
     class FailedEval(Exception):
     class FailedEval(Exception):
@@ -246,17 +269,10 @@ class ContainerRef:
         self.addIndirection(indirection)
         self.addIndirection(indirection)
 
 
     def destroy(self):
     def destroy(self):
+        # re-entrant
         for indirection in self._indirections:
         for indirection in self._indirections:
             indirection.release()
             indirection.release()
-        del self._indirections
-
-    def destroyIfGarbageDictKey(self):
-        # if any of our indirections are holding onto objects that
-        # should be garbage-collected, destroy
-        for indirection in self._indirections:
-            if indirection.isGarbageDictKey():
-                self.destroy()
-                return
+        self._indirections = []
 
 
     def addIndirection(self, indirection):
     def addIndirection(self, indirection):
         indirection.acquire()
         indirection.acquire()
@@ -283,6 +299,7 @@ class ContainerRef:
         evalStr = ''
         evalStr = ''
         curObj = None
         curObj = None
         for indirection in self._indirections:
         for indirection in self._indirections:
+            yield None
             if not indirection.isDictKey():
             if not indirection.isDictKey():
                 # build up a string to be eval'd
                 # build up a string to be eval'd
                 evalStr += indirection.getString()
                 evalStr += indirection.getString()
@@ -294,7 +311,7 @@ class ContainerRef:
                 curObj = indirection.dereferenceDictKey(curObj)
                 curObj = indirection.dereferenceDictKey(curObj)
                 evalStr = ''
                 evalStr = ''
 
 
-        return self._evalWithObj(evalStr, curObj)
+        yield self._evalWithObj(evalStr, curObj)
         
         
     def __repr__(self):
     def __repr__(self):
         str = ''
         str = ''
@@ -319,7 +336,8 @@ class ContainerLeakDetector(Job):
     """
     """
     Low-priority Python object-graph walker that looks for leaking containers.
     Low-priority Python object-graph walker that looks for leaking containers.
     To reduce memory usage, this does a random walk of the Python objects to
     To reduce memory usage, this does a random walk of the Python objects to
-    discover containers rather than keep a set of all visited objects.
+    discover containers rather than keep a set of all visited objects; it may
+    visit the same object many times but eventually it will discover every object.
     Checks container sizes at ever-increasing intervals.
     Checks container sizes at ever-increasing intervals.
     """
     """
     notify = directNotify.newCategory("ContainerLeakDetector")
     notify = directNotify.newCategory("ContainerLeakDetector")
@@ -371,6 +389,7 @@ class ContainerLeakDetector(Job):
             ]))
             ]))
 
 
     def destroy(self):
     def destroy(self):
+        self.ignoreAll()
         if self._checkContainersJob is not None:
         if self._checkContainersJob is not None:
             jobMgr.remove(self._checkContainersJob)
             jobMgr.remove(self._checkContainersJob)
             self._checkContainersJob = None
             self._checkContainersJob = None
@@ -401,17 +420,19 @@ class ContainerLeakDetector(Job):
         return self._id2ref.keys()
         return self._id2ref.keys()
 
 
     def getContainerById(self, id):
     def getContainerById(self, id):
-        return self._id2ref[id].getContainer()
+        for result in self._id2ref[id].getContainer():
+            pass
+        return result
     def getContainerNameById(self, id):
     def getContainerNameById(self, id):
         return repr(self._id2ref[id])
         return repr(self._id2ref[id])
     def removeContainerById(self, id):
     def removeContainerById(self, id):
+        self._id2ref[id].destroy()
         del self._id2ref[id]
         del self._id2ref[id]
 
 
     def run(self):
     def run(self):
         taskMgr.doMethodLater(self._nextCheckDelay, self._checkForLeaks,
         taskMgr.doMethodLater(self._nextCheckDelay, self._checkForLeaks,
                               self._getCheckTaskName())
                               self._getCheckTaskName())
-        taskMgr.doMethodLater(self._pruneTaskPeriod, self._pruneContainerRefs,
-                              self._getPruneTaskName())
+        self._scheduleNextPruning()
 
 
         while True:
         while True:
             # yield up here instead of at the end, since we skip back to the
             # yield up here instead of at the end, since we skip back to the
@@ -422,19 +443,16 @@ class ContainerLeakDetector(Job):
             if self._curObjRef is None:
             if self._curObjRef is None:
                 self._curObjRef = self._baseObjRef
                 self._curObjRef = self._baseObjRef
             try:
             try:
-                curObj = self._curObjRef.getContainer()
+                for result in self._curObjRef.getContainer():
+                    yield None
+                curObj = result
             except:
             except:
                 self.notify.debug('lost current container: %s' % self._curObjRef)
                 self.notify.debug('lost current container: %s' % self._curObjRef)
-                while len(self._id2ref):
-                    _id = random.choice(self._id2ref.keys())
-                    curObj = self.getContainerById(_id)
-                    if curObj is not None:
-                        break
-                    # container is no longer valid
-                    del self._id2ref[_id]
-                self._curObjRef = self._id2ref[_id]
+                # that container is gone, try again
+                self._curObjRef = None
+                continue
             #print '%s: %s, %s' % (id(curObj), type(curObj), self._id2ref[id(curObj)])
             #print '%s: %s, %s' % (id(curObj), type(curObj), self._id2ref[id(curObj)])
-            #self.notify.debug('--> %s' % self._curObjRef)
+            self.notify.debug('--> %s' % self._curObjRef)
 
 
             # keep a copy of this obj's eval str, it might not be in _id2ref
             # keep a copy of this obj's eval str, it might not be in _id2ref
             curObjRef = self._curObjRef
             curObjRef = self._curObjRef
@@ -443,34 +461,48 @@ class ContainerLeakDetector(Job):
 
 
             if type(curObj) in (types.ModuleType, types.InstanceType):
             if type(curObj) in (types.ModuleType, types.InstanceType):
                 child = curObj.__dict__
                 child = curObj.__dict__
-                if not self._isDeadEnd(child):
-                    self._curObjRef = ContainerRef(Indirection(evalStr='.__dict__'), curObjRef)
-                    if self._isContainer(child):
-                        self._nameContainer(child, self._curObjRef)
+                isContainer = self._isContainer(child)
+                notDeadEnd = not self._isDeadEnd(child)
+                if isContainer or notDeadEnd:
+                    objRef = ContainerRef(Indirection(evalStr='.__dict__'), curObjRef)
+                    yield None
+                    if isContainer:
+                        self._nameContainer(child, objRef)
+                    if notDeadEnd:
+                        self._curObjRef = objRef
                 continue
                 continue
 
 
             if type(curObj) is types.DictType:
             if type(curObj) is types.DictType:
                 key = None
                 key = None
                 attr = None
                 attr = None
                 keys = curObj.keys()
                 keys = curObj.keys()
-                # we will continue traversing the object graph via the last container
-                # in the list; shuffle the list to randomize the traversal
-                random.shuffle(keys)
+                # we will continue traversing the object graph via one key of the dict,
+                # choose it at random without taking a big chunk of CPU time
+                numKeysLeft = len(keys)
+                nextObjRef = None
                 for key in keys:
                 for key in keys:
+                    yield None
                     try:
                     try:
                         attr = curObj[key]
                         attr = curObj[key]
                     except KeyError, e:
                     except KeyError, e:
                         self.notify.warning('could not index into %s with key %s' % (curObjRef, key))
                         self.notify.warning('could not index into %s with key %s' % (curObjRef, key))
                         continue
                         continue
-                    if not self._isDeadEnd(attr, key):
+                    isContainer = self._isContainer(attr)
+                    notDeadEnd = False
+                    if nextObjRef is None:
+                        notDeadEnd = not self._isDeadEnd(attr, key)
+                    if isContainer or notDeadEnd:
                         if curObj is __builtin__.__dict__:
                         if curObj is __builtin__.__dict__:
-                            indirection=Indirection(evalStr=key)
-                            self._curObjRef = ContainerRef(indirection)
+                            objRef = ContainerRef(Indirection(evalStr=key))
                         else:
                         else:
-                            indirection=Indirection(dictKey=key)
-                            self._curObjRef = ContainerRef(indirection, curObjRef)
-                        if self._isContainer(attr):
-                            self._nameContainer(attr, self._curObjRef)
+                            objRef = ContainerRef(Indirection(dictKey=key), curObjRef)
+                        yield None
+                        if isContainer:
+                            self._nameContainer(attr, objRef)
+                        if notDeadEnd and nextObjRef is None:
+                            if random.randrange(numKeysLeft) == 0:
+                                nextObjRef = objRef
+                        numKeysLeft -= 1
                 del key
                 del key
                 del attr
                 del attr
                 continue
                 continue
@@ -485,6 +517,7 @@ class ContainerLeakDetector(Job):
                         index = 0
                         index = 0
                         attrs = []
                         attrs = []
                         while 1:
                         while 1:
+                            yield None
                             try:
                             try:
                                 attr = itr.next()
                                 attr = itr.next()
                             except:
                             except:
@@ -492,14 +525,25 @@ class ContainerLeakDetector(Job):
                                 attr = None
                                 attr = None
                                 break
                                 break
                             attrs.append(attr)
                             attrs.append(attr)
-                        # we will continue traversing the object graph via the last container
-                        # in the list; shuffle the list to randomize the traversal
-                        random.shuffle(attrs)
+                        # we will continue traversing the object graph via one attr,
+                        # choose it at random without taking a big chunk of CPU time
+                        numAttrsLeft = len(attrs)
+                        nextObjRef = None
                         for attr in attrs:
                         for attr in attrs:
-                            if not self._isDeadEnd(attr):
-                                self._curObjRef = ContainerRef(Indirection(evalStr='[%s]' % index), curObjRef)
-                                if self._isContainer(attr):
-                                    self._nameContainer(attr, self._curObjRef)
+                            yield None
+                            isContainer = self._isContainer(attr)
+                            notDeadEnd = False
+                            if nextObjRef is None:
+                                notDeadEnd = not self._isDeadEnd(attr)
+                            if isContainer or notDeadEnd:
+                                objRef = ContainerRef(Indirection(evalStr='[%s]' % index), curObjRef)
+                                yield None
+                                if isContainer:
+                                    self._nameContainer(attr, objRef)
+                                if notDeadEnd and nextObjRef is None:
+                                    if random.randrange(numAttrsLeft) == 0:
+                                        nextObjRef = objRef
+                            numAttrsLeft -= 1
                             index += 1
                             index += 1
                         del attr
                         del attr
                     except StopIteration, e:
                     except StopIteration, e:
@@ -514,15 +558,26 @@ class ContainerLeakDetector(Job):
             else:
             else:
                 childName = None
                 childName = None
                 child = None
                 child = None
-                # we will continue traversing the object graph via the last container
-                # in the list; shuffle the list to randomize the traversal
-                random.shuffle(childNames)
+                # we will continue traversing the object graph via one child,
+                # choose it at random without taking a big chunk of CPU time
+                numChildrenLeft = len(childNames)
+                nextObjRef = None
                 for childName in childNames:
                 for childName in childNames:
+                    yield None
                     child = getattr(curObj, childName)
                     child = getattr(curObj, childName)
-                    if not self._isDeadEnd(child, childName):
-                        self._curObjRef = ContainerRef(Indirection(evalStr='.%s' % childName), curObjRef)
-                        if self._isContainer(child):
-                            self._nameContainer(child, self._curObjRef)
+                    isContainer = self._isContainer(child)
+                    notDeadEnd = False
+                    if nextObjRef is None:
+                        notDeadEnd = not self._isDeadEnd(child, childName)
+                    if isContainer or notDeadEnd:
+                        objRef = ContainerRef(Indirection(evalStr='.%s' % childName), curObjRef)
+                        yield None
+                        if isContainer:
+                            self._nameContainer(child, objRef)
+                        if notDeadEnd and nextObjRef is None:
+                            if random.randrange(numChildrenLeft) == 0:
+                                nextObjRef = objRef
+                    numChildrenLeft -= 1
                 del childName
                 del childName
                 del child
                 del child
                 continue
                 continue
@@ -535,9 +590,8 @@ class ContainerLeakDetector(Job):
                          types.FloatType, types.IntType, types.LongType,
                          types.FloatType, types.IntType, types.LongType,
                          types.NoneType, types.NotImplementedType,
                          types.NoneType, types.NotImplementedType,
                          types.TypeType, types.CodeType, types.FunctionType,
                          types.TypeType, types.CodeType, types.FunctionType,
-                         types.StringType, types.UnicodeType):
-            return True
-        if id(obj) in self._id2ref:
+                         types.StringType, types.UnicodeType,
+                         types.TupleType):
             return True
             return True
         # if it's an internal object, ignore it
         # if it's an internal object, ignore it
         if id(obj) in ContainerLeakDetector.PrivateIds:
         if id(obj) in ContainerLeakDetector.PrivateIds:
@@ -553,7 +607,6 @@ class ContainerLeakDetector(Job):
             if className == 'method-wrapper':
             if className == 'method-wrapper':
                 return True
                 return True
         return False
         return False
-            
 
 
     def _isContainer(self, obj):
     def _isContainer(self, obj):
         try:
         try:
@@ -572,21 +625,32 @@ class ContainerLeakDetector(Job):
         # if this container is new, or the objRef repr is shorter than what we already have,
         # if this container is new, or the objRef repr is shorter than what we already have,
         # put it in the table
         # put it in the table
         if contId not in self._id2ref or len(repr(objRef)) < len(repr(self._id2ref[contId])):
         if contId not in self._id2ref or len(repr(objRef)) < len(repr(self._id2ref[contId])):
+            if contId in self._id2ref:
+                self.removeContainerById(contId)
             self._id2ref[contId] = objRef
             self._id2ref[contId] = objRef
 
 
+    def _scheduleNextLeakCheck(self):
+        taskMgr.doMethodLater(self._nextCheckDelay, self._checkForLeaks,
+                              self._getCheckTaskName())
+        self._nextCheckDelay *= 2
+
     def _checkForLeaks(self, task=None):
     def _checkForLeaks(self, task=None):
         self._index2delay[len(self._index2containerId2len)] = self._nextCheckDelay
         self._index2delay[len(self._index2containerId2len)] = self._nextCheckDelay
         self._checkContainersJob = CheckContainers(
         self._checkContainersJob = CheckContainers(
             '%s-checkForLeaks' % self.getJobName(), self, len(self._index2containerId2len))
             '%s-checkForLeaks' % self.getJobName(), self, len(self._index2containerId2len))
+        self.acceptOnce(self._checkContainersJob.getFinishedEvent(),
+                        self._scheduleNextLeakCheck)
         jobMgr.add(self._checkContainersJob)
         jobMgr.add(self._checkContainersJob)
-        
-        self._nextCheckDelay *= 2
-        taskMgr.doMethodLater(self._nextCheckDelay, self._checkForLeaks,
-                              self._getCheckTaskName())
         return task.done
         return task.done
 
 
-    def _pruneContainerRefs(self, task=None):
+    def _scheduleNextPruning(self):
         taskMgr.doMethodLater(self._pruneTaskPeriod, self._pruneContainerRefs,
         taskMgr.doMethodLater(self._pruneTaskPeriod, self._pruneContainerRefs,
                               self._getPruneTaskName())
                               self._getPruneTaskName())
-        return task.done
 
 
+    def _pruneContainerRefs(self, task=None):
+        self._pruneContainersJob = PruneContainerRefs(
+            '%s-pruneContainerRefs' % self.getJobName(), self)
+        self.acceptOnce(self._pruneContainersJob.getFinishedEvent(),
+                        self._scheduleNextPruning)
+        jobMgr.add(self._pruneContainersJob)
+        return task.done