Rev 5655: (vila) Moves CatchingExceptionThread out of the bzrlib.tests hierarchy. in file:///home/pqm/archives/thelove/bzr/%2Btrunk/

Canonical.com Patch Queue Manager pqm at pqm.ubuntu.com
Wed Feb 9 10:44:20 UTC 2011


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

------------------------------------------------------------
revno: 5655 [merge]
revision-id: pqm at pqm.ubuntu.com-20110209104418-n3058l06ehubtl9a
parent: pqm at pqm.ubuntu.com-20110209100454-gb7liylp8ywpdrn2
parent: v.ladeuil+lp at free.fr-20110209091410-cubxgkmrfd0hu577
committer: Canonical.com Patch Queue Manager <pqm at pqm.ubuntu.com>
branch nick: +trunk
timestamp: Wed 2011-02-09 10:44:18 +0000
message:
  (vila) Moves CatchingExceptionThread out of the bzrlib.tests hierarchy.
   (Vincent Ladeuil)
added:
  bzrlib/tests/test_thread.py    test_thread.py-20110208152811-9ivenqn0omw1ev4d-1
  bzrlib/thread.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
  bzrlib/tests/test_test_server.py test_test_server.py-20100601152414-r8rln0ok7514pcoz-1
  doc/en/release-notes/bzr-2.4.txt bzr2.4.txt-20110114053217-k7ym9jfz243fddjm-1
=== modified file 'bzrlib/tests/__init__.py'
--- a/bzrlib/tests/__init__.py	2011-01-27 15:58:36 +0000
+++ b/bzrlib/tests/__init__.py	2011-02-08 15:54:40 +0000
@@ -3858,6 +3858,7 @@
         'bzrlib.tests.test_testament',
         'bzrlib.tests.test_textfile',
         'bzrlib.tests.test_textmerge',
+        'bzrlib.tests.test_thread',
         'bzrlib.tests.test_timestamp',
         'bzrlib.tests.test_trace',
         'bzrlib.tests.test_transactions',

=== modified file 'bzrlib/tests/test_http.py'
--- a/bzrlib/tests/test_http.py	2011-02-03 09:07:25 +0000
+++ b/bzrlib/tests/test_http.py	2011-02-08 16:26:23 +0000
@@ -37,6 +37,7 @@
     osutils,
     remote as _mod_remote,
     tests,
+    thread,
     transport,
     ui,
     )
@@ -178,8 +179,8 @@
         self._sock.bind(('127.0.0.1', 0))
         self.host, self.port = self._sock.getsockname()
         self._ready = threading.Event()
-        self._thread = test_server.ThreadWithException(
-            event=self._ready, target=self._accept_read_and_reply)
+        self._thread = test_server.TestThread(
+            sync_event=self._ready, target=self._accept_read_and_reply)
         self._thread.start()
         if 'threads' in tests.selftest_debug_flags:
             sys.stderr.write('Thread started: %s\n' % (self._thread.ident,))

=== modified file 'bzrlib/tests/test_server.py'
--- a/bzrlib/tests/test_server.py	2011-01-14 22:36:45 +0000
+++ b/bzrlib/tests/test_server.py	2011-02-08 16:26:23 +0000
@@ -23,6 +23,7 @@
 
 from bzrlib import (
     osutils,
+    thread,
     transport,
     urlutils,
     )
@@ -242,94 +243,15 @@
         raise NotImplementedError
 
 
-class ThreadWithException(threading.Thread):
-    """A catching exception thread.
-
-    If an exception occurs during the thread execution, it's caught and
-    re-raised when the thread is joined().
-    """
-
-    def __init__(self, *args, **kwargs):
-        # There are cases where the calling thread must wait, yet, if an
-        # exception occurs, the event should be set so the caller is not
-        # blocked. The main example is a calling thread that want to wait for
-        # the called thread to be in a given state before continuing.
-        try:
-            event = kwargs.pop('event')
-        except KeyError:
-            # If the caller didn't pass a specific event, create our own
-            event = threading.Event()
-        super(ThreadWithException, self).__init__(*args, **kwargs)
-        self.set_ready_event(event)
-        self.exception = None
-        self.ignored_exceptions = None # see set_ignored_exceptions
-
-    # compatibility thunk for python-2.4 and python-2.5...
-    if sys.version_info < (2, 6):
-        name = property(threading.Thread.getName, threading.Thread.setName)
-
-    def set_ready_event(self, event):
-        """Set the ``ready`` event used to synchronize exception catching.
-
-        When the thread uses an event to synchronize itself with another thread
-        (setting it when the other thread can wake up from a ``wait`` call),
-        the event must be set after catching an exception or the other thread
-        will hang.
-
-        Some threads require multiple events and should set the relevant one
-        when appropriate.
-        """
-        self.ready = event
-
-    def set_ignored_exceptions(self, ignored):
-        """Declare which exceptions will be ignored.
-
-        :param ignored: Can be either:
-           - None: all exceptions will be raised,
-           - an exception class: the instances of this class will be ignored,
-           - a tuple of exception classes: the instances of any class of the
-             list will be ignored,
-           - a callable: that will be passed the exception object
-             and should return True if the exception should be ignored
-        """
-        if ignored is None:
-            self.ignored_exceptions = None
-        elif isinstance(ignored, (Exception, tuple)):
-            self.ignored_exceptions = lambda e: isinstance(e, ignored)
-        else:
-            self.ignored_exceptions = ignored
-
-    def run(self):
-        """Overrides Thread.run to capture any exception."""
-        self.ready.clear()
-        try:
-            try:
-                super(ThreadWithException, self).run()
-            except:
-                self.exception = sys.exc_info()
-        finally:
-            # Make sure the calling thread is released
-            self.ready.set()
-
+class TestThread(thread.CatchingExceptionThread):
 
     def join(self, timeout=5):
-        """Overrides Thread.join to raise any exception caught.
-
-
-        Calling join(timeout=0) will raise the caught exception or return None
-        if the thread is still alive.
+        """Overrides to use a default timeout.
 
         The default timeout is set to 5 and should expire only when a thread
         serving a client connection is hung.
         """
-        super(ThreadWithException, self).join(timeout)
-        if self.exception is not None:
-            exc_class, exc_value, exc_tb = self.exception
-            self.exception = None # The exception should be raised only once
-            if (self.ignored_exceptions is None
-                or not self.ignored_exceptions(exc_value)):
-                # Raise non ignored exceptions
-                raise exc_class, exc_value, exc_tb
+        super(TestThread, self).join(timeout)
         if timeout and self.isAlive():
             # The timeout expired without joining the thread, the thread is
             # therefore stucked and that's a failure as far as the test is
@@ -342,13 +264,6 @@
             sys.stderr.write('thread %s hung\n' % (self.name,))
             #raise AssertionError('thread %s hung' % (self.name,))
 
-    def pending_exception(self):
-        """Raise the caught exception.
-
-        This does nothing if no exception occurred.
-        """
-        self.join(timeout=0)
-
 
 class TestingTCPServerMixin:
     """Mixin to support running SocketServer.TCPServer in a thread.
@@ -522,8 +437,8 @@
         """Start a new thread to process the request."""
         started = threading.Event()
         stopped = threading.Event()
-        t = ThreadWithException(
-            event=stopped,
+        t = TestThread(
+            sync_event=stopped,
             name='%s -> %s' % (client_address, self.server_address),
             target = self.process_request_thread,
             args = (started, stopped, request, client_address))
@@ -589,8 +504,8 @@
 
     def start_server(self):
         self.server = self.create_server()
-        self._server_thread = ThreadWithException(
-            event=self.server.started,
+        self._server_thread = TestThread(
+            sync_event=self.server.started,
             target=self.run_server)
         self._server_thread.start()
         # Wait for the server thread to start (i.e release the lock)
@@ -606,7 +521,7 @@
         self._server_thread.pending_exception()
         # From now on, we'll use a different event to ensure the server can set
         # its exception
-        self._server_thread.set_ready_event(self.server.stopped)
+        self._server_thread.set_sync_event(self.server.stopped)
 
     def run_server(self):
         self.server.serve()

=== modified file 'bzrlib/tests/test_test_server.py'
--- a/bzrlib/tests/test_test_server.py	2011-01-10 22:20:12 +0000
+++ b/bzrlib/tests/test_test_server.py	2011-02-08 16:26:23 +0000
@@ -30,46 +30,6 @@
 load_tests = load_tests_apply_scenarios
 
 
-class TestThreadWithException(tests.TestCase):
-
-    def test_start_and_join_smoke_test(self):
-        def do_nothing():
-            pass
-
-        tt = test_server.ThreadWithException(target=do_nothing)
-        tt.start()
-        tt.join()
-
-    def test_exception_is_re_raised(self):
-        class MyException(Exception):
-            pass
-
-        def raise_my_exception():
-            raise MyException()
-
-        tt = test_server.ThreadWithException(target=raise_my_exception)
-        tt.start()
-        self.assertRaises(MyException, tt.join)
-
-    def test_join_when_no_exception(self):
-        resume = threading.Event()
-        class MyException(Exception):
-            pass
-
-        def raise_my_exception():
-            # Wait for the test to tell us to resume
-            resume.wait()
-            # Now we can raise
-            raise MyException()
-
-        tt = test_server.ThreadWithException(target=raise_my_exception)
-        tt.start()
-        tt.join(timeout=0)
-        self.assertIs(None, tt.exception)
-        resume.set()
-        self.assertRaises(MyException, tt.join)
-
-
 class TCPClient(object):
 
     def __init__(self):
@@ -221,7 +181,7 @@
 
             def handle_connection(self):
                 req = self.rfile.readline()
-                threading.currentThread().set_ready_event(sync)
+                threading.currentThread().set_sync_event(sync)
                 raise FailToRespond()
 
         server = self.get_server(
@@ -243,7 +203,7 @@
             def handle(self):
                 # We want to sync with the thread that is serving the
                 # connection.
-                threading.currentThread().set_ready_event(sync)
+                threading.currentThread().set_sync_event(sync)
                 raise CantServe()
 
         server = self.get_server(

=== added file 'bzrlib/tests/test_thread.py'
--- a/bzrlib/tests/test_thread.py	1970-01-01 00:00:00 +0000
+++ b/bzrlib/tests/test_thread.py	2011-02-08 16:26:23 +0000
@@ -0,0 +1,64 @@
+# Copyright (C) 2010, 2011 Canonical Ltd
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 2 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, write to the Free Software
+# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+
+import threading
+
+from bzrlib import (
+    tests,
+    thread,
+    )
+
+
+class TestCatchingExceptionThread(tests.TestCase):
+
+    def test_start_and_join_smoke_test(self):
+        def do_nothing():
+            pass
+
+        tt = thread.CatchingExceptionThread(target=do_nothing)
+        tt.start()
+        tt.join()
+
+    def test_exception_is_re_raised(self):
+        class MyException(Exception):
+            pass
+
+        def raise_my_exception():
+            raise MyException()
+
+        tt = thread.CatchingExceptionThread(target=raise_my_exception)
+        tt.start()
+        self.assertRaises(MyException, tt.join)
+
+    def test_join_when_no_exception(self):
+        resume = threading.Event()
+        class MyException(Exception):
+            pass
+
+        def raise_my_exception():
+            # Wait for the test to tell us to resume
+            resume.wait()
+            # Now we can raise
+            raise MyException()
+
+        tt = thread.CatchingExceptionThread(target=raise_my_exception)
+        tt.start()
+        tt.join(timeout=0)
+        self.assertIs(None, tt.exception)
+        resume.set()
+        self.assertRaises(MyException, tt.join)
+
+

=== added file 'bzrlib/thread.py'
--- a/bzrlib/thread.py	1970-01-01 00:00:00 +0000
+++ b/bzrlib/thread.py	2011-02-08 16:26:23 +0000
@@ -0,0 +1,114 @@
+# Copyright (C) 2011 Canonical Ltd
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 2 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, write to the Free Software
+# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+
+import sys
+import threading
+
+
+class CatchingExceptionThread(threading.Thread):
+    """A thread that keeps track of exceptions.
+
+    If an exception occurs during the thread execution, it's caught and
+    re-raised when the thread is joined().
+    """
+
+    def __init__(self, *args, **kwargs):
+        # There are cases where the calling thread must wait, yet, if an
+        # exception occurs, the event should be set so the caller is not
+        # blocked. The main example is a calling thread that want to wait for
+        # the called thread to be in a given state before continuing.
+        try:
+            sync_event = kwargs.pop('sync_event')
+        except KeyError:
+            # If the caller didn't pass a specific event, create our own
+            sync_event = threading.Event()
+        super(CatchingExceptionThread, self).__init__(*args, **kwargs)
+        self.set_sync_event(sync_event)
+        self.exception = None
+        self.ignored_exceptions = None # see set_ignored_exceptions
+
+    # compatibility thunk for python-2.4 and python-2.5...
+    if sys.version_info < (2, 6):
+        name = property(threading.Thread.getName, threading.Thread.setName)
+
+    def set_sync_event(self, event):
+        """Set the ``sync_event`` event used to synchronize exception catching.
+
+        When the thread uses an event to synchronize itself with another thread
+        (setting it when the other thread can wake up from a ``wait`` call),
+        the event must be set after catching an exception or the other thread
+        will hang.
+
+        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.
+        """
+        self.sync_event = event
+
+    def set_ignored_exceptions(self, ignored):
+        """Declare which exceptions will be ignored.
+
+        :param ignored: Can be either:
+           - None: all exceptions will be raised,
+           - an exception class: the instances of this class will be ignored,
+           - a tuple of exception classes: the instances of any class of the
+             list will be ignored,
+           - a callable: that will be passed the exception object
+             and should return True if the exception should be ignored
+        """
+        if ignored is None:
+            self.ignored_exceptions = None
+        elif isinstance(ignored, (Exception, tuple)):
+            self.ignored_exceptions = lambda e: isinstance(e, ignored)
+        else:
+            self.ignored_exceptions = ignored
+
+    def run(self):
+        """Overrides Thread.run to capture any exception."""
+        self.sync_event.clear()
+        try:
+            try:
+                super(CatchingExceptionThread, self).run()
+            except:
+                self.exception = sys.exc_info()
+        finally:
+            # Make sure the calling thread is released
+            self.sync_event.set()
+
+
+    def join(self, timeout=None):
+        """Overrides Thread.join to raise any exception caught.
+
+        Calling join(timeout=0) will raise the caught exception or return None
+        if the thread is still alive.
+        """
+        super(CatchingExceptionThread, self).join(timeout)
+        if self.exception is not None:
+            exc_class, exc_value, exc_tb = self.exception
+            self.exception = None # The exception should be raised only once
+            if (self.ignored_exceptions is None
+                or not self.ignored_exceptions(exc_value)):
+                # Raise non ignored exceptions
+                raise exc_class, exc_value, exc_tb
+
+    def pending_exception(self):
+        """Raise the caught exception.
+
+        This does nothing if no exception occurred.
+        """
+        self.join(timeout=0)

=== modified file 'doc/en/release-notes/bzr-2.4.txt'
--- a/doc/en/release-notes/bzr-2.4.txt	2011-02-09 09:01:33 +0000
+++ b/doc/en/release-notes/bzr-2.4.txt	2011-02-09 10:44:18 +0000
@@ -118,6 +118,12 @@
 .. Major internal changes, unlikely to be visible to users or plugin 
    developers, but interesting for bzr developers.
 
+* ``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)
+
 Testing
 *******
 




More information about the bazaar-commits mailing list