|
@@ -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
|