summaryrefslogtreecommitdiff
path: root/bzrlib/cethread.py
diff options
context:
space:
mode:
authorLorry <lorry@roadtrain.codethink.co.uk>2012-08-22 15:47:16 +0100
committerLorry <lorry@roadtrain.codethink.co.uk>2012-08-22 15:47:16 +0100
commit25335618bf8755ce6b116ee14f47f5a1f2c821e9 (patch)
treed889d7ab3f9f985d0c54c534cb8052bd2e6d7163 /bzrlib/cethread.py
downloadbzr-tarball-25335618bf8755ce6b116ee14f47f5a1f2c821e9.tar.gz
Tarball conversion
Diffstat (limited to 'bzrlib/cethread.py')
-rw-r--r--bzrlib/cethread.py156
1 files changed, 156 insertions, 0 deletions
diff --git a/bzrlib/cethread.py b/bzrlib/cethread.py
new file mode 100644
index 0000000..9d5972e
--- /dev/null
+++ b/bzrlib/cethread.py
@@ -0,0 +1,156 @@
+# 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
+
+from __future__ import absolute_import
+
+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
+ self.lock = threading.Lock()
+
+ # 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 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.
+
+ :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)