Please note:The SCons wiki is in read-only mode due to ongoing spam/DoS issues. Also, new account creation is currently disabled. We are looking into alternative wiki hosts.

Attachment 'leak_weakmethod.patch'

Download

   1 Index: src/engine/SCons/Subst.py
   2 ===================================================================
   3 --- src/engine/SCons/Subst.py	(revision 3186)
   4 +++ src/engine/SCons/Subst.py	(working copy)
   5 @@ -193,7 +193,7 @@
   6          # The map(self.func) call is what actually turns
   7          # a list into appropriate proxies.
   8          self.nodelist = SCons.Util.NodeList(map(self.func, list))
   9 -        self._create_nodelist = self._return_nodelist
  10 +        self._create_nodelist = SCons.Util.WeakMethod(self._return_nodelist)
  11          return self.nodelist
  12      _create_nodelist = _gen_nodelist
  13      
  14 @@ -592,9 +592,9 @@
  15              self.gvars = gvars
  16  
  17              if self.mode == SUBST_RAW:
  18 -                self.add_strip = lambda x, s=self: s.append(x)
  19 +                self.add_strip = lambda x, s=SCons.Util.WeakProxy(self): s.append(x)
  20              else:
  21 -                self.add_strip = lambda x, s=self: None
  22 +                self.add_strip = lambda x, s=SCons.Util.WeakProxy(self): None
  23              self.in_strip = None
  24              self.next_line()
  25  
  26 @@ -714,11 +714,11 @@
  27          def this_word(self):
  28              """Arrange for the next word to append to the end of the
  29              current last word in the result."""
  30 -            self.append = self.add_to_current_word
  31 +            self.append = SCons.Util.WeakMethod(self.add_to_current_word)
  32  
  33          def next_word(self):
  34              """Arrange for the next word to start a new word."""
  35 -            self.append = self.add_new_word
  36 +            self.append = SCons.Util.WeakMethod(self.add_new_word)
  37  
  38          def add_to_current_word(self, x):
  39              """Append the string x to the end of the current last word
  40 @@ -775,7 +775,7 @@
  41                  if is_String(x):
  42                      x = CmdStringHolder(x, literal)
  43                  self[-1].append(x)
  44 -            self.append = self.add_to_current_word
  45 +            self.append = SCons.Util.WeakMethod(self.add_to_current_word)
  46  
  47          def literal(self, x):
  48              try:
  49 Index: src/engine/SCons/Scanner/__init__.py
  50 ===================================================================
  51 --- src/engine/SCons/Scanner/__init__.py	(revision 3186)
  52 +++ src/engine/SCons/Scanner/__init__.py	(working copy)
  53 @@ -180,9 +180,9 @@
  54          if callable(recursive):
  55              self.recurse_nodes = recursive
  56          elif recursive:
  57 -            self.recurse_nodes = self._recurse_all_nodes
  58 +            self.recurse_nodes = SCons.Util.WeakMethod(self._recurse_all_nodes)
  59          else:
  60 -            self.recurse_nodes = self._recurse_no_nodes
  61 +            self.recurse_nodes = SCons.Util.WeakMethod(self._recurse_no_nodes)
  62  
  63      def path(self, env, dir=None, target=None, source=None):
  64          if not self.path_function:
  65 @@ -325,7 +325,7 @@
  66  
  67          self.cre = re.compile(regex, re.M)
  68  
  69 -        def _scan(node, env, path=(), self=self):
  70 +        def _scan(node, env, path=(), self=SCons.Util.WeakProxy(self)):
  71              node = node.rfile()
  72              if not node.exists():
  73                  return []
  74 Index: src/engine/SCons/Scanner/Fortran.py
  75 ===================================================================
  76 --- src/engine/SCons/Scanner/Fortran.py	(revision 3186)
  77 +++ src/engine/SCons/Scanner/Fortran.py	(working copy)
  78 @@ -61,7 +61,7 @@
  79          self.cre_incl = re.compile(incl_regex, re.M)
  80          self.cre_def = re.compile(def_regex, re.M)
  81  
  82 -        def _scan(node, env, path, self=self):
  83 +        def _scan(node, env, path, self=SCons.Util.WeakProxy(self)):
  84              node = node.rfile()
  85  
  86              if not node.exists():
  87 Index: src/engine/SCons/Scanner/ScannerTests.py
  88 ===================================================================
  89 --- src/engine/SCons/Scanner/ScannerTests.py	(revision 3186)
  90 +++ src/engine/SCons/Scanner/ScannerTests.py	(working copy)
  91 @@ -543,6 +543,18 @@
  92          ret = s.function(n, env, ('foo5',))
  93          assert ret == ['jkl', 'mno'], ret
  94  
  95 +        # Verify that the scanner does not create reference cycles.
  96 +        try:
  97 +            import gc
  98 +            import weakref
  99 +        except ImportError:
 100 +            pass
 101 +        else:
 102 +            id_s = id(s)
 103 +            del s
 104 +            gc.set_debug(gc.DEBUG_SAVEALL)
 105 +            gc.collect()
 106 +            assert id_s not in map(id, gc.garbage)
 107          
 108  
 109  class ClassicCPPTestCase(unittest.TestCase):
 110 Index: src/engine/SCons/Taskmaster.py
 111 ===================================================================
 112 --- src/engine/SCons/Taskmaster.py	(revision 3186)
 113 +++ src/engine/SCons/Taskmaster.py	(working copy)
 114 @@ -417,7 +417,7 @@
 115          to the appropriate do-nothing method.
 116          """
 117          self.exception = (None, None, None)
 118 -        self.exception_raise = self._no_exception_to_raise
 119 +        self.exception_raise = SCons.Util.WeakMethod(self._no_exception_to_raise)
 120  
 121      def exception_set(self, exception=None):
 122          """
 123 @@ -429,7 +429,7 @@
 124          if not exception:
 125              exception = sys.exc_info()
 126          self.exception = exception
 127 -        self.exception_raise = self._exception_raise
 128 +        self.exception_raise = SCons.Util.WeakMethod(self._exception_raise)
 129  
 130      def _no_exception_to_raise(self):
 131          pass
 132 @@ -478,7 +478,7 @@
 133          self.order = order
 134          self.message = None
 135          self.trace = trace
 136 -        self.next_candidate = self.find_next_candidate
 137 +        self.next_candidate = SCons.Util.WeakMethod(self.find_next_candidate)
 138          self.pending_children = set()
 139  
 140  
 141 @@ -780,7 +780,7 @@
 142          """
 143          Stops the current build completely.
 144          """
 145 -        self.next_candidate = self.no_next_candidate
 146 +        self.next_candidate = SCons.Util.WeakMethod(self.no_next_candidate)
 147  
 148      def cleanup(self):
 149          """
 150 Index: src/engine/SCons/Environment.py
 151 ===================================================================
 152 --- src/engine/SCons/Environment.py	(revision 3186)
 153 +++ src/engine/SCons/Environment.py	(working copy)
 154 @@ -260,7 +260,7 @@
 155          # Set self.env before calling the superclass initialization,
 156          # because it will end up calling our other methods, which will
 157          # need to point the values in this dictionary to self.env.
 158 -        self.env = env
 159 +        self.env = SCons.Util.SafeWeakProxy(env)
 160          UserDict.__init__(self, dict)
 161  
 162      def __semi_deepcopy__(self):
 163 @@ -890,7 +890,7 @@
 164  
 165          self.copy_from_cache = default_copy_from_cache
 166  
 167 -        self._dict['BUILDERS'] = BuilderDict(self._dict['BUILDERS'], self)
 168 +        self._dict['BUILDERS'] = BuilderDict(self._dict['BUILDERS'], SCons.Util.WeakProxy(self))
 169  
 170          if platform is None:
 171              platform = self._dict.get('PLATFORM', None)
 172 @@ -1202,7 +1202,7 @@
 173          except KeyError:
 174              pass
 175          else:
 176 -            clone._dict['BUILDERS'] = BuilderDict(cbd, clone)
 177 +            clone._dict['BUILDERS'] = BuilderDict(cbd, SCons.Util.WeakProxy(clone))
 178  
 179          # Check the methods added via AddMethod() and re-bind them to
 180          # the cloned environment.  Only do this if the attribute hasn't
 181 Index: src/engine/SCons/SConf.py
 182 ===================================================================
 183 --- src/engine/SCons/SConf.py	(revision 3186)
 184 +++ src/engine/SCons/SConf.py	(working copy)
 185 @@ -623,7 +623,7 @@
 186          """A wrapper around Tests (to ensure sanity)"""
 187          def __init__(self, test, sconf):
 188              self.test = test
 189 -            self.sconf = sconf
 190 +            self.sconf = SCons.Util.WeakProxy(sconf)
 191          def __call__(self, *args, **kw):
 192              if not self.sconf.active:
 193                  raise (SCons.Errors.UserError,
 194 Index: src/engine/SCons/Util.py
 195 ===================================================================
 196 --- src/engine/SCons/Util.py	(revision 3186)
 197 +++ src/engine/SCons/Util.py	(working copy)
 198 @@ -1497,6 +1497,84 @@
 199      def __delattr__(self, name):
 200          return self
 201  
 202 +# WeakMethod to avoid reference cycles when bound methods are involved. The
 203 +# reference to the object is held as a weak reference.
 204 +# http://aspn.activestate.com/ASPN/Cookbook/Python/Recipe/81253
 205  
 206 +try:
 207 +    import weakref
 208  
 209 +except ImportError:
 210 +    def WeakMethod(f):
 211 +        return f
 212 +
 213 +    def WeakProxy(o):
 214 +        return o
 215 +
 216 +    def SafeWeakProxy(o):
 217 +        return o
 218 +
 219 +else:
 220 +    try:
 221 +        class WeakMethod(object):
 222 +            """
 223 +            Object encapsulating a bound method with a weak reference to the
 224 +            instance.
 225 +            """
 226 +            __slots__ = ('f', 'c')
 227 +            def __init__( self , f ) :
 228 +                """
 229 +                Keep function and weak reference of instance object. If the
 230 +                passed function is not a bound method, an AttributeError will be
 231 +                raised.
 232 +                """
 233 +                self.f = f.im_func
 234 +                self.c = weakref.ref( f.im_self )
 235 +            def __call__( self , *arg ) :
 236 +                """
 237 +                Call the function and pass the reference to the instance. If the
 238 +                instance is freed, an exception (which might be raised by the
 239 +                called function) is raised which is not handled for performance
 240 +                reasons.
 241 +                'self.f(self.c(), *arg)' would be faster but is not supported in
 242 +                Python 1.5.
 243 +                """
 244 +                try:
 245 +                    return apply( self.f , ( self.c() , ) + arg )
 246 +                except:
 247 +                    if self.c() is None:
 248 +                        raise ReferenceError, \
 249 +                            "weakly referenced method instance no longer exists"
 250 +                    else:
 251 +                        raise
 252 +
 253 +    except NameError:
 254 +        # Python 2.1 compliance (no new-style classes)
 255 +        class WeakMethod:
 256 +            def __init__( self , f ) :
 257 +                self.f = f.im_func
 258 +                self.c = weakref.ref( f.im_self )
 259 +            def __call__( self , *arg ) :
 260 +                try:
 261 +                    return apply( self.f , ( self.c() , ) + arg )
 262 +                except:
 263 +                    if self.c() is None:
 264 +                        raise ReferenceError, \
 265 +                            "weakly referenced method instance no longer exists"
 266 +                    else:
 267 +                        raise
 268 +
 269 +    WeakProxy = weakref.proxy 
 270 +
 271 +    def SafeWeakProxy(o):
 272 +        """
 273 +        Return a weak proxy of the passed object. If the object is already a
 274 +        weak proxy return it as-is.
 275 +        """
 276 +        try:
 277 +            return weakref.proxy(o)
 278 +        except TypeError:
 279 +            return o
 280 +
 281 +
 282  del __revision__
 283 Index: src/engine/SCons/Tool/__init__.py
 284 ===================================================================
 285 --- src/engine/SCons/Tool/__init__.py	(revision 3186)
 286 +++ src/engine/SCons/Tool/__init__.py	(working copy)
 287 @@ -486,13 +486,13 @@
 288              tools = [tools]
 289          if not SCons.Util.is_List(names):
 290              names = [names]
 291 -        self.env = env
 292 +        self.env = SCons.Util.WeakProxy(env)
 293          self.tools = tools
 294          self.names = names
 295          self.methods = {}
 296          for name in names:
 297              method = ToolInitializerMethod(name, self)
 298 -            self.methods[name] = method
 299 +            self.methods[name] = SCons.Util.WeakProxy(method)
 300              env.AddMethod(method)
 301  
 302      def remove_methods(self, env):
 303 Index: src/engine/SCons/Tool/CVS.py
 304 ===================================================================
 305 --- src/engine/SCons/Tool/CVS.py	(revision 3186)
 306 +++ src/engine/SCons/Tool/CVS.py	(working copy)
 307 @@ -41,7 +41,7 @@
 308      """Add a Builder factory function and construction variables for
 309      CVS to an Environment."""
 310  
 311 -    def CVSFactory(repos, module='', env=env):
 312 +    def CVSFactory(repos, module='', env=SCons.Util.WeakProxy(env)):
 313          """ """
 314          # fail if repos is not an absolute path name?
 315          if module != '':

Attached Files

To refer to attachments on a page, use attachment:filename, as shown below in the list of files. Do NOT use the URL of the [get] link, since this is subject to change and can break easily.

You are not allowed to attach a file to this page.