OLD | NEW |
1 # Copyright 2013 The Chromium Authors. All rights reserved. | 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 | 2 # Use of this source code is governed by a BSD-style license that can be |
3 # found in the LICENSE file. | 3 # found in the LICENSE file. |
4 | 4 |
5 """Thread and ThreadGroup that reraise exceptions on the main thread.""" | 5 """Thread and ThreadGroup that reraise exceptions on the main thread.""" |
6 | 6 |
7 import logging | 7 import logging |
8 import sys | 8 import sys |
9 import threading | 9 import threading |
10 import time | 10 import time |
11 import traceback | 11 import traceback |
12 | 12 |
13 import watchdog_timer | 13 import watchdog_timer |
14 | 14 |
15 | 15 |
16 class TimeoutError(Exception): | 16 class TimeoutError(Exception): |
17 """Module-specific timeout exception.""" | 17 """Module-specific timeout exception.""" |
18 pass | 18 pass |
19 | 19 |
20 | 20 |
| 21 def LogThreadStack(thread): |
| 22 """Log the stack for the given thread. |
| 23 |
| 24 Args: |
| 25 thread: a threading.Thread instance. |
| 26 """ |
| 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) |
| 33 if line: |
| 34 logging.critical(' %s', line.strip()) |
| 35 logging.critical('*' * 80) |
| 36 |
| 37 |
21 class ReraiserThread(threading.Thread): | 38 class ReraiserThread(threading.Thread): |
22 """Thread class that can reraise exceptions.""" | 39 """Thread class that can reraise exceptions.""" |
23 | 40 |
24 def __init__(self, func, args=[], kwargs={}, name=None): | 41 def __init__(self, func, args=[], kwargs={}, name=None): |
25 """Initialize thread. | 42 """Initialize thread. |
26 | 43 |
27 Args: | 44 Args: |
28 func: callable to call on a new thread. | 45 func: callable to call on a new thread. |
29 args: list of positional arguments for callable, defaults to empty. | 46 args: list of positional arguments for callable, defaults to empty. |
30 kwargs: dictionary of keyword arguments for callable, defaults to empty. | 47 kwargs: dictionary of keyword arguments for callable, defaults to empty. |
(...skipping 75 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
106 immediately on exceptions raised on the main thread. Unfinished threads' | 123 immediately on exceptions raised on the main thread. Unfinished threads' |
107 stacks will be logged on watchdog timeout. | 124 stacks will be logged on watchdog timeout. |
108 | 125 |
109 Args: | 126 Args: |
110 watcher: Watchdog object providing timeout, by default waits forever. | 127 watcher: Watchdog object providing timeout, by default waits forever. |
111 """ | 128 """ |
112 try: | 129 try: |
113 self._JoinAll(watcher) | 130 self._JoinAll(watcher) |
114 except TimeoutError: | 131 except TimeoutError: |
115 for thread in (t for t in self._threads if t.isAlive()): | 132 for thread in (t for t in self._threads if t.isAlive()): |
116 stack = sys._current_frames()[thread.ident] | 133 LogThreadStack(thread) |
117 logging.critical('*' * 80) | |
118 logging.critical('Stack dump for timed out thread \'%s\'', thread.name) | |
119 logging.critical('*' * 80) | |
120 for filename, lineno, name, line in traceback.extract_stack(stack): | |
121 logging.critical('File: "%s", line %d, in %s', filename, lineno, name) | |
122 if line: | |
123 logging.critical(' %s', line.strip()) | |
124 logging.critical('*' * 80) | |
125 raise | 134 raise |
OLD | NEW |