Browse Source

prevent garbage leak

Darren Ranalli 19 years ago
parent
commit
a86451ad35
1 changed files with 89 additions and 40 deletions
  1. 89 40
      direct/src/showbase/EnforcesCalldowns.py

+ 89 - 40
direct/src/showbase/EnforcesCalldowns.py

@@ -62,41 +62,47 @@ class EnforcesCalldowns:
         if EnforcesCalldowns.notActive():
             return
 
-        # this map tracks how many times each func has been called
-        self._funcId2calls = {}
-        # this map tracks the 'latch' values for each func; if the call count
-        # for a func is greater than the latch, then the func has been called.
-        self._funcId2latch = {}
-
-        if self.__class__ not in EnforcesCalldowns._class2funcName2funcIds:
-            # prepare stubs to enforce method call-downs
-            EnforcesCalldowns._class2funcName2funcIds.setdefault(self.__class__, {})
-            # look through all of our base classes and find matches
-            classes = ClassTree(self).getAllClasses()
-            # collect IDs of all the enforced methods
-            funcId2func = {}
-            for cls in classes:
-                for name, item in cls.__dict__.items():
-                    if id(item) in EnforcesCalldowns._decoId2funcId:
-                        funcId = EnforcesCalldowns._decoId2funcId[id(item)]
-                        funcId2func[funcId] = item
-                        EnforcesCalldowns._funcId2class[funcId] = cls
-            # add these funcs to the list for our class
+        # protect against multiple calldowns via multiple inheritance
+        if hasattr(self, '_numInits'):
+            self._numInits += 1
+        else:
+            self._numInits = 1
+
+            # this map tracks how many times each func has been called
+            self._funcId2calls = {}
+            # this map tracks the 'latch' values for each func; if the call count
+            # for a func is greater than the latch, then the func has been called.
+            self._funcId2latch = {}
+
+            if self.__class__ not in EnforcesCalldowns._class2funcName2funcIds:
+                # prepare stubs to enforce method call-downs
+                EnforcesCalldowns._class2funcName2funcIds.setdefault(self.__class__, {})
+                # look through all of our base classes and find matches
+                classes = ClassTree(self).getAllClasses()
+                # collect IDs of all the enforced methods
+                funcId2func = {}
+                for cls in classes:
+                    for name, item in cls.__dict__.items():
+                        if id(item) in EnforcesCalldowns._decoId2funcId:
+                            funcId = EnforcesCalldowns._decoId2funcId[id(item)]
+                            funcId2func[funcId] = item
+                            EnforcesCalldowns._funcId2class[funcId] = cls
+                # add these funcs to the list for our class
+                funcName2funcIds = EnforcesCalldowns._class2funcName2funcIds[self.__class__]
+                for funcId, func in funcId2func.items():
+                    funcName2funcIds.setdefault(func.__name__, [])
+                    funcName2funcIds[func.__name__].append(funcId)
+
+            # now run through all the enforced funcs for this class and insert
+            # stub methods to do the enforcement
             funcName2funcIds = EnforcesCalldowns._class2funcName2funcIds[self.__class__]
-            for funcId, func in funcId2func.items():
-                funcName2funcIds.setdefault(func.__name__, [])
-                funcName2funcIds[func.__name__].append(funcId)
-
-        # now run through all the enforced funcs for this class and insert
-        # stub methods to do the enforcement
-        funcName2funcIds = EnforcesCalldowns._class2funcName2funcIds[self.__class__]
-        self._obscuredMethodNames = set()
-        for name in funcName2funcIds:
-            oldMethod = getattr(self, name)
-            self._obscuredMethodNames.add(name)
-            setattr(self, name, new.instancemethod(
-                Functor(EnforcesCalldowns._enforceCalldowns, oldMethod, name),
-                self, self.__class__))
+            self._obscuredMethodNames = set()
+            for name in funcName2funcIds:
+                oldMethod = getattr(self, name)
+                self._obscuredMethodNames.add(name)
+                setattr(self, name, new.instancemethod(
+                    Functor(EnforcesCalldowns._enforceCalldowns, oldMethod, name),
+                    self, self.__class__))
             
     def EC_destroy(self):
         """this used to be called destroy() but it was masking destroy() functions
@@ -105,12 +111,21 @@ class EnforcesCalldowns:
         if EnforcesCalldowns.notActive():
             return
         # this must be called on destruction to prevent memory leaks
-        for name in self._obscuredMethodNames:
-            delattr(self, name)
-        del self._obscuredMethodNames
-        # this opens up more cans of worms. Let's keep it closed for the moment
-        #del self._funcId2calls
-        #del self._funcId2latch
+        # protect against multiple calldowns via multiple inheritance
+        assert hasattr(self, '_numInits'), (
+            'too many calls to EnforcesCalldowns.EC_destroy, class=%s' % self.__class__.__name__)
+        if self._numInits == 1:
+            for name in self._obscuredMethodNames:
+                # Functors need to be destroyed to prevent garbage leaks
+                getattr(self, name).destroy()
+                delattr(self, name)
+            del self._obscuredMethodNames
+            # this opens up more cans of worms. Let's keep it closed for the moment
+            #del self._funcId2calls
+            #del self._funcId2latch
+            del self._numInits
+        else:
+            self._numInits -= 1
 
     def skipCalldown(self, method):
         if EnforcesCalldowns.notActive():
@@ -164,15 +179,35 @@ if not EnforcesCalldowns.notActive():
     class CalldownEnforceTestSubclass(CalldownEnforceTest):
         def testFunc(self):
             CalldownEnforceTest.testFunc(self)
+        def destroy(self):
+            CalldownEnforceTest.EC_destroy(self)
     class CalldownEnforceTestSubclassFail(CalldownEnforceTest):
         def testFunc(self):
             pass
     class CalldownEnforceTestSubclassSkip(CalldownEnforceTest):
         def testFunc(self):
             self.skipCalldown(CalldownEnforceTest.testFunc)
+    class CalldownEnforceTestSubclass2(CalldownEnforceTest):
+        def testFunc(self):
+            CalldownEnforceTest.testFunc(self)
+        def destroy(self):
+            CalldownEnforceTest.EC_destroy(self)
+    class CalldownEnforceTestDiamond(CalldownEnforceTestSubclass,
+                                     CalldownEnforceTestSubclass2):
+        def __init__(self):
+            CalldownEnforceTestSubclass.__init__(self)
+            CalldownEnforceTestSubclass2.__init__(self)
+        def testFunc(self):
+            CalldownEnforceTestSubclass.testFunc(self)
+            CalldownEnforceTestSubclass2.testFunc(self)
+        def destroy(self):
+            CalldownEnforceTestSubclass.destroy(self)
+            CalldownEnforceTestSubclass2.destroy(self)
+            
     cets = CalldownEnforceTestSubclass()
     cetsf = CalldownEnforceTestSubclassFail()
     cetss = CalldownEnforceTestSubclassSkip()
+    cetd = CalldownEnforceTestDiamond()
     raised = False
     try:
         cets.testFunc()
@@ -194,12 +229,26 @@ if not EnforcesCalldowns.notActive():
         raised = True
     if raised:
         raise "calldownEnforced.skipCalldown raised when it shouldn't"
+    raised = False
+    cetd.testFunc()
+    msg = ''
+    try:
+        # make sure we're OK to call down to destroy multiple times
+        cetd.destroy()
+    except Exception, e:
+        msg = str(e)
+        raised = True
+    if raised:
+        raise "calldownEnforcedDiamond.destroy raised when it shouldn't\n%s" % msg
     cetss.EC_destroy()
     cetsf.EC_destroy()
     cets.EC_destroy()
+    del cetd
     del cetss
     del cetsf
     del cets
+    del CalldownEnforceTestDiamond
+    del CalldownEnforceTestSubclass2
     del CalldownEnforceTestSubclassSkip
     del CalldownEnforceTestSubclassFail
     del CalldownEnforceTestSubclass