Revision: 8341
          http://matplotlib.svn.sourceforge.net/matplotlib/?rev=8341&view=rev
Author:   mdboom
Date:     2010-05-28 17:42:05 +0000 (Fri, 28 May 2010)

Log Message:
-----------
Fix memory leak caused by never disconnecting callbacks.

Modified Paths:
--------------
    trunk/matplotlib/lib/matplotlib/cbook.py

Modified: trunk/matplotlib/lib/matplotlib/cbook.py
===================================================================
--- trunk/matplotlib/lib/matplotlib/cbook.py    2010-05-28 16:56:45 UTC (rev 
8340)
+++ trunk/matplotlib/lib/matplotlib/cbook.py    2010-05-28 17:42:05 UTC (rev 
8341)
@@ -13,6 +13,7 @@
 import os.path
 import random
 import urllib2
+import new
 
 import matplotlib
 
@@ -124,12 +125,87 @@
        callbacks.disconnect(ideat)        # disconnect oneat
        callbacks.process('eat', 456)      # nothing will be called
 
+    In practice, one should always disconnect all callbacks when they
+    are no longer needed to avoid dangling references (and thus memory
+    leaks).  However, real code in matplotlib rarely does so, and due
+    to its design, it is rather difficult to place this kind of code.
+    To get around this, and prevent this class of memory leaks, we
+    instead store weak references to bound methods only, so when the
+    destination object needs to die, the CallbackRegistry won't keep
+    it alive.  The Python stdlib weakref module can not create weak
+    references to bound methods directly, so we need to create a proxy
+    object to handle weak references to bound methods (or regular free
+    functions).  This technique was shared by Peter Parente on his
+    `"Mindtrove" blog
+    <http://mindtrove.info/articles/python-weak-references/>`_.
     """
+    class BoundMethodProxy(object):
+        '''
+        Our own proxy object which enables weak references to bound and unbound
+        methods and arbitrary callables. Pulls information about the function,
+        class, and instance out of a bound method. Stores a weak reference to 
the
+        instance to support garbage collection.
+
+        @organization: IBM Corporation
+        @copyright: Copyright (c) 2005, 2006 IBM Corporation
+        @license: The BSD License
+
+        Minor bugfixes by Michael Droettboom
+        '''
+        def __init__(self, cb):
+            try:
+                try:
+                    self.inst = ref(cb.im_self)
+                except TypeError:
+                    self.inst = None
+                self.func = cb.im_func
+                self.klass = cb.im_class
+            except AttributeError:
+                self.inst = None
+                self.func = cb
+                self.klass = None
+
+        def __call__(self, *args, **kwargs):
+            '''
+            Proxy for a call to the weak referenced object. Take
+            arbitrary params to pass to the callable.
+
+            Raises `ReferenceError`: When the weak reference refers to
+            a dead object
+            '''
+            if self.inst is not None and self.inst() is None:
+                raise ReferenceError
+            elif self.inst is not None:
+                # build a new instance method with a strong reference to the 
instance
+                mtd = new.instancemethod(self.func, self.inst(), self.klass)
+            else:
+                # not a bound method, just return the func
+                mtd = self.func
+            # invoke the callable and return the result
+            return mtd(*args, **kwargs)
+
+        def __eq__(self, other):
+            '''
+            Compare the held function and instance with that held by
+            another proxy.
+            '''
+            try:
+                if self.inst is None:
+                    return self.func == other.func and other.inst is None
+                else:
+                    return self.func == other.func and self.inst() == 
other.inst()
+            except Exception:
+                return False
+
+        def __ne__(self, other):
+            '''
+            Inverse of __eq__.
+            '''
+            return not self.__eq__(other)
+
     def __init__(self, signals):
         '*signals* is a sequence of valid signals'
         self.signals = set(signals)
-        # callbacks is a dict mapping the signal to a dictionary
-        # mapping callback id to the callback function
         self.callbacks = dict([(s, dict()) for s in signals])
         self._cid = 0
 
@@ -146,8 +222,15 @@
         func will be called
         """
         self._check_signal(s)
-        self._cid +=1
-        self.callbacks[s][self._cid] = func
+        proxy = self.BoundMethodProxy(func)
+        for cid, callback in self.callbacks[s].items():
+            # Clean out dead references
+            if callback.inst is not None and callback.inst() is None:
+                del self.callbacks[s][cid]
+            elif callback == proxy:
+                return cid
+        self._cid += 1
+        self.callbacks[s][self._cid] = proxy
         return self._cid
 
     def disconnect(self, cid):
@@ -155,9 +238,12 @@
         disconnect the callback registered with callback id *cid*
         """
         for eventname, callbackd in self.callbacks.items():
-            try: del callbackd[cid]
-            except KeyError: continue
-            else: return
+            try:
+                del callbackd[cid]
+            except KeyError:
+                continue
+            else:
+                return
 
     def process(self, s, *args, **kwargs):
         """
@@ -165,8 +251,12 @@
         callbacks on *s* will be called with *\*args* and *\*\*kwargs*
         """
         self._check_signal(s)
-        for func in self.callbacks[s].values():
-            func(*args, **kwargs)
+        for cid, proxy in self.callbacks[s].items():
+            # Clean out dead references
+            if proxy.inst is not None and proxy.inst() is None:
+                del self.callbacks[s][cid]
+            else:
+                proxy(*args, **kwargs)
 
 
 class Scheduler(threading.Thread):


This was sent by the SourceForge.net collaborative development platform, the 
world's largest Open Source development site.

------------------------------------------------------------------------------

_______________________________________________
Matplotlib-checkins mailing list
Matplotlib-checkins@lists.sourceforge.net
https://lists.sourceforge.net/lists/listinfo/matplotlib-checkins

Reply via email to