mirror of
https://github.com/panda3d/panda3d.git
synced 2025-10-03 18:31:55 -04:00
prevent cycles in the ObjectRefs (i.e. base.loader.base.loader)
This commit is contained in:
parent
f619fd68bd
commit
f82ac8cfde
@ -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,14 +475,17 @@ 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)
|
||||||
yield None
|
if not parentObjRef.goesThrough(child):
|
||||||
if hasLength:
|
objRef = ObjectRef(Indirection(evalStr='.__dict__'),
|
||||||
for i in self._addContainerGen(child, objRef):
|
id(child), parentObjRef)
|
||||||
yield None
|
yield None
|
||||||
if notDeadEnd:
|
if hasLength:
|
||||||
self._addDiscoveredStartRef(child, objRef)
|
for i in self._addContainerGen(child, objRef):
|
||||||
curObjRef = objRef
|
yield None
|
||||||
|
if notDeadEnd:
|
||||||
|
self._addDiscoveredStartRef(child, objRef)
|
||||||
|
curObjRef = objRef
|
||||||
continue
|
continue
|
||||||
|
|
||||||
if type(curObj) is types.DictType:
|
if type(curObj) is types.DictType:
|
||||||
@ -492,18 +511,22 @@ 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:
|
||||||
if curObj is __builtin__.__dict__:
|
# prevent cycles in the references (i.e. base.loader.base)
|
||||||
objRef = ContainerRef(Indirection(evalStr='%s' % key))
|
if not parentObjRef.goesThrough(curObj[key]):
|
||||||
else:
|
if curObj is __builtin__.__dict__:
|
||||||
objRef = ContainerRef(Indirection(dictKey=key), parentObjRef)
|
objRef = ObjectRef(Indirection(evalStr='%s' % key),
|
||||||
yield None
|
id(curObj[key]))
|
||||||
if hasLength:
|
else:
|
||||||
for i in self._addContainerGen(attr, objRef):
|
objRef = ObjectRef(Indirection(dictKey=key),
|
||||||
yield None
|
id(curObj[key]), parentObjRef)
|
||||||
if notDeadEnd:
|
yield None
|
||||||
self._addDiscoveredStartRef(attr, objRef)
|
if hasLength:
|
||||||
if curObjRef is None and random.randrange(numKeysLeft) == 0:
|
for i in self._addContainerGen(attr, objRef):
|
||||||
curObjRef = objRef
|
yield None
|
||||||
|
if notDeadEnd:
|
||||||
|
self._addDiscoveredStartRef(attr, objRef)
|
||||||
|
if curObjRef is None and random.randrange(numKeysLeft) == 0:
|
||||||
|
curObjRef = objRef
|
||||||
del key
|
del key
|
||||||
del attr
|
del attr
|
||||||
continue
|
continue
|
||||||
@ -537,16 +560,18 @@ 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]):
|
||||||
yield None
|
objRef = ObjectRef(Indirection(evalStr='[%s]' % index),
|
||||||
if hasLength:
|
id(curObj[index]), parentObjRef)
|
||||||
for i in self._addContainerGen(attr, objRef):
|
yield None
|
||||||
yield None
|
if hasLength:
|
||||||
if notDeadEnd:
|
for i in self._addContainerGen(attr, objRef):
|
||||||
self._addDiscoveredStartRef(attr, objRef)
|
yield None
|
||||||
if curObjRef is None and random.randrange(numAttrsLeft) == 0:
|
if notDeadEnd:
|
||||||
curObjRef = objRef
|
self._addDiscoveredStartRef(attr, objRef)
|
||||||
|
if curObjRef is None and random.randrange(numAttrsLeft) == 0:
|
||||||
|
curObjRef = objRef
|
||||||
del attr
|
del attr
|
||||||
except StopIteration, e:
|
except StopIteration, e:
|
||||||
pass
|
pass
|
||||||
@ -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)
|
||||||
|
Loading…
x
Reference in New Issue
Block a user