1 # Copyright 2013 The Chromium Authors. All rights reserved.
2 # Use of this source code is governed by a BSD-style license that can be
3 # found in the LICENSE file.
5 """Thread and ThreadGroup that reraise exceptions on the main thread."""
6 # pylint: disable=W0212
13 from pylib.utils import watchdog_timer
16 class TimeoutError(Exception):
17 """Module-specific timeout exception."""
21 def LogThreadStack(thread):
22 """Log the stack for the given thread.
25 thread: a threading.Thread instance.
27 stack = sys._current_frames()[thread.ident]
28 logging.critical('*' * 80)
29 logging.critical('Stack dump for thread \'%s\'', thread.name)
30 logging.critical('*' * 80)
31 for filename, lineno, name, line in traceback.extract_stack(stack):
32 logging.critical('File: "%s", line %d, in %s', filename, lineno, name)
34 logging.critical(' %s', line.strip())
35 logging.critical('*' * 80)
38 class ReraiserThread(threading.Thread):
39 """Thread class that can reraise exceptions."""
41 def __init__(self, func, args=None, kwargs=None, name=None):
45 func: callable to call on a new thread.
46 args: list of positional arguments for callable, defaults to empty.
47 kwargs: dictionary of keyword arguments for callable, defaults to empty.
48 name: thread name, defaults to Thread-N.
50 super(ReraiserThread, self).__init__(name=name)
62 def ReraiseIfException(self):
63 """Reraise exception if an exception was raised in the thread."""
65 raise self._exc_info[0], self._exc_info[1], self._exc_info[2]
67 def GetReturnValue(self):
68 """Reraise exception if present, otherwise get the return value."""
69 self.ReraiseIfException()
74 """Overrides Thread.run() to add support for reraising exceptions."""
76 self._ret = self._func(*self._args, **self._kwargs)
77 except: # pylint: disable=W0702
78 self._exc_info = sys.exc_info()
81 class ReraiserThreadGroup(object):
82 """A group of ReraiserThread objects."""
84 def __init__(self, threads=None):
85 """Initialize thread group.
88 threads: a list of ReraiserThread objects; defaults to empty.
92 self._threads = threads
94 def Add(self, thread):
95 """Add a thread to the group.
98 thread: a ReraiserThread object.
100 self._threads.append(thread)
103 """Start all threads."""
104 for thread in self._threads:
107 def _JoinAll(self, watcher=watchdog_timer.WatchdogTimer(None)):
108 """Join all threads without stack dumps.
110 Reraises exceptions raised by the child threads and supports breaking
111 immediately on exceptions raised on the main thread.
114 watcher: Watchdog object providing timeout, by default waits forever.
116 alive_threads = self._threads[:]
118 for thread in alive_threads[:]:
119 if watcher.IsTimedOut():
120 raise TimeoutError('Timed out waiting for %d of %d threads.' %
121 (len(alive_threads), len(self._threads)))
122 # Allow the main thread to periodically check for interrupts.
124 if not thread.isAlive():
125 alive_threads.remove(thread)
126 # All threads are allowed to complete before reraising exceptions.
127 for thread in self._threads:
128 thread.ReraiseIfException()
130 def JoinAll(self, watcher=watchdog_timer.WatchdogTimer(None)):
133 Reraises exceptions raised by the child threads and supports breaking
134 immediately on exceptions raised on the main thread. Unfinished threads'
135 stacks will be logged on watchdog timeout.
138 watcher: Watchdog object providing timeout, by default waits forever.
141 self._JoinAll(watcher)
143 for thread in (t for t in self._threads if t.isAlive()):
144 LogThreadStack(thread)
147 def GetAllReturnValues(self, watcher=watchdog_timer.WatchdogTimer(None)):
148 """Get all return values, joining all threads if necessary.
151 watcher: same as in |JoinAll|. Only used if threads are alive.
153 if any([t.isAlive() for t in self._threads]):
154 self.JoinAll(watcher)
155 return [t.GetReturnValue() for t in self._threads]