Rev 5661: (vila) Use cethread to avoid ambiguity with python module. (Vincent Ladeuil) in file:///home/pqm/archives/thelove/bzr/%2Btrunk/

Canonical.com Patch Queue Manager pqm at pqm.ubuntu.com
Fri Feb 11 17:12:39 UTC 2011


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

------------------------------------------------------------
revno: 5661 [merge]
revision-id: pqm at pqm.ubuntu.com-20110211171235-ky1uu0l0gz6501g7
parent: pqm at pqm.ubuntu.com-20110211145931-vkmwtlao6q032l1c
parent: v.ladeuil+lp at free.fr-20110211161539-26o5a28ihyemvuzg
committer: Canonical.com Patch Queue Manager <pqm at pqm.ubuntu.com>
branch nick: +trunk
timestamp: Fri 2011-02-11 17:12:35 +0000
message:
  (vila) Use cethread to avoid ambiguity with python module. (Vincent Ladeuil)
renamed:
  bzrlib/tests/test_thread.py => bzrlib/tests/test_cethread.py test_thread.py-20110208152811-9ivenqn0omw1ev4d-1
  bzrlib/thread.py => bzrlib/cethread.py thread.py-20110208152810-i29un3um8v6marcr-1
modified:
  bzrlib/tests/__init__.py       selftest.py-20050531073622-8d0e3c8845c97a64
  bzrlib/tests/test_http.py      testhttp.py-20051018020158-b2eef6e867c514d9
  bzrlib/tests/test_server.py    test_server.py-20100209163834-im1ozfuenfmqaa2m-1
  doc/en/release-notes/bzr-2.4.txt bzr2.4.txt-20110114053217-k7ym9jfz243fddjm-1
  bzrlib/tests/test_cethread.py  test_thread.py-20110208152811-9ivenqn0omw1ev4d-1
  bzrlib/cethread.py             thread.py-20110208152810-i29un3um8v6marcr-1
=== renamed file 'bzrlib/thread.py' => 'bzrlib/cethread.py'
--- a/bzrlib/thread.py	2011-02-08 16:26:23 +0000
+++ b/bzrlib/cethread.py	2011-02-10 12:37:27 +0000
@@ -39,6 +39,7 @@
         self.set_sync_event(sync_event)
         self.exception = None
         self.ignored_exceptions = None # see set_ignored_exceptions
+        self.lock = threading.Lock()
 
     # compatibility thunk for python-2.4 and python-2.5...
     if sys.version_info < (2, 6):
@@ -55,11 +56,49 @@
         Some threads require multiple events and should set the relevant one
         when appropriate.
 
-        Note that the event should be cleared so the caller can wait() on him
-        and be released when the thread set the event.
+        Note that the event should be initially cleared so the caller can
+        wait() on him and be released when the thread set the event.
+
+        Also note that the thread can use multiple events, setting them as it
+        progress, while the caller can chose to wait on any of them. What
+        matters is that there is always one event set so that the caller is
+        always released when an exception is caught. Re-using the same event is
+        therefore risky as the thread itself has no idea about which event the
+        caller is waiting on. If the caller has already been released then a
+        cleared event won't guarantee that the caller is still waiting on it.
         """
         self.sync_event = event
 
+    def switch_and_set(self, new):
+        """Switch to a new ``sync_event`` and set the current one.
+
+        Using this method protects against race conditions while setting a new
+        ``sync_event``.
+
+        Note that this allows a caller to wait either on the old or the new
+        event depending on whether it wants a fine control on what is happening
+        inside a thread.
+
+        :param new: The event that will become ``sync_event``
+        """
+        cur = self.sync_event
+        self.lock.acquire()
+        try: # Always release the lock
+            try:
+                self.set_sync_event(new)
+                # From now on, any exception will be synced with the new event
+            except:
+                # Unlucky, we couldn't set the new sync event, try restoring a
+                # safe state
+                self.set_sync_event(cur)
+                raise
+            # Setting the current ``sync_event`` will release callers waiting
+            # on it, note that it will also be set in run() if an exception is
+            # raised
+            cur.set()
+        finally:
+            self.lock.release()
+
     def set_ignored_exceptions(self, ignored):
         """Declare which exceptions will be ignored.
 

=== modified file 'bzrlib/tests/__init__.py'
--- a/bzrlib/tests/__init__.py	2011-02-10 16:30:54 +0000
+++ b/bzrlib/tests/__init__.py	2011-02-11 17:12:35 +0000
@@ -3858,7 +3858,7 @@
         'bzrlib.tests.test_testament',
         'bzrlib.tests.test_textfile',
         'bzrlib.tests.test_textmerge',
-        'bzrlib.tests.test_thread',
+        'bzrlib.tests.test_cethread',
         'bzrlib.tests.test_timestamp',
         'bzrlib.tests.test_trace',
         'bzrlib.tests.test_transactions',

=== renamed file 'bzrlib/tests/test_thread.py' => 'bzrlib/tests/test_cethread.py'
--- a/bzrlib/tests/test_thread.py	2011-02-08 16:26:23 +0000
+++ b/bzrlib/tests/test_cethread.py	2011-02-11 16:15:39 +0000
@@ -17,8 +17,8 @@
 import threading
 
 from bzrlib import (
+    cethread,
     tests,
-    thread,
     )
 
 
@@ -28,7 +28,7 @@
         def do_nothing():
             pass
 
-        tt = thread.CatchingExceptionThread(target=do_nothing)
+        tt = cethread.CatchingExceptionThread(target=do_nothing)
         tt.start()
         tt.join()
 
@@ -39,11 +39,11 @@
         def raise_my_exception():
             raise MyException()
 
-        tt = thread.CatchingExceptionThread(target=raise_my_exception)
+        tt = cethread.CatchingExceptionThread(target=raise_my_exception)
         tt.start()
         self.assertRaises(MyException, tt.join)
 
-    def test_join_when_no_exception(self):
+    def test_join_around_exception(self):
         resume = threading.Event()
         class MyException(Exception):
             pass
@@ -54,11 +54,108 @@
             # Now we can raise
             raise MyException()
 
-        tt = thread.CatchingExceptionThread(target=raise_my_exception)
+        tt = cethread.CatchingExceptionThread(target=raise_my_exception)
         tt.start()
         tt.join(timeout=0)
         self.assertIs(None, tt.exception)
         resume.set()
         self.assertRaises(MyException, tt.join)
 
-
+    def test_sync_event(self):
+        control = threading.Event()
+        in_thread = threading.Event()
+        class MyException(Exception):
+            pass
+
+        def raise_my_exception():
+            # Wait for the test to tell us to resume
+            control.wait()
+            # Now we can raise
+            raise MyException()
+
+        tt = cethread.CatchingExceptionThread(target=raise_my_exception,
+                                            sync_event=in_thread)
+        tt.start()
+        tt.join(timeout=0)
+        self.assertIs(None, tt.exception)
+        self.assertIs(in_thread, tt.sync_event)
+        control.set()
+        self.assertRaises(MyException, tt.join)
+        self.assertEquals(True, tt.sync_event.isSet())
+
+    def test_switch_and_set(self):
+        """Caller can precisely control a thread."""
+        control1 = threading.Event()
+        control2 = threading.Event()
+        control3 = threading.Event()
+
+        class TestThread(cethread.CatchingExceptionThread):
+
+            def __init__(self):
+                super(TestThread, self).__init__(target=self.step_by_step)
+                self.current_step = 'starting'
+                self.step1 = threading.Event()
+                self.set_sync_event(self.step1)
+                self.step2 = threading.Event()
+                self.final = threading.Event()
+
+            def step_by_step(self):
+                control1.wait()
+                self.current_step = 'step1'
+                self.switch_and_set(self.step2)
+                control2.wait()
+                self.current_step = 'step2'
+                self.switch_and_set(self.final)
+                control3.wait()
+                self.current_step = 'done'
+
+        tt = TestThread()
+        tt.start()
+        self.assertEquals('starting', tt.current_step)
+        control1.set()
+        tt.step1.wait()
+        self.assertEquals('step1', tt.current_step)
+        control2.set()
+        tt.step2.wait()
+        self.assertEquals('step2', tt.current_step)
+        control3.set()
+        # We don't wait on tt.final
+        tt.join()
+        self.assertEquals('done', tt.current_step)
+
+    def test_exception_while_switch_and_set(self):
+        control1 = threading.Event()
+
+        class MyException(Exception):
+            pass
+
+        class TestThread(cethread.CatchingExceptionThread):
+
+            def __init__(self, *args, **kwargs):
+                self.step1 = threading.Event()
+                self.step2 = threading.Event()
+                super(TestThread, self).__init__(target=self.step_by_step,
+                                                 sync_event=self.step1)
+                self.current_step = 'starting'
+                self.set_sync_event(self.step1)
+
+            def step_by_step(self):
+                control1.wait()
+                self.current_step = 'step1'
+                self.switch_and_set(self.step2)
+
+            def set_sync_event(self, event):
+                # We force an exception while trying to set step2
+                if event is self.step2:
+                    raise MyException()
+                super(TestThread, self).set_sync_event(event)
+
+        tt = TestThread()
+        tt.start()
+        self.assertEquals('starting', tt.current_step)
+        control1.set()
+        # We now wait on step1 which will be set when catching the exception
+        tt.step1.wait()
+        self.assertRaises(MyException, tt.pending_exception)
+        self.assertIs(tt.step1, tt.sync_event)
+        self.assertTrue(tt.step1.isSet())

=== modified file 'bzrlib/tests/test_http.py'
--- a/bzrlib/tests/test_http.py	2011-02-08 16:26:23 +0000
+++ b/bzrlib/tests/test_http.py	2011-02-10 12:37:27 +0000
@@ -32,12 +32,12 @@
 import bzrlib
 from bzrlib import (
     bzrdir,
+    cethread,
     config,
     errors,
     osutils,
     remote as _mod_remote,
     tests,
-    thread,
     transport,
     ui,
     )

=== modified file 'bzrlib/tests/test_server.py'
--- a/bzrlib/tests/test_server.py	2011-02-08 16:26:23 +0000
+++ b/bzrlib/tests/test_server.py	2011-02-10 12:37:27 +0000
@@ -22,8 +22,8 @@
 
 
 from bzrlib import (
+    cethread,
     osutils,
-    thread,
     transport,
     urlutils,
     )
@@ -243,7 +243,7 @@
         raise NotImplementedError
 
 
-class TestThread(thread.CatchingExceptionThread):
+class TestThread(cethread.CatchingExceptionThread):
 
     def join(self, timeout=5):
         """Overrides to use a default timeout.

=== modified file 'doc/en/release-notes/bzr-2.4.txt'
--- a/doc/en/release-notes/bzr-2.4.txt	2011-02-11 14:59:31 +0000
+++ b/doc/en/release-notes/bzr-2.4.txt	2011-02-11 17:12:35 +0000
@@ -135,8 +135,8 @@
 * ``CatchingExceptionThread`` (formerly ThreadWithException) has been moved
   out of the ``bzrlib.tests`` hierarchy to make it clearer that it can be used
   outside of tests. This class makes it easier to track exceptions in threads
-  by cacthing them so they can be re-raised in the controlling thread.
-  (Vincent Ladeuil)
+  by cacthing them so they can be re-raised in the controlling thread. It's
+  available in the ``bzrlib.cethread`` module.  (Vincent Ladeuil)
 
 Testing
 *******




More information about the bazaar-commits mailing list