/brz/remove-bazaar

To get this branch, use:
bzr branch http://gegoxaren.bato24.eu/bzr/brz/remove-bazaar
5652.1.1 by Vincent Ladeuil
Split ThreadWithException out of the tests hierarchy.
1
# Copyright (C) 2011 Canonical Ltd
2
#
3
# This program is free software; you can redistribute it and/or modify
4
# it under the terms of the GNU General Public License as published by
5
# the Free Software Foundation; either version 2 of the License, or
6
# (at your option) any later version.
7
#
8
# This program is distributed in the hope that it will be useful,
9
# but WITHOUT ANY WARRANTY; without even the implied warranty of
10
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
11
# GNU General Public License for more details.
12
#
13
# You should have received a copy of the GNU General Public License
14
# along with this program; if not, write to the Free Software
15
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
16
17
import sys
18
import threading
19
20
5652.1.2 by Vincent Ladeuil
Use clearer names.
21
class CatchingExceptionThread(threading.Thread):
22
    """A thread that keeps track of exceptions.
5652.1.1 by Vincent Ladeuil
Split ThreadWithException out of the tests hierarchy.
23
24
    If an exception occurs during the thread execution, it's caught and
25
    re-raised when the thread is joined().
26
    """
27
28
    def __init__(self, *args, **kwargs):
29
        # There are cases where the calling thread must wait, yet, if an
30
        # exception occurs, the event should be set so the caller is not
31
        # blocked. The main example is a calling thread that want to wait for
32
        # the called thread to be in a given state before continuing.
33
        try:
5652.1.2 by Vincent Ladeuil
Use clearer names.
34
            sync_event = kwargs.pop('sync_event')
5652.1.1 by Vincent Ladeuil
Split ThreadWithException out of the tests hierarchy.
35
        except KeyError:
36
            # If the caller didn't pass a specific event, create our own
5652.1.2 by Vincent Ladeuil
Use clearer names.
37
            sync_event = threading.Event()
38
        super(CatchingExceptionThread, self).__init__(*args, **kwargs)
39
        self.set_sync_event(sync_event)
5652.1.1 by Vincent Ladeuil
Split ThreadWithException out of the tests hierarchy.
40
        self.exception = None
7143.15.2 by Jelmer Vernooij
Run autopep8.
41
        self.ignored_exceptions = None  # see set_ignored_exceptions
5652.1.4 by Vincent Ladeuil
Add CatchingExceptionThread.set_and_switch() to avoid race conditions.
42
        self.lock = threading.Lock()
5652.1.1 by Vincent Ladeuil
Split ThreadWithException out of the tests hierarchy.
43
5652.1.2 by Vincent Ladeuil
Use clearer names.
44
    def set_sync_event(self, event):
45
        """Set the ``sync_event`` event used to synchronize exception catching.
5652.1.1 by Vincent Ladeuil
Split ThreadWithException out of the tests hierarchy.
46
47
        When the thread uses an event to synchronize itself with another thread
48
        (setting it when the other thread can wake up from a ``wait`` call),
49
        the event must be set after catching an exception or the other thread
50
        will hang.
51
52
        Some threads require multiple events and should set the relevant one
53
        when appropriate.
5652.1.2 by Vincent Ladeuil
Use clearer names.
54
5652.1.4 by Vincent Ladeuil
Add CatchingExceptionThread.set_and_switch() to avoid race conditions.
55
        Note that the event should be initially cleared so the caller can
56
        wait() on him and be released when the thread set the event.
57
58
        Also note that the thread can use multiple events, setting them as it
59
        progress, while the caller can chose to wait on any of them. What
60
        matters is that there is always one event set so that the caller is
61
        always released when an exception is caught. Re-using the same event is
62
        therefore risky as the thread itself has no idea about which event the
63
        caller is waiting on. If the caller has already been released then a
64
        cleared event won't guarantee that the caller is still waiting on it.
5652.1.1 by Vincent Ladeuil
Split ThreadWithException out of the tests hierarchy.
65
        """
5652.1.2 by Vincent Ladeuil
Use clearer names.
66
        self.sync_event = event
5652.1.1 by Vincent Ladeuil
Split ThreadWithException out of the tests hierarchy.
67
5652.1.5 by Vincent Ladeuil
Align method name and implementation.
68
    def switch_and_set(self, new):
69
        """Switch to a new ``sync_event`` and set the current one.
5652.1.4 by Vincent Ladeuil
Add CatchingExceptionThread.set_and_switch() to avoid race conditions.
70
71
        Using this method protects against race conditions while setting a new
72
        ``sync_event``.
73
74
        Note that this allows a caller to wait either on the old or the new
75
        event depending on whether it wants a fine control on what is happening
76
        inside a thread.
77
78
        :param new: The event that will become ``sync_event``
79
        """
80
        cur = self.sync_event
81
        self.lock.acquire()
7143.15.2 by Jelmer Vernooij
Run autopep8.
82
        try:  # Always release the lock
5652.1.4 by Vincent Ladeuil
Add CatchingExceptionThread.set_and_switch() to avoid race conditions.
83
            try:
84
                self.set_sync_event(new)
85
                # From now on, any exception will be synced with the new event
7143.15.5 by Jelmer Vernooij
More PEP8 fixes.
86
            except BaseException:
5652.1.4 by Vincent Ladeuil
Add CatchingExceptionThread.set_and_switch() to avoid race conditions.
87
                # Unlucky, we couldn't set the new sync event, try restoring a
88
                # safe state
89
                self.set_sync_event(cur)
90
                raise
91
            # Setting the current ``sync_event`` will release callers waiting
92
            # on it, note that it will also be set in run() if an exception is
93
            # raised
94
            cur.set()
95
        finally:
96
            self.lock.release()
97
5652.1.1 by Vincent Ladeuil
Split ThreadWithException out of the tests hierarchy.
98
    def set_ignored_exceptions(self, ignored):
99
        """Declare which exceptions will be ignored.
100
101
        :param ignored: Can be either:
7143.15.2 by Jelmer Vernooij
Run autopep8.
102
5652.1.1 by Vincent Ladeuil
Split ThreadWithException out of the tests hierarchy.
103
           - None: all exceptions will be raised,
104
           - an exception class: the instances of this class will be ignored,
105
           - a tuple of exception classes: the instances of any class of the
106
             list will be ignored,
107
           - a callable: that will be passed the exception object
108
             and should return True if the exception should be ignored
109
        """
110
        if ignored is None:
111
            self.ignored_exceptions = None
112
        elif isinstance(ignored, (Exception, tuple)):
113
            self.ignored_exceptions = lambda e: isinstance(e, ignored)
114
        else:
115
            self.ignored_exceptions = ignored
116
117
    def run(self):
118
        """Overrides Thread.run to capture any exception."""
5652.1.2 by Vincent Ladeuil
Use clearer names.
119
        self.sync_event.clear()
5652.1.1 by Vincent Ladeuil
Split ThreadWithException out of the tests hierarchy.
120
        try:
121
            try:
5652.1.2 by Vincent Ladeuil
Use clearer names.
122
                super(CatchingExceptionThread, self).run()
7143.15.5 by Jelmer Vernooij
More PEP8 fixes.
123
            except BaseException:
5652.1.1 by Vincent Ladeuil
Split ThreadWithException out of the tests hierarchy.
124
                self.exception = sys.exc_info()
125
        finally:
126
            # Make sure the calling thread is released
5652.1.2 by Vincent Ladeuil
Use clearer names.
127
            self.sync_event.set()
128
129
    def join(self, timeout=None):
5652.1.1 by Vincent Ladeuil
Split ThreadWithException out of the tests hierarchy.
130
        """Overrides Thread.join to raise any exception caught.
131
132
        Calling join(timeout=0) will raise the caught exception or return None
133
        if the thread is still alive.
134
        """
5652.1.2 by Vincent Ladeuil
Use clearer names.
135
        super(CatchingExceptionThread, self).join(timeout)
5652.1.1 by Vincent Ladeuil
Split ThreadWithException out of the tests hierarchy.
136
        if self.exception is not None:
137
            exc_class, exc_value, exc_tb = self.exception
7143.15.2 by Jelmer Vernooij
Run autopep8.
138
            self.exception = None  # The exception should be raised only once
5652.1.1 by Vincent Ladeuil
Split ThreadWithException out of the tests hierarchy.
139
            if (self.ignored_exceptions is None
7143.15.2 by Jelmer Vernooij
Run autopep8.
140
                    or not self.ignored_exceptions(exc_value)):
5652.1.1 by Vincent Ladeuil
Split ThreadWithException out of the tests hierarchy.
141
                # Raise non ignored exceptions
7479.2.1 by Jelmer Vernooij
Drop python2 support.
142
                raise exc_value
5652.1.1 by Vincent Ladeuil
Split ThreadWithException out of the tests hierarchy.
143
144
    def pending_exception(self):
145
        """Raise the caught exception.
146
147
        This does nothing if no exception occurred.
148
        """
149
        self.join(timeout=0)