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