/brz/remove-bazaar

To get this branch, use:
bzr branch http://gegoxaren.bato24.eu/bzr/brz/remove-bazaar

« back to all changes in this revision

Viewing changes to breezy/cethread.py

  • Committer: Jelmer Vernooij
  • Date: 2020-02-07 02:14:30 UTC
  • mto: This revision was merged to the branch mainline in revision 7492.
  • Revision ID: jelmer@jelmer.uk-20200207021430-m49iq3x4x8xlib6x
Drop python2 support.

Show diffs side-by-side

added added

removed removed

Lines of Context:
 
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
from __future__ import absolute_import
 
18
 
 
19
import sys
 
20
import threading
 
21
 
 
22
 
 
23
class CatchingExceptionThread(threading.Thread):
 
24
    """A thread that keeps track of exceptions.
 
25
 
 
26
    If an exception occurs during the thread execution, it's caught and
 
27
    re-raised when the thread is joined().
 
28
    """
 
29
 
 
30
    def __init__(self, *args, **kwargs):
 
31
        # There are cases where the calling thread must wait, yet, if an
 
32
        # exception occurs, the event should be set so the caller is not
 
33
        # blocked. The main example is a calling thread that want to wait for
 
34
        # the called thread to be in a given state before continuing.
 
35
        try:
 
36
            sync_event = kwargs.pop('sync_event')
 
37
        except KeyError:
 
38
            # If the caller didn't pass a specific event, create our own
 
39
            sync_event = threading.Event()
 
40
        super(CatchingExceptionThread, self).__init__(*args, **kwargs)
 
41
        self.set_sync_event(sync_event)
 
42
        self.exception = None
 
43
        self.ignored_exceptions = None  # see set_ignored_exceptions
 
44
        self.lock = threading.Lock()
 
45
 
 
46
    def set_sync_event(self, event):
 
47
        """Set the ``sync_event`` event used to synchronize exception catching.
 
48
 
 
49
        When the thread uses an event to synchronize itself with another thread
 
50
        (setting it when the other thread can wake up from a ``wait`` call),
 
51
        the event must be set after catching an exception or the other thread
 
52
        will hang.
 
53
 
 
54
        Some threads require multiple events and should set the relevant one
 
55
        when appropriate.
 
56
 
 
57
        Note that the event should be initially cleared so the caller can
 
58
        wait() on him and be released when the thread set the event.
 
59
 
 
60
        Also note that the thread can use multiple events, setting them as it
 
61
        progress, while the caller can chose to wait on any of them. What
 
62
        matters is that there is always one event set so that the caller is
 
63
        always released when an exception is caught. Re-using the same event is
 
64
        therefore risky as the thread itself has no idea about which event the
 
65
        caller is waiting on. If the caller has already been released then a
 
66
        cleared event won't guarantee that the caller is still waiting on it.
 
67
        """
 
68
        self.sync_event = event
 
69
 
 
70
    def switch_and_set(self, new):
 
71
        """Switch to a new ``sync_event`` and set the current one.
 
72
 
 
73
        Using this method protects against race conditions while setting a new
 
74
        ``sync_event``.
 
75
 
 
76
        Note that this allows a caller to wait either on the old or the new
 
77
        event depending on whether it wants a fine control on what is happening
 
78
        inside a thread.
 
79
 
 
80
        :param new: The event that will become ``sync_event``
 
81
        """
 
82
        cur = self.sync_event
 
83
        self.lock.acquire()
 
84
        try:  # Always release the lock
 
85
            try:
 
86
                self.set_sync_event(new)
 
87
                # From now on, any exception will be synced with the new event
 
88
            except BaseException:
 
89
                # Unlucky, we couldn't set the new sync event, try restoring a
 
90
                # safe state
 
91
                self.set_sync_event(cur)
 
92
                raise
 
93
            # Setting the current ``sync_event`` will release callers waiting
 
94
            # on it, note that it will also be set in run() if an exception is
 
95
            # raised
 
96
            cur.set()
 
97
        finally:
 
98
            self.lock.release()
 
99
 
 
100
    def set_ignored_exceptions(self, ignored):
 
101
        """Declare which exceptions will be ignored.
 
102
 
 
103
        :param ignored: Can be either:
 
104
 
 
105
           - None: all exceptions will be raised,
 
106
           - an exception class: the instances of this class will be ignored,
 
107
           - a tuple of exception classes: the instances of any class of the
 
108
             list will be ignored,
 
109
           - a callable: that will be passed the exception object
 
110
             and should return True if the exception should be ignored
 
111
        """
 
112
        if ignored is None:
 
113
            self.ignored_exceptions = None
 
114
        elif isinstance(ignored, (Exception, tuple)):
 
115
            self.ignored_exceptions = lambda e: isinstance(e, ignored)
 
116
        else:
 
117
            self.ignored_exceptions = ignored
 
118
 
 
119
    def run(self):
 
120
        """Overrides Thread.run to capture any exception."""
 
121
        self.sync_event.clear()
 
122
        try:
 
123
            try:
 
124
                super(CatchingExceptionThread, self).run()
 
125
            except BaseException:
 
126
                self.exception = sys.exc_info()
 
127
        finally:
 
128
            # Make sure the calling thread is released
 
129
            self.sync_event.set()
 
130
 
 
131
    def join(self, timeout=None):
 
132
        """Overrides Thread.join to raise any exception caught.
 
133
 
 
134
        Calling join(timeout=0) will raise the caught exception or return None
 
135
        if the thread is still alive.
 
136
        """
 
137
        super(CatchingExceptionThread, self).join(timeout)
 
138
        if self.exception is not None:
 
139
            exc_class, exc_value, exc_tb = self.exception
 
140
            self.exception = None  # The exception should be raised only once
 
141
            if (self.ignored_exceptions is None
 
142
                    or not self.ignored_exceptions(exc_value)):
 
143
                # Raise non ignored exceptions
 
144
                raise exc_value
 
145
 
 
146
    def pending_exception(self):
 
147
        """Raise the caught exception.
 
148
 
 
149
        This does nothing if no exception occurred.
 
150
        """
 
151
        self.join(timeout=0)