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