17db96d56Sopenharmony_ciimport _thread 27db96d56Sopenharmony_ciimport contextlib 37db96d56Sopenharmony_ciimport functools 47db96d56Sopenharmony_ciimport sys 57db96d56Sopenharmony_ciimport threading 67db96d56Sopenharmony_ciimport time 77db96d56Sopenharmony_ciimport unittest 87db96d56Sopenharmony_ci 97db96d56Sopenharmony_cifrom test import support 107db96d56Sopenharmony_ci 117db96d56Sopenharmony_ci 127db96d56Sopenharmony_ci#======================================================================= 137db96d56Sopenharmony_ci# Threading support to prevent reporting refleaks when running regrtest.py -R 147db96d56Sopenharmony_ci 157db96d56Sopenharmony_ci# NOTE: we use thread._count() rather than threading.enumerate() (or the 167db96d56Sopenharmony_ci# moral equivalent thereof) because a threading.Thread object is still alive 177db96d56Sopenharmony_ci# until its __bootstrap() method has returned, even after it has been 187db96d56Sopenharmony_ci# unregistered from the threading module. 197db96d56Sopenharmony_ci# thread._count(), on the other hand, only gets decremented *after* the 207db96d56Sopenharmony_ci# __bootstrap() method has returned, which gives us reliable reference counts 217db96d56Sopenharmony_ci# at the end of a test run. 227db96d56Sopenharmony_ci 237db96d56Sopenharmony_ci 247db96d56Sopenharmony_cidef threading_setup(): 257db96d56Sopenharmony_ci return _thread._count(), threading._dangling.copy() 267db96d56Sopenharmony_ci 277db96d56Sopenharmony_ci 287db96d56Sopenharmony_cidef threading_cleanup(*original_values): 297db96d56Sopenharmony_ci _MAX_COUNT = 100 307db96d56Sopenharmony_ci 317db96d56Sopenharmony_ci for count in range(_MAX_COUNT): 327db96d56Sopenharmony_ci values = _thread._count(), threading._dangling 337db96d56Sopenharmony_ci if values == original_values: 347db96d56Sopenharmony_ci break 357db96d56Sopenharmony_ci 367db96d56Sopenharmony_ci if not count: 377db96d56Sopenharmony_ci # Display a warning at the first iteration 387db96d56Sopenharmony_ci support.environment_altered = True 397db96d56Sopenharmony_ci dangling_threads = values[1] 407db96d56Sopenharmony_ci support.print_warning(f"threading_cleanup() failed to cleanup " 417db96d56Sopenharmony_ci f"{values[0] - original_values[0]} threads " 427db96d56Sopenharmony_ci f"(count: {values[0]}, " 437db96d56Sopenharmony_ci f"dangling: {len(dangling_threads)})") 447db96d56Sopenharmony_ci for thread in dangling_threads: 457db96d56Sopenharmony_ci support.print_warning(f"Dangling thread: {thread!r}") 467db96d56Sopenharmony_ci 477db96d56Sopenharmony_ci # Don't hold references to threads 487db96d56Sopenharmony_ci dangling_threads = None 497db96d56Sopenharmony_ci values = None 507db96d56Sopenharmony_ci 517db96d56Sopenharmony_ci time.sleep(0.01) 527db96d56Sopenharmony_ci support.gc_collect() 537db96d56Sopenharmony_ci 547db96d56Sopenharmony_ci 557db96d56Sopenharmony_cidef reap_threads(func): 567db96d56Sopenharmony_ci """Use this function when threads are being used. This will 577db96d56Sopenharmony_ci ensure that the threads are cleaned up even when the test fails. 587db96d56Sopenharmony_ci """ 597db96d56Sopenharmony_ci @functools.wraps(func) 607db96d56Sopenharmony_ci def decorator(*args): 617db96d56Sopenharmony_ci key = threading_setup() 627db96d56Sopenharmony_ci try: 637db96d56Sopenharmony_ci return func(*args) 647db96d56Sopenharmony_ci finally: 657db96d56Sopenharmony_ci threading_cleanup(*key) 667db96d56Sopenharmony_ci return decorator 677db96d56Sopenharmony_ci 687db96d56Sopenharmony_ci 697db96d56Sopenharmony_ci@contextlib.contextmanager 707db96d56Sopenharmony_cidef wait_threads_exit(timeout=None): 717db96d56Sopenharmony_ci """ 727db96d56Sopenharmony_ci bpo-31234: Context manager to wait until all threads created in the with 737db96d56Sopenharmony_ci statement exit. 747db96d56Sopenharmony_ci 757db96d56Sopenharmony_ci Use _thread.count() to check if threads exited. Indirectly, wait until 767db96d56Sopenharmony_ci threads exit the internal t_bootstrap() C function of the _thread module. 777db96d56Sopenharmony_ci 787db96d56Sopenharmony_ci threading_setup() and threading_cleanup() are designed to emit a warning 797db96d56Sopenharmony_ci if a test leaves running threads in the background. This context manager 807db96d56Sopenharmony_ci is designed to cleanup threads started by the _thread.start_new_thread() 817db96d56Sopenharmony_ci which doesn't allow to wait for thread exit, whereas thread.Thread has a 827db96d56Sopenharmony_ci join() method. 837db96d56Sopenharmony_ci """ 847db96d56Sopenharmony_ci if timeout is None: 857db96d56Sopenharmony_ci timeout = support.SHORT_TIMEOUT 867db96d56Sopenharmony_ci old_count = _thread._count() 877db96d56Sopenharmony_ci try: 887db96d56Sopenharmony_ci yield 897db96d56Sopenharmony_ci finally: 907db96d56Sopenharmony_ci start_time = time.monotonic() 917db96d56Sopenharmony_ci deadline = start_time + timeout 927db96d56Sopenharmony_ci while True: 937db96d56Sopenharmony_ci count = _thread._count() 947db96d56Sopenharmony_ci if count <= old_count: 957db96d56Sopenharmony_ci break 967db96d56Sopenharmony_ci if time.monotonic() > deadline: 977db96d56Sopenharmony_ci dt = time.monotonic() - start_time 987db96d56Sopenharmony_ci msg = (f"wait_threads() failed to cleanup {count - old_count} " 997db96d56Sopenharmony_ci f"threads after {dt:.1f} seconds " 1007db96d56Sopenharmony_ci f"(count: {count}, old count: {old_count})") 1017db96d56Sopenharmony_ci raise AssertionError(msg) 1027db96d56Sopenharmony_ci time.sleep(0.010) 1037db96d56Sopenharmony_ci support.gc_collect() 1047db96d56Sopenharmony_ci 1057db96d56Sopenharmony_ci 1067db96d56Sopenharmony_cidef join_thread(thread, timeout=None): 1077db96d56Sopenharmony_ci """Join a thread. Raise an AssertionError if the thread is still alive 1087db96d56Sopenharmony_ci after timeout seconds. 1097db96d56Sopenharmony_ci """ 1107db96d56Sopenharmony_ci if timeout is None: 1117db96d56Sopenharmony_ci timeout = support.SHORT_TIMEOUT 1127db96d56Sopenharmony_ci thread.join(timeout) 1137db96d56Sopenharmony_ci if thread.is_alive(): 1147db96d56Sopenharmony_ci msg = f"failed to join the thread in {timeout:.1f} seconds" 1157db96d56Sopenharmony_ci raise AssertionError(msg) 1167db96d56Sopenharmony_ci 1177db96d56Sopenharmony_ci 1187db96d56Sopenharmony_ci@contextlib.contextmanager 1197db96d56Sopenharmony_cidef start_threads(threads, unlock=None): 1207db96d56Sopenharmony_ci import faulthandler 1217db96d56Sopenharmony_ci threads = list(threads) 1227db96d56Sopenharmony_ci started = [] 1237db96d56Sopenharmony_ci try: 1247db96d56Sopenharmony_ci try: 1257db96d56Sopenharmony_ci for t in threads: 1267db96d56Sopenharmony_ci t.start() 1277db96d56Sopenharmony_ci started.append(t) 1287db96d56Sopenharmony_ci except: 1297db96d56Sopenharmony_ci if support.verbose: 1307db96d56Sopenharmony_ci print("Can't start %d threads, only %d threads started" % 1317db96d56Sopenharmony_ci (len(threads), len(started))) 1327db96d56Sopenharmony_ci raise 1337db96d56Sopenharmony_ci yield 1347db96d56Sopenharmony_ci finally: 1357db96d56Sopenharmony_ci try: 1367db96d56Sopenharmony_ci if unlock: 1377db96d56Sopenharmony_ci unlock() 1387db96d56Sopenharmony_ci endtime = time.monotonic() 1397db96d56Sopenharmony_ci for timeout in range(1, 16): 1407db96d56Sopenharmony_ci endtime += 60 1417db96d56Sopenharmony_ci for t in started: 1427db96d56Sopenharmony_ci t.join(max(endtime - time.monotonic(), 0.01)) 1437db96d56Sopenharmony_ci started = [t for t in started if t.is_alive()] 1447db96d56Sopenharmony_ci if not started: 1457db96d56Sopenharmony_ci break 1467db96d56Sopenharmony_ci if support.verbose: 1477db96d56Sopenharmony_ci print('Unable to join %d threads during a period of ' 1487db96d56Sopenharmony_ci '%d minutes' % (len(started), timeout)) 1497db96d56Sopenharmony_ci finally: 1507db96d56Sopenharmony_ci started = [t for t in started if t.is_alive()] 1517db96d56Sopenharmony_ci if started: 1527db96d56Sopenharmony_ci faulthandler.dump_traceback(sys.stdout) 1537db96d56Sopenharmony_ci raise AssertionError('Unable to join %d threads' % len(started)) 1547db96d56Sopenharmony_ci 1557db96d56Sopenharmony_ci 1567db96d56Sopenharmony_ciclass catch_threading_exception: 1577db96d56Sopenharmony_ci """ 1587db96d56Sopenharmony_ci Context manager catching threading.Thread exception using 1597db96d56Sopenharmony_ci threading.excepthook. 1607db96d56Sopenharmony_ci 1617db96d56Sopenharmony_ci Attributes set when an exception is caught: 1627db96d56Sopenharmony_ci 1637db96d56Sopenharmony_ci * exc_type 1647db96d56Sopenharmony_ci * exc_value 1657db96d56Sopenharmony_ci * exc_traceback 1667db96d56Sopenharmony_ci * thread 1677db96d56Sopenharmony_ci 1687db96d56Sopenharmony_ci See threading.excepthook() documentation for these attributes. 1697db96d56Sopenharmony_ci 1707db96d56Sopenharmony_ci These attributes are deleted at the context manager exit. 1717db96d56Sopenharmony_ci 1727db96d56Sopenharmony_ci Usage: 1737db96d56Sopenharmony_ci 1747db96d56Sopenharmony_ci with threading_helper.catch_threading_exception() as cm: 1757db96d56Sopenharmony_ci # code spawning a thread which raises an exception 1767db96d56Sopenharmony_ci ... 1777db96d56Sopenharmony_ci 1787db96d56Sopenharmony_ci # check the thread exception, use cm attributes: 1797db96d56Sopenharmony_ci # exc_type, exc_value, exc_traceback, thread 1807db96d56Sopenharmony_ci ... 1817db96d56Sopenharmony_ci 1827db96d56Sopenharmony_ci # exc_type, exc_value, exc_traceback, thread attributes of cm no longer 1837db96d56Sopenharmony_ci # exists at this point 1847db96d56Sopenharmony_ci # (to avoid reference cycles) 1857db96d56Sopenharmony_ci """ 1867db96d56Sopenharmony_ci 1877db96d56Sopenharmony_ci def __init__(self): 1887db96d56Sopenharmony_ci self.exc_type = None 1897db96d56Sopenharmony_ci self.exc_value = None 1907db96d56Sopenharmony_ci self.exc_traceback = None 1917db96d56Sopenharmony_ci self.thread = None 1927db96d56Sopenharmony_ci self._old_hook = None 1937db96d56Sopenharmony_ci 1947db96d56Sopenharmony_ci def _hook(self, args): 1957db96d56Sopenharmony_ci self.exc_type = args.exc_type 1967db96d56Sopenharmony_ci self.exc_value = args.exc_value 1977db96d56Sopenharmony_ci self.exc_traceback = args.exc_traceback 1987db96d56Sopenharmony_ci self.thread = args.thread 1997db96d56Sopenharmony_ci 2007db96d56Sopenharmony_ci def __enter__(self): 2017db96d56Sopenharmony_ci self._old_hook = threading.excepthook 2027db96d56Sopenharmony_ci threading.excepthook = self._hook 2037db96d56Sopenharmony_ci return self 2047db96d56Sopenharmony_ci 2057db96d56Sopenharmony_ci def __exit__(self, *exc_info): 2067db96d56Sopenharmony_ci threading.excepthook = self._old_hook 2077db96d56Sopenharmony_ci del self.exc_type 2087db96d56Sopenharmony_ci del self.exc_value 2097db96d56Sopenharmony_ci del self.exc_traceback 2107db96d56Sopenharmony_ci del self.thread 2117db96d56Sopenharmony_ci 2127db96d56Sopenharmony_ci 2137db96d56Sopenharmony_cidef _can_start_thread() -> bool: 2147db96d56Sopenharmony_ci """Detect whether Python can start new threads. 2157db96d56Sopenharmony_ci 2167db96d56Sopenharmony_ci Some WebAssembly platforms do not provide a working pthread 2177db96d56Sopenharmony_ci implementation. Thread support is stubbed and any attempt 2187db96d56Sopenharmony_ci to create a new thread fails. 2197db96d56Sopenharmony_ci 2207db96d56Sopenharmony_ci - wasm32-wasi does not have threading. 2217db96d56Sopenharmony_ci - wasm32-emscripten can be compiled with or without pthread 2227db96d56Sopenharmony_ci support (-s USE_PTHREADS / __EMSCRIPTEN_PTHREADS__). 2237db96d56Sopenharmony_ci """ 2247db96d56Sopenharmony_ci if sys.platform == "emscripten": 2257db96d56Sopenharmony_ci return sys._emscripten_info.pthreads 2267db96d56Sopenharmony_ci elif sys.platform == "wasi": 2277db96d56Sopenharmony_ci return False 2287db96d56Sopenharmony_ci else: 2297db96d56Sopenharmony_ci # assume all other platforms have working thread support. 2307db96d56Sopenharmony_ci return True 2317db96d56Sopenharmony_ci 2327db96d56Sopenharmony_cican_start_thread = _can_start_thread() 2337db96d56Sopenharmony_ci 2347db96d56Sopenharmony_cidef requires_working_threading(*, module=False): 2357db96d56Sopenharmony_ci """Skip tests or modules that require working threading. 2367db96d56Sopenharmony_ci 2377db96d56Sopenharmony_ci Can be used as a function/class decorator or to skip an entire module. 2387db96d56Sopenharmony_ci """ 2397db96d56Sopenharmony_ci msg = "requires threading support" 2407db96d56Sopenharmony_ci if module: 2417db96d56Sopenharmony_ci if not can_start_thread: 2427db96d56Sopenharmony_ci raise unittest.SkipTest(msg) 2437db96d56Sopenharmony_ci else: 2447db96d56Sopenharmony_ci return unittest.skipUnless(can_start_thread, msg) 245