Rev 6101: (jameinel) Handle bug #702914, in file:///home/pqm/archives/thelove/bzr/%2Btrunk/

Canonical.com Patch Queue Manager pqm at pqm.ubuntu.com
Thu Aug 25 14:04:32 UTC 2011


At file:///home/pqm/archives/thelove/bzr/%2Btrunk/

------------------------------------------------------------
revno: 6101 [merge]
revision-id: pqm at pqm.ubuntu.com-20110825140403-9xf3jcvxzxrazjuv
parent: pqm at pqm.ubuntu.com-20110825103314-h70ju19dco9937aa
parent: benji at benjiyork.com-20110819181931-ltgm1rp1n27uh9e2
committer: Canonical.com Patch Queue Manager <pqm at pqm.ubuntu.com>
branch nick: +trunk
timestamp: Thu 2011-08-25 14:04:03 +0000
message:
  (jameinel) Handle bug #702914,
   avoid race conditions removing attributes of ImportReplacer. (Benji York)
modified:
  bzrlib/lazy_import.py          lazy_import.py-20060910203832-f77c54gf3n232za0-1
  bzrlib/tests/test_lazy_import.py test_lazy_import.py-20060910203832-f77c54gf3n232za0-2
=== modified file 'bzrlib/lazy_import.py'
--- a/bzrlib/lazy_import.py	2011-05-18 16:42:48 +0000
+++ b/bzrlib/lazy_import.py	2011-08-19 18:02:37 +0000
@@ -98,8 +98,19 @@
 
     def _cleanup(self):
         """Stop holding on to all the extra stuff"""
-        del self._factory
-        del self._scope
+        try:
+            del self._factory
+        except AttributeError:
+            # Oops, we just lost a race with another caller of _cleanup.  Just
+            # ignore it.
+            pass
+
+        try:
+            del self._scope
+        except AttributeError:
+            # Another race loss.  See above.
+            pass
+
         # We keep _name, so that we can report errors
         # del self._name
 

=== modified file 'bzrlib/tests/test_lazy_import.py'
--- a/bzrlib/tests/test_lazy_import.py	2011-05-13 12:51:05 +0000
+++ b/bzrlib/tests/test_lazy_import.py	2011-08-19 18:19:31 +0000
@@ -16,7 +16,9 @@
 
 """Test the lazy_import functionality."""
 
+import linecache
 import os
+import re
 import sys
 
 from bzrlib import (
@@ -1167,3 +1169,79 @@
                           ('_import', 'root8'),
                           ('import', self.root_name, []),
                          ], self.actions)
+
+
+class TestScopeReplacerReentrance(TestCase):
+    """The ScopeReplacer should be reentrant.
+
+    Invoking a replacer while an invocation was already on-going leads to a
+    race to see which invocation will be the first to delete the _factory and
+    _scope attributes.  The loosing caller used to see AttributeErrors (bug
+    702914).
+
+    These tests set up a tracer that stops at the moment just before one of
+    the attributes is being deleted and starts another call to the
+    functionality in question (__call__, __getattribute__, __setattr_) in
+    order win the race, setting up the originall caller to loose.
+    """
+
+    def tracer(self, frame, event, arg):
+        # Grab the name of the file that contains the code being executed.
+        filename = frame.f_globals["__file__"]
+        # Convert ".pyc" and ".pyo" file names to their ".py" equivalent.
+        filename = re.sub(r'\.py[co]$', '.py', filename)
+        # If we're executing a line of code from the right module...
+        if event == 'line' and 'lazy_import.py' in filename:
+            line = linecache.getline(filename, frame.f_lineno)
+            # ...and the line of code is the one we're looking for...
+            if 'del self._factory' in line:
+                # We don't need to trace any more.
+                sys.settrace(None)
+                # Run another racer.  This one will "win" the race, deleting
+                # the attributes.  When the first racer resumes it will loose
+                # the race, generating an AttributeError.
+                self.racer()
+        return self.tracer
+
+    def run_race(self, racer):
+        self.racer = racer
+        sys.settrace(self.tracer)
+        self.racer() # Should not raise an AttributeError
+        # Make sure the tracer actually found the code it was looking for.  If
+        # not, maybe the code was refactored in such a way that these tests
+        # aren't needed any more.
+        self.assertEqual(None, sys.gettrace())
+
+    def test_call(self):
+        def factory(*args):
+            return factory
+        replacer = lazy_import.ScopeReplacer({}, factory, 'name')
+        self.run_race(replacer)
+
+    def test_setattr(self):
+        class Replaced:
+            pass
+
+        def factory(*args):
+            return Replaced()
+
+        replacer = lazy_import.ScopeReplacer({}, factory, 'name')
+
+        def racer():
+            replacer.foo = 42
+
+        self.run_race(racer)
+
+    def test_getattribute(self):
+        class Replaced:
+            foo = 'bar'
+
+        def factory(*args):
+            return Replaced()
+
+        replacer = lazy_import.ScopeReplacer({}, factory, 'name')
+
+        def racer():
+            replacer.foo
+
+        self.run_race(racer)




More information about the bazaar-commits mailing list