prevent cycles in the ObjectRefs (i.e. base.loader.base.loader)

This commit is contained in:
Darren Ranalli 2007-04-19 22:24:49 +00:00
parent f619fd68bd
commit f82ac8cfde

View File

@ -129,39 +129,55 @@ class Indirection:
def __repr__(self): def __repr__(self):
return self.getString() return self.getString()
class ContainerRef: class ObjectRef:
""" """
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 if possible 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.)
""" """
notify = directNotify.newCategory("ContainerRef") notify = directNotify.newCategory("ObjectRef")
class FailedEval(Exception): class FailedEval(Exception):
pass pass
def __init__(self, indirection, other=None): def __init__(self, indirection, objId, other=None):
self._indirections = [] self._indirections = []
# this is a cache of the ids of our component objects
self._objIds = set()
# are we building off of an existing ref? # are we building off of an existing ref?
if other is not None: if other is not None:
self._objIds = set(other._objIds)
for ind in other._indirections: for ind in other._indirections:
self.addIndirection(ind) self._indirections.append(ind)
self.addIndirection(indirection) self._indirections.append(indirection)
# make sure we're not storing a reference to the actual object,
# that could cause a memory leak
assert type(objId) in (types.IntType, types.LongType)
# prevent cycles (i.e. base.loader.base.loader)
assert objId not in self._objIds
self._objIds.add(objId)
# make sure our indirections don't get destroyed while we're using them
for ind in self._indirections:
ind.acquire()
self.notify.debug(repr(self)) self.notify.debug(repr(self))
def destroy(self): def destroy(self):
# re-entrant
for indirection in self._indirections: for indirection in self._indirections:
indirection.release() indirection.release()
self._indirections = [] del self._indirections
def addIndirection(self, indirection):
indirection.acquire()
self._indirections.append(indirection)
def getNumIndirections(self): def getNumIndirections(self):
return len(self._indirections) return len(self._indirections)
def goesThrough(self, obj):
# since we cache the ids involved in this reference,
# this isn't perfect, for example if base.myObject is reassigned
# to a different object after this Ref was created this would return
# false, allowing a ref to base.myObject.otherObject.myObject
return id(obj) in self._objIds
def _getContainerByEval(self, evalStr, curObj=None): def _getContainerByEval(self, evalStr, curObj=None):
if curObj is not None: if curObj is not None:
# eval('curObj.foo.bar.someDict') # eval('curObj.foo.bar.someDict')
@ -263,7 +279,7 @@ class FindContainers(Job):
ContainerLeakDetector.addPrivateObj(self.__dict__) ContainerLeakDetector.addPrivateObj(self.__dict__)
# set up the base containers, the ones that hold most objects # set up the base containers, the ones that hold most objects
ref = ContainerRef(Indirection(evalStr='__builtin__.__dict__')) ref = ObjectRef(Indirection(evalStr='__builtin__.__dict__'), id(__builtin__.__dict__))
self._id2baseStartRef[id(__builtin__.__dict__)] = ref self._id2baseStartRef[id(__builtin__.__dict__)] = ref
# container for objects that want to make sure they are found by # container for objects that want to make sure they are found by
# the object exploration algorithm, including objects that exist # the object exploration algorithm, including objects that exist
@ -271,7 +287,7 @@ class FindContainers(Job):
# framerate, etc. See LeakDetectors.py # framerate, etc. See LeakDetectors.py
if not hasattr(__builtin__, "leakDetectors"): if not hasattr(__builtin__, "leakDetectors"):
__builtin__.leakDetectors = {} __builtin__.leakDetectors = {}
ref = ContainerRef(Indirection(evalStr='leakDetectors')) ref = ObjectRef(Indirection(evalStr='leakDetectors'), id(leakDetectors))
self._id2baseStartRef[id(leakDetectors)] = ref self._id2baseStartRef[id(leakDetectors)] = ref
for i in self._addContainerGen(__builtin__.__dict__, ref): for i in self._addContainerGen(__builtin__.__dict__, ref):
pass pass
@ -280,7 +296,7 @@ class FindContainers(Job):
except: except:
pass pass
else: else:
ref = ContainerRef(Indirection(evalStr='base.__dict__')) ref = ObjectRef(Indirection(evalStr='base.__dict__'), id(base.__dict__))
self._id2baseStartRef[id(base.__dict__)] = ref self._id2baseStartRef[id(base.__dict__)] = ref
for i in self._addContainerGen(base.__dict__, ref): for i in self._addContainerGen(base.__dict__, ref):
pass pass
@ -289,7 +305,7 @@ class FindContainers(Job):
except: except:
pass pass
else: else:
ref = ContainerRef(Indirection(evalStr='simbase.__dict__')) ref = ObjectRef(Indirection(evalStr='simbase.__dict__'), id(simbase.__dict__))
self._id2baseStartRef[id(simbase.__dict__)] = ref self._id2baseStartRef[id(simbase.__dict__)] = ref
for i in self._addContainerGen(simbase.__dict__, ref): for i in self._addContainerGen(simbase.__dict__, ref):
pass pass
@ -459,7 +475,10 @@ class FindContainers(Job):
hasLength = self._hasLength(child) hasLength = self._hasLength(child)
notDeadEnd = not self._isDeadEnd(child) notDeadEnd = not self._isDeadEnd(child)
if hasLength or notDeadEnd: if hasLength or notDeadEnd:
objRef = ContainerRef(Indirection(evalStr='.__dict__'), parentObjRef) # prevent cycles in the references (i.e. base.loader.base)
if not parentObjRef.goesThrough(child):
objRef = ObjectRef(Indirection(evalStr='.__dict__'),
id(child), parentObjRef)
yield None yield None
if hasLength: if hasLength:
for i in self._addContainerGen(child, objRef): for i in self._addContainerGen(child, objRef):
@ -492,10 +511,14 @@ class FindContainers(Job):
if curObjRef is None: if curObjRef is None:
notDeadEnd = not self._isDeadEnd(attr, key) notDeadEnd = not self._isDeadEnd(attr, key)
if hasLength or notDeadEnd: if hasLength or notDeadEnd:
# prevent cycles in the references (i.e. base.loader.base)
if not parentObjRef.goesThrough(curObj[key]):
if curObj is __builtin__.__dict__: if curObj is __builtin__.__dict__:
objRef = ContainerRef(Indirection(evalStr='%s' % key)) objRef = ObjectRef(Indirection(evalStr='%s' % key),
id(curObj[key]))
else: else:
objRef = ContainerRef(Indirection(dictKey=key), parentObjRef) objRef = ObjectRef(Indirection(dictKey=key),
id(curObj[key]), parentObjRef)
yield None yield None
if hasLength: if hasLength:
for i in self._addContainerGen(attr, objRef): for i in self._addContainerGen(attr, objRef):
@ -537,8 +560,10 @@ class FindContainers(Job):
if curObjRef is None: if curObjRef is None:
notDeadEnd = not self._isDeadEnd(attr) notDeadEnd = not self._isDeadEnd(attr)
if hasLength or notDeadEnd: if hasLength or notDeadEnd:
objRef = ContainerRef(Indirection(evalStr='[%s]' % index), # prevent cycles in the references (i.e. base.loader.base)
parentObjRef) if not parentObjRef.goesThrough(curObj[index]):
objRef = ObjectRef(Indirection(evalStr='[%s]' % index),
id(curObj[index]), parentObjRef)
yield None yield None
if hasLength: if hasLength:
for i in self._addContainerGen(attr, objRef): for i in self._addContainerGen(attr, objRef):
@ -698,7 +723,7 @@ class CheckContainers(Job):
raise raise
yield Job.Done yield Job.Done
class PruneContainerRefs(Job): class PruneObjectRefs(Job):
""" """
Job to destroy any container refs that are no longer valid. Job to destroy any container refs that are no longer valid.
Checks validity by asking for each container Checks validity by asking for each container
@ -748,7 +773,7 @@ class PruneContainerRefs(Job):
# reference is invalid, remove it # reference is invalid, remove it
del _id2discoveredStartRef[id] del _id2discoveredStartRef[id]
except Exception, e: except Exception, e:
print 'PruneContainerRefs job caught exception: %s' % e print 'PruneObjectRefs job caught exception: %s' % e
if __dev__: if __dev__:
raise raise
yield Job.Done yield Job.Done
@ -883,12 +908,12 @@ class ContainerLeakDetector(Job):
return task.done return task.done
def _scheduleNextPruning(self): def _scheduleNextPruning(self):
taskMgr.doMethodLater(self._pruneTaskPeriod, self._pruneContainerRefs, taskMgr.doMethodLater(self._pruneTaskPeriod, self._pruneObjectRefs,
self._getPruneTaskName()) self._getPruneTaskName())
def _pruneContainerRefs(self, task=None): def _pruneObjectRefs(self, task=None):
self._pruneContainersJob = PruneContainerRefs( self._pruneContainersJob = PruneObjectRefs(
'%s-pruneContainerRefs' % self.getJobName(), self) '%s-pruneObjectRefs' % self.getJobName(), self)
self.acceptOnce(self._pruneContainersJob.getFinishedEvent(), self.acceptOnce(self._pruneContainersJob.getFinishedEvent(),
self._scheduleNextPruning) self._scheduleNextPruning)
jobMgr.add(self._pruneContainersJob) jobMgr.add(self._pruneContainersJob)