Rev 5653: Split ThreadWithException out of the tests hierarchy. in file:///home/vila/src/bzr/experimental/thread-with-exception/
Vincent Ladeuil
v.ladeuil+lp at free.fr
Tue Feb 8 15:54:40 UTC 2011
At file:///home/vila/src/bzr/experimental/thread-with-exception/
------------------------------------------------------------
revno: 5653
revision-id: v.ladeuil+lp at free.fr-20110208155440-lns0pivfv8vskxji
parent: pqm at pqm.ubuntu.com-20110208144422-qz2zctgn6n5apl33
committer: Vincent Ladeuil <v.ladeuil+lp at free.fr>
branch nick: thread-with-exception
timestamp: Tue 2011-02-08 16:54:40 +0100
message:
Split ThreadWithException out of the tests hierarchy.
-------------- next part --------------
=== 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 15:54:40 +0000
@@ -37,6 +37,7 @@
osutils,
remote as _mod_remote,
tests,
+ thread,
transport,
ui,
)
@@ -178,7 +179,7 @@
self._sock.bind(('127.0.0.1', 0))
self.host, self.port = self._sock.getsockname()
self._ready = threading.Event()
- self._thread = test_server.ThreadWithException(
+ self._thread = test_server.TestThread(
event=self._ready, target=self._accept_read_and_reply)
self._thread.start()
if 'threads' in tests.selftest_debug_flags:
=== 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 15:54:40 +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.ThreadWithException):
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,7 +437,7 @@
"""Start a new thread to process the request."""
started = threading.Event()
stopped = threading.Event()
- t = ThreadWithException(
+ t = TestThread(
event=stopped,
name='%s -> %s' % (client_address, self.server_address),
target = self.process_request_thread,
@@ -589,7 +504,7 @@
def start_server(self):
self.server = self.create_server()
- self._server_thread = ThreadWithException(
+ self._server_thread = TestThread(
event=self.server.started,
target=self.run_server)
self._server_thread.start()
=== 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 15:54:40 +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):
=== 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 15:54:40 +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 TestThreadWithException(tests.TestCase):
+
+ def test_start_and_join_smoke_test(self):
+ def do_nothing():
+ pass
+
+ tt = thread.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 = thread.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 = thread.ThreadWithException(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 15:54:40 +0000
@@ -0,0 +1,115 @@
+# 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 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()
+
+
+ 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.
+
+ 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
+
+ def pending_exception(self):
+ """Raise the caught exception.
+
+ This does nothing if no exception occurred.
+ """
+ self.join(timeout=0)
More information about the bazaar-commits
mailing list