|
5 | 5 | import _thread |
6 | 6 |
|
7 | 7 | from time import monotonic as _time |
8 | | -from traceback import format_exc as _format_exc |
9 | 8 | from _weakrefset import WeakSet |
10 | 9 | from itertools import islice as _islice, count as _count |
11 | 10 | try: |
|
27 | 26 | 'enumerate', 'main_thread', 'TIMEOUT_MAX', |
28 | 27 | 'Event', 'Lock', 'RLock', 'Semaphore', 'BoundedSemaphore', 'Thread', |
29 | 28 | 'Barrier', 'BrokenBarrierError', 'Timer', 'ThreadError', |
30 | | - 'setprofile', 'settrace', 'local', 'stack_size'] |
| 29 | + 'setprofile', 'settrace', 'local', 'stack_size', |
| 30 | + 'excepthook', 'ExceptHookArgs'] |
31 | 31 |
|
32 | 32 | # Rename some stuff so "from threading import *" is safe |
33 | 33 | _start_new_thread = _thread.start_new_thread |
@@ -752,14 +752,6 @@ class Thread: |
752 | 752 | """ |
753 | 753 |
|
754 | 754 | _initialized = False |
755 | | - # Need to store a reference to sys.exc_info for printing |
756 | | - # out exceptions when a thread tries to use a global var. during interp. |
757 | | - # shutdown and thus raises an exception about trying to perform some |
758 | | - # operation on/with a NoneType |
759 | | - _exc_info = _sys.exc_info |
760 | | - # Keep sys.exc_clear too to clear the exception just before |
761 | | - # allowing .join() to return. |
762 | | - #XXX __exc_clear = _sys.exc_clear |
763 | 755 |
|
764 | 756 | def __init__(self, group=None, target=None, name=None, |
765 | 757 | args=(), kwargs=None, *, daemon=None): |
@@ -802,9 +794,9 @@ class is implemented. |
802 | 794 | self._started = Event() |
803 | 795 | self._is_stopped = False |
804 | 796 | self._initialized = True |
805 | | - # sys.stderr is not stored in the class like |
806 | | - # sys.exc_info since it can be changed between instances |
| 797 | + # Copy of sys.stderr used by self._invoke_excepthook() |
807 | 798 | self._stderr = _sys.stderr |
| 799 | + self._invoke_excepthook = _make_invoke_excepthook() |
808 | 800 | # For debugging and _after_fork() |
809 | 801 | _dangling.add(self) |
810 | 802 |
|
@@ -929,47 +921,8 @@ def _bootstrap_inner(self): |
929 | 921 |
|
930 | 922 | try: |
931 | 923 | self.run() |
932 | | - except SystemExit: |
933 | | - pass |
934 | 924 | except: |
935 | | - # If sys.stderr is no more (most likely from interpreter |
936 | | - # shutdown) use self._stderr. Otherwise still use sys (as in |
937 | | - # _sys) in case sys.stderr was redefined since the creation of |
938 | | - # self. |
939 | | - if _sys and _sys.stderr is not None: |
940 | | - print("Exception in thread %s:\n%s" % |
941 | | - (self.name, _format_exc()), file=_sys.stderr) |
942 | | - elif self._stderr is not None: |
943 | | - # Do the best job possible w/o a huge amt. of code to |
944 | | - # approximate a traceback (code ideas from |
945 | | - # Lib/traceback.py) |
946 | | - exc_type, exc_value, exc_tb = self._exc_info() |
947 | | - try: |
948 | | - print(( |
949 | | - "Exception in thread " + self.name + |
950 | | - " (most likely raised during interpreter shutdown):"), file=self._stderr) |
951 | | - print(( |
952 | | - "Traceback (most recent call last):"), file=self._stderr) |
953 | | - while exc_tb: |
954 | | - print(( |
955 | | - ' File "%s", line %s, in %s' % |
956 | | - (exc_tb.tb_frame.f_code.co_filename, |
957 | | - exc_tb.tb_lineno, |
958 | | - exc_tb.tb_frame.f_code.co_name)), file=self._stderr) |
959 | | - exc_tb = exc_tb.tb_next |
960 | | - print(("%s: %s" % (exc_type, exc_value)), file=self._stderr) |
961 | | - self._stderr.flush() |
962 | | - # Make sure that exc_tb gets deleted since it is a memory |
963 | | - # hog; deleting everything else is just for thoroughness |
964 | | - finally: |
965 | | - del exc_type, exc_value, exc_tb |
966 | | - finally: |
967 | | - # Prevent a race in |
968 | | - # test_threading.test_no_refcycle_through_target when |
969 | | - # the exception keeps the target alive past when we |
970 | | - # assert that it's dead. |
971 | | - #XXX self._exc_clear() |
972 | | - pass |
| 925 | + self._invoke_excepthook(self) |
973 | 926 | finally: |
974 | 927 | with _active_limbo_lock: |
975 | 928 | try: |
@@ -1163,6 +1116,104 @@ def getName(self): |
1163 | 1116 | def setName(self, name): |
1164 | 1117 | self.name = name |
1165 | 1118 |
|
| 1119 | + |
| 1120 | +try: |
| 1121 | + from _thread import (_excepthook as excepthook, |
| 1122 | + _ExceptHookArgs as ExceptHookArgs) |
| 1123 | +except ImportError: |
| 1124 | + # Simple Python implementation if _thread._excepthook() is not available |
| 1125 | + from traceback import print_exception as _print_exception |
| 1126 | + from collections import namedtuple |
| 1127 | + |
| 1128 | + _ExceptHookArgs = namedtuple( |
| 1129 | + 'ExceptHookArgs', |
| 1130 | + 'exc_type exc_value exc_traceback thread') |
| 1131 | + |
| 1132 | + def ExceptHookArgs(args): |
| 1133 | + return _ExceptHookArgs(*args) |
| 1134 | + |
| 1135 | + def excepthook(args, /): |
| 1136 | + """ |
| 1137 | + Handle uncaught Thread.run() exception. |
| 1138 | + """ |
| 1139 | + if args.exc_type == SystemExit: |
| 1140 | + # silently ignore SystemExit |
| 1141 | + return |
| 1142 | + |
| 1143 | + if _sys is not None and _sys.stderr is not None: |
| 1144 | + stderr = _sys.stderr |
| 1145 | + elif args.thread is not None: |
| 1146 | + stderr = args.thread._stderr |
| 1147 | + if stderr is None: |
| 1148 | + # do nothing if sys.stderr is None and sys.stderr was None |
| 1149 | + # when the thread was created |
| 1150 | + return |
| 1151 | + else: |
| 1152 | + # do nothing if sys.stderr is None and args.thread is None |
| 1153 | + return |
| 1154 | + |
| 1155 | + if args.thread is not None: |
| 1156 | + name = args.thread.name |
| 1157 | + else: |
| 1158 | + name = get_ident() |
| 1159 | + print(f"Exception in thread {name}:", |
| 1160 | + file=stderr, flush=True) |
| 1161 | + _print_exception(args.exc_type, args.exc_value, args.exc_traceback, |
| 1162 | + file=stderr) |
| 1163 | + stderr.flush() |
| 1164 | + |
| 1165 | + |
| 1166 | +def _make_invoke_excepthook(): |
| 1167 | + # Create a local namespace to ensure that variables remain alive |
| 1168 | + # when _invoke_excepthook() is called, even if it is called late during |
| 1169 | + # Python shutdown. It is mostly needed for daemon threads. |
| 1170 | + |
| 1171 | + old_excepthook = excepthook |
| 1172 | + old_sys_excepthook = _sys.excepthook |
| 1173 | + if old_excepthook is None: |
| 1174 | + raise RuntimeError("threading.excepthook is None") |
| 1175 | + if old_sys_excepthook is None: |
| 1176 | + raise RuntimeError("sys.excepthook is None") |
| 1177 | + |
| 1178 | + sys_exc_info = _sys.exc_info |
| 1179 | + local_print = print |
| 1180 | + local_sys = _sys |
| 1181 | + |
| 1182 | + def invoke_excepthook(thread): |
| 1183 | + global excepthook |
| 1184 | + try: |
| 1185 | + hook = excepthook |
| 1186 | + if hook is None: |
| 1187 | + hook = old_excepthook |
| 1188 | + |
| 1189 | + args = ExceptHookArgs([*sys_exc_info(), thread]) |
| 1190 | + |
| 1191 | + hook(args) |
| 1192 | + except Exception as exc: |
| 1193 | + exc.__suppress_context__ = True |
| 1194 | + del exc |
| 1195 | + |
| 1196 | + if local_sys is not None and local_sys.stderr is not None: |
| 1197 | + stderr = local_sys.stderr |
| 1198 | + else: |
| 1199 | + stderr = thread._stderr |
| 1200 | + |
| 1201 | + local_print("Exception in threading.excepthook:", |
| 1202 | + file=stderr, flush=True) |
| 1203 | + |
| 1204 | + if local_sys is not None and local_sys.excepthook is not None: |
| 1205 | + sys_excepthook = local_sys.excepthook |
| 1206 | + else: |
| 1207 | + sys_excepthook = old_sys_excepthook |
| 1208 | + |
| 1209 | + sys_excepthook(*sys_exc_info()) |
| 1210 | + finally: |
| 1211 | + # Break reference cycle (exception stored in a variable) |
| 1212 | + args = None |
| 1213 | + |
| 1214 | + return invoke_excepthook |
| 1215 | + |
| 1216 | + |
1166 | 1217 | # The timer class was contributed by Itamar Shtull-Trauring |
1167 | 1218 |
|
1168 | 1219 | class Timer(Thread): |
|
0 commit comments