[Python-checkins] bpo-40275: Adding threading_helper submodule in test.support (GH-20263)

Hai Shi webhook-mailer at python.org
Wed May 27 18:10:35 EDT 2020


https://github.com/python/cpython/commit/e80697d687b610bd7fb9104af905dec8f0bc55a7
commit: e80697d687b610bd7fb9104af905dec8f0bc55a7
branch: master
author: Hai Shi <shihai1992 at gmail.com>
committer: GitHub <noreply at github.com>
date: 2020-05-28T00:10:27+02:00
summary:

bpo-40275: Adding threading_helper submodule in test.support (GH-20263)

files:
A Lib/test/support/threading_helper.py
M Doc/library/test.rst
M Lib/test/_test_multiprocessing.py
M Lib/test/fork_wait.py
M Lib/test/lock_tests.py
M Lib/test/pickletester.py
M Lib/test/support/__init__.py
M Lib/test/test_asynchat.py
M Lib/test/test_asyncio/test_events.py
M Lib/test/test_asyncio/utils.py
M Lib/test/test_asyncore.py
M Lib/test/test_bz2.py
M Lib/test/test_capi.py
M Lib/test/test_concurrent_futures.py
M Lib/test/test_email/test_email.py
M Lib/test/test_enum.py
M Lib/test/test_ftplib.py
M Lib/test/test_functools.py
M Lib/test/test_gc.py
M Lib/test/test_hashlib.py
M Lib/test/test_httpservers.py
M Lib/test/test_imaplib.py
M Lib/test/test_import/__init__.py
M Lib/test/test_importlib/test_locks.py
M Lib/test/test_importlib/test_threaded_import.py
M Lib/test/test_io.py
M Lib/test/test_logging.py
M Lib/test/test_os.py
M Lib/test/test_poll.py
M Lib/test/test_poplib.py
M Lib/test/test_pydoc.py
M Lib/test/test_queue.py
M Lib/test/test_robotparser.py
M Lib/test/test_sched.py
M Lib/test/test_smtplib.py
M Lib/test/test_socket.py
M Lib/test/test_socketserver.py
M Lib/test/test_ssl.py
M Lib/test/test_sys.py
M Lib/test/test_thread.py
M Lib/test/test_threadedtempfile.py
M Lib/test/test_threading.py
M Lib/test/test_threading_local.py
M Lib/test/test_threadsignals.py
M Lib/test/test_urllib2_localnet.py
M Lib/test/test_xmlrpc.py

diff --git a/Doc/library/test.rst b/Doc/library/test.rst
index f7e6eba018161..7bee6e8031a05 100644
--- a/Doc/library/test.rst
+++ b/Doc/library/test.rst
@@ -838,18 +838,6 @@ The :mod:`test.support` module defines the following functions:
    .. versionadded:: 3.9
 
 
-.. function:: wait_threads_exit(timeout=60.0)
-
-   Context manager to wait until all threads created in the ``with`` statement
-   exit.
-
-
-.. function:: start_threads(threads, unlock=None)
-
-   Context manager to start *threads*.  It attempts to join the threads upon
-   exit.
-
-
 .. function:: calcobjsize(fmt)
 
    Return :func:`struct.calcsize` for ``nP{fmt}0n`` or, if ``gettotalrefcount``
@@ -988,11 +976,6 @@ The :mod:`test.support` module defines the following functions:
    the trace function.
 
 
-.. decorator:: reap_threads(func)
-
-   Decorator to ensure the threads are cleaned up even if the test fails.
-
-
 .. decorator:: bigmemtest(size, memuse, dry_run=True)
 
    Decorator for bigmem tests.
@@ -1110,23 +1093,6 @@ The :mod:`test.support` module defines the following functions:
    preserve internal cache.
 
 
-.. function:: threading_setup()
-
-   Return current thread count and copy of dangling threads.
-
-
-.. function:: threading_cleanup(*original_values)
-
-   Cleanup up threads not specified in *original_values*.  Designed to emit
-   a warning if a test leaves running threads in the background.
-
-
-.. function:: join_thread(thread, timeout=30.0)
-
-   Join a *thread* within *timeout*.  Raise an :exc:`AssertionError` if thread
-   is still alive after *timeout* seconds.
-
-
 .. function:: reap_children()
 
    Use this at the end of ``test_main`` whenever sub-processes are started.
@@ -1140,39 +1106,6 @@ The :mod:`test.support` module defines the following functions:
    is raised.
 
 
-.. function:: catch_threading_exception()
-
-   Context manager catching :class:`threading.Thread` exception using
-   :func:`threading.excepthook`.
-
-   Attributes set when an exception is catched:
-
-   * ``exc_type``
-   * ``exc_value``
-   * ``exc_traceback``
-   * ``thread``
-
-   See :func:`threading.excepthook` documentation.
-
-   These attributes are deleted at the context manager exit.
-
-   Usage::
-
-       with support.catch_threading_exception() as cm:
-           # code spawning a thread which raises an exception
-           ...
-
-           # check the thread exception, use cm attributes:
-           # exc_type, exc_value, exc_traceback, thread
-           ...
-
-       # exc_type, exc_value, exc_traceback, thread attributes of cm no longer
-       # exists at this point
-       # (to avoid reference cycles)
-
-   .. versionadded:: 3.8
-
-
 .. function:: catch_unraisable_exception()
 
    Context manager catching unraisable exception using
@@ -1628,3 +1561,81 @@ The module defines the following class:
 .. method:: BytecodeTestCase.assertNotInBytecode(x, opname, argval=_UNSPECIFIED)
 
    Throws :exc:`AssertionError` if *opname* is found.
+
+
+:mod:`test.support.threading_helper` --- Utilities for threading tests
+======================================================================
+
+.. module:: test.support.threading_helper
+   :synopsis: Support for threading tests.
+
+The :mod:`test.support.threading_helper` module provides support for threading tests.
+
+.. versionadded:: 3.10
+
+
+.. function:: join_thread(thread, timeout=None)
+
+   Join a *thread* within *timeout*.  Raise an :exc:`AssertionError` if thread
+   is still alive after *timeout* seconds.
+
+
+.. decorator:: reap_threads(func)
+
+   Decorator to ensure the threads are cleaned up even if the test fails.
+
+
+.. function:: start_threads(threads, unlock=None)
+
+   Context manager to start *threads*.  It attempts to join the threads upon
+   exit.
+
+
+.. function:: threading_cleanup(*original_values)
+
+   Cleanup up threads not specified in *original_values*.  Designed to emit
+   a warning if a test leaves running threads in the background.
+
+
+.. function:: threading_setup()
+
+   Return current thread count and copy of dangling threads.
+
+
+.. function:: wait_threads_exit(timeout=None)
+
+   Context manager to wait until all threads created in the ``with`` statement
+   exit.
+
+
+.. function:: catch_threading_exception()
+
+   Context manager catching :class:`threading.Thread` exception using
+   :func:`threading.excepthook`.
+
+   Attributes set when an exception is catched:
+
+   * ``exc_type``
+   * ``exc_value``
+   * ``exc_traceback``
+   * ``thread``
+
+   See :func:`threading.excepthook` documentation.
+
+   These attributes are deleted at the context manager exit.
+
+   Usage::
+
+       with threading_helper.catch_threading_exception() as cm:
+           # code spawning a thread which raises an exception
+           ...
+
+           # check the thread exception, use cm attributes:
+           # exc_type, exc_value, exc_traceback, thread
+           ...
+
+       # exc_type, exc_value, exc_traceback, thread attributes of cm no longer
+       # exists at this point
+       # (to avoid reference cycles)
+
+   .. versionadded:: 3.8
diff --git a/Lib/test/_test_multiprocessing.py b/Lib/test/_test_multiprocessing.py
index 155a8276e7507..bbba2b45e5f03 100644
--- a/Lib/test/_test_multiprocessing.py
+++ b/Lib/test/_test_multiprocessing.py
@@ -27,6 +27,7 @@
 import test.support.script_helper
 from test import support
 from test.support import socket_helper
+from test.support import threading_helper
 
 
 # Skip tests if _multiprocessing wasn't built.
@@ -81,7 +82,7 @@ def close_queue(queue):
 def join_process(process):
     # Since multiprocessing.Process has the same API than threading.Thread
     # (join() and is_alive(), the support function can be reused
-    support.join_thread(process)
+    threading_helper.join_thread(process)
 
 
 if os.name == "posix":
@@ -4234,7 +4235,7 @@ def make_finalizers():
             gc.set_threshold(5, 5, 5)
             threads = [threading.Thread(target=run_finalizers),
                        threading.Thread(target=make_finalizers)]
-            with test.support.start_threads(threads):
+            with threading_helper.start_threads(threads):
                 time.sleep(4.0)  # Wait a bit to trigger race condition
                 finish = True
             if exc is not None:
diff --git a/Lib/test/fork_wait.py b/Lib/test/fork_wait.py
index 249b5e9607329..4d3dbd8e83f5a 100644
--- a/Lib/test/fork_wait.py
+++ b/Lib/test/fork_wait.py
@@ -12,6 +12,7 @@
 import os, sys, time, unittest
 import threading
 from test import support
+from test.support import threading_helper
 
 
 LONGSLEEP = 2
@@ -21,7 +22,7 @@
 class ForkWait(unittest.TestCase):
 
     def setUp(self):
-        self._threading_key = support.threading_setup()
+        self._threading_key = threading_helper.threading_setup()
         self.alive = {}
         self.stop = 0
         self.threads = []
@@ -33,7 +34,7 @@ def tearDown(self):
             thread.join()
         thread = None
         self.threads.clear()
-        support.threading_cleanup(*self._threading_key)
+        threading_helper.threading_cleanup(*self._threading_key)
 
     def f(self, id):
         while not self.stop:
diff --git a/Lib/test/lock_tests.py b/Lib/test/lock_tests.py
index b3975254c79b5..d69bcc9496843 100644
--- a/Lib/test/lock_tests.py
+++ b/Lib/test/lock_tests.py
@@ -11,6 +11,7 @@
 import weakref
 
 from test import support
+from test.support import threading_helper
 
 
 requires_fork = unittest.skipUnless(hasattr(os, 'fork'),
@@ -37,7 +38,7 @@ def __init__(self, f, n, wait_before_exit=False):
         self.started = []
         self.finished = []
         self._can_exit = not wait_before_exit
-        self.wait_thread = support.wait_threads_exit()
+        self.wait_thread = threading_helper.wait_threads_exit()
         self.wait_thread.__enter__()
 
         def task():
@@ -73,10 +74,10 @@ def do_finish(self):
 
 class BaseTestCase(unittest.TestCase):
     def setUp(self):
-        self._threads = support.threading_setup()
+        self._threads = threading_helper.threading_setup()
 
     def tearDown(self):
-        support.threading_cleanup(*self._threads)
+        threading_helper.threading_cleanup(*self._threads)
         support.reap_children()
 
     def assertTimeout(self, actual, expected):
@@ -239,7 +240,7 @@ def f():
             lock.acquire()
             phase.append(None)
 
-        with support.wait_threads_exit():
+        with threading_helper.wait_threads_exit():
             start_new_thread(f, ())
             while len(phase) == 0:
                 _wait()
diff --git a/Lib/test/pickletester.py b/Lib/test/pickletester.py
index 6ef4c8989f55b..ca566a28d60a9 100644
--- a/Lib/test/pickletester.py
+++ b/Lib/test/pickletester.py
@@ -29,8 +29,9 @@
 from test import support
 from test.support import (
     TestFailed, TESTFN, run_with_locale, no_tracing,
-    _2G, _4G, bigmemtest, reap_threads, forget,
+    _2G, _4G, bigmemtest, forget,
     )
+from test.support import threading_helper
 
 from pickle import bytes_types
 
@@ -1350,7 +1351,7 @@ def test_truncated_data(self):
         for p in badpickles:
             self.check_unpickling_error(self.truncated_errors, p)
 
-    @reap_threads
+    @threading_helper.reap_threads
     def test_unpickle_module_race(self):
         # https://bugs.python.org/issue34572
         locker_module = dedent("""
diff --git a/Lib/test/support/__init__.py b/Lib/test/support/__init__.py
index 8dee5b9dcc7ab..e894545f87e42 100644
--- a/Lib/test/support/__init__.py
+++ b/Lib/test/support/__init__.py
@@ -19,8 +19,6 @@
 import subprocess
 import sys
 import sysconfig
-import _thread
-import threading
 import time
 import types
 import unittest
@@ -62,8 +60,6 @@
     "open_urlresource",
     # processes
     'temp_umask', "reap_children",
-    # threads
-    "threading_setup", "threading_cleanup", "reap_threads", "start_threads",
     # miscellaneous
     "check_warnings", "check_no_resource_warning", "check_no_warnings",
     "EnvironmentVarGuard",
@@ -1991,120 +1987,14 @@ def modules_cleanup(oldmodules):
     # Implicitly imported *real* modules should be left alone (see issue 10556).
     sys.modules.update(oldmodules)
 
-#=======================================================================
-# Threading support to prevent reporting refleaks when running regrtest.py -R
-
 # Flag used by saved_test_environment of test.libregrtest.save_env,
 # to check if a test modified the environment. The flag should be set to False
 # before running a new test.
 #
-# For example, threading_cleanup() sets the flag is the function fails
+# For example, threading_helper.threading_cleanup() sets the flag is the function fails
 # to cleanup threads.
 environment_altered = False
 
-# NOTE: we use thread._count() rather than threading.enumerate() (or the
-# moral equivalent thereof) because a threading.Thread object is still alive
-# until its __bootstrap() method has returned, even after it has been
-# unregistered from the threading module.
-# thread._count(), on the other hand, only gets decremented *after* the
-# __bootstrap() method has returned, which gives us reliable reference counts
-# at the end of a test run.
-
-def threading_setup():
-    return _thread._count(), threading._dangling.copy()
-
-def threading_cleanup(*original_values):
-    global environment_altered
-
-    _MAX_COUNT = 100
-
-    for count in range(_MAX_COUNT):
-        values = _thread._count(), threading._dangling
-        if values == original_values:
-            break
-
-        if not count:
-            # Display a warning at the first iteration
-            environment_altered = True
-            dangling_threads = values[1]
-            print_warning(f"threading_cleanup() failed to cleanup "
-                          f"{values[0] - original_values[0]} threads "
-                          f"(count: {values[0]}, "
-                          f"dangling: {len(dangling_threads)})")
-            for thread in dangling_threads:
-                print_warning(f"Dangling thread: {thread!r}")
-
-            # Don't hold references to threads
-            dangling_threads = None
-        values = None
-
-        time.sleep(0.01)
-        gc_collect()
-
-
-def reap_threads(func):
-    """Use this function when threads are being used.  This will
-    ensure that the threads are cleaned up even when the test fails.
-    """
-    @functools.wraps(func)
-    def decorator(*args):
-        key = threading_setup()
-        try:
-            return func(*args)
-        finally:
-            threading_cleanup(*key)
-    return decorator
-
-
- at contextlib.contextmanager
-def wait_threads_exit(timeout=None):
-    """
-    bpo-31234: Context manager to wait until all threads created in the with
-    statement exit.
-
-    Use _thread.count() to check if threads exited. Indirectly, wait until
-    threads exit the internal t_bootstrap() C function of the _thread module.
-
-    threading_setup() and threading_cleanup() are designed to emit a warning
-    if a test leaves running threads in the background. This context manager
-    is designed to cleanup threads started by the _thread.start_new_thread()
-    which doesn't allow to wait for thread exit, whereas thread.Thread has a
-    join() method.
-    """
-    if timeout is None:
-        timeout = SHORT_TIMEOUT
-    old_count = _thread._count()
-    try:
-        yield
-    finally:
-        start_time = time.monotonic()
-        deadline = start_time + timeout
-        while True:
-            count = _thread._count()
-            if count <= old_count:
-                break
-            if time.monotonic() > deadline:
-                dt = time.monotonic() - start_time
-                msg = (f"wait_threads() failed to cleanup {count - old_count} "
-                       f"threads after {dt:.1f} seconds "
-                       f"(count: {count}, old count: {old_count})")
-                raise AssertionError(msg)
-            time.sleep(0.010)
-            gc_collect()
-
-
-def join_thread(thread, timeout=None):
-    """Join a thread. Raise an AssertionError if the thread is still alive
-    after timeout seconds.
-    """
-    if timeout is None:
-        timeout = SHORT_TIMEOUT
-    thread.join(timeout)
-    if thread.is_alive():
-        msg = f"failed to join the thread in {timeout:.1f} seconds"
-        raise AssertionError(msg)
-
-
 def reap_children():
     """Use this function at the end of test_main() whenever sub-processes
     are started.  This will help ensure that no extra children (zombies)
@@ -2133,43 +2023,6 @@ def reap_children():
         environment_altered = True
 
 
- at contextlib.contextmanager
-def start_threads(threads, unlock=None):
-    import faulthandler
-    threads = list(threads)
-    started = []
-    try:
-        try:
-            for t in threads:
-                t.start()
-                started.append(t)
-        except:
-            if verbose:
-                print("Can't start %d threads, only %d threads started" %
-                      (len(threads), len(started)))
-            raise
-        yield
-    finally:
-        try:
-            if unlock:
-                unlock()
-            endtime = starttime = time.monotonic()
-            for timeout in range(1, 16):
-                endtime += 60
-                for t in started:
-                    t.join(max(endtime - time.monotonic(), 0.01))
-                started = [t for t in started if t.is_alive()]
-                if not started:
-                    break
-                if verbose:
-                    print('Unable to join %d threads during a period of '
-                          '%d minutes' % (len(started), timeout))
-        finally:
-            started = [t for t in started if t.is_alive()]
-            if started:
-                faulthandler.dump_traceback(sys.stdout)
-                raise AssertionError('Unable to join %d threads' % len(started))
-
 @contextlib.contextmanager
 def swap_attr(obj, attr, new_val):
     """Temporary swap out an attribute with a new object.
@@ -3023,63 +2876,6 @@ def __exit__(self, *exc_info):
         del self.unraisable
 
 
-class catch_threading_exception:
-    """
-    Context manager catching threading.Thread exception using
-    threading.excepthook.
-
-    Attributes set when an exception is catched:
-
-    * exc_type
-    * exc_value
-    * exc_traceback
-    * thread
-
-    See threading.excepthook() documentation for these attributes.
-
-    These attributes are deleted at the context manager exit.
-
-    Usage:
-
-        with support.catch_threading_exception() as cm:
-            # code spawning a thread which raises an exception
-            ...
-
-            # check the thread exception, use cm attributes:
-            # exc_type, exc_value, exc_traceback, thread
-            ...
-
-        # exc_type, exc_value, exc_traceback, thread attributes of cm no longer
-        # exists at this point
-        # (to avoid reference cycles)
-    """
-
-    def __init__(self):
-        self.exc_type = None
-        self.exc_value = None
-        self.exc_traceback = None
-        self.thread = None
-        self._old_hook = None
-
-    def _hook(self, args):
-        self.exc_type = args.exc_type
-        self.exc_value = args.exc_value
-        self.exc_traceback = args.exc_traceback
-        self.thread = args.thread
-
-    def __enter__(self):
-        self._old_hook = threading.excepthook
-        threading.excepthook = self._hook
-        return self
-
-    def __exit__(self, *exc_info):
-        threading.excepthook = self._old_hook
-        del self.exc_type
-        del self.exc_value
-        del self.exc_traceback
-        del self.thread
-
-
 def wait_process(pid, *, exitcode, timeout=None):
     """
     Wait until process pid completes and check that the process exit code is
diff --git a/Lib/test/support/threading_helper.py b/Lib/test/support/threading_helper.py
new file mode 100644
index 0000000000000..96f7b3fcebfac
--- /dev/null
+++ b/Lib/test/support/threading_helper.py
@@ -0,0 +1,208 @@
+import contextlib
+import functools
+import _thread
+import threading
+import time
+
+from test import support
+
+
+#=======================================================================
+# Threading support to prevent reporting refleaks when running regrtest.py -R
+
+# NOTE: we use thread._count() rather than threading.enumerate() (or the
+# moral equivalent thereof) because a threading.Thread object is still alive
+# until its __bootstrap() method has returned, even after it has been
+# unregistered from the threading module.
+# thread._count(), on the other hand, only gets decremented *after* the
+# __bootstrap() method has returned, which gives us reliable reference counts
+# at the end of a test run.
+
+
+def threading_setup():
+    return _thread._count(), threading._dangling.copy()
+
+
+def threading_cleanup(*original_values):
+    _MAX_COUNT = 100
+
+    for count in range(_MAX_COUNT):
+        values = _thread._count(), threading._dangling
+        if values == original_values:
+            break
+
+        if not count:
+            # Display a warning at the first iteration
+            support.environment_altered = True
+            dangling_threads = values[1]
+            support.print_warning(f"threading_cleanup() failed to cleanup "
+                                  f"{values[0] - original_values[0]} threads "
+                                  f"(count: {values[0]}, "
+                                  f"dangling: {len(dangling_threads)})")
+            for thread in dangling_threads:
+                support.print_warning(f"Dangling thread: {thread!r}")
+
+            # Don't hold references to threads
+            dangling_threads = None
+        values = None
+
+        time.sleep(0.01)
+        gc_collect()
+
+
+def reap_threads(func):
+    """Use this function when threads are being used.  This will
+    ensure that the threads are cleaned up even when the test fails.
+    """
+    @functools.wraps(func)
+    def decorator(*args):
+        key = threading_setup()
+        try:
+            return func(*args)
+        finally:
+            threading_cleanup(*key)
+    return decorator
+
+
+ at contextlib.contextmanager
+def wait_threads_exit(timeout=None):
+    """
+    bpo-31234: Context manager to wait until all threads created in the with
+    statement exit.
+
+    Use _thread.count() to check if threads exited. Indirectly, wait until
+    threads exit the internal t_bootstrap() C function of the _thread module.
+
+    threading_setup() and threading_cleanup() are designed to emit a warning
+    if a test leaves running threads in the background. This context manager
+    is designed to cleanup threads started by the _thread.start_new_thread()
+    which doesn't allow to wait for thread exit, whereas thread.Thread has a
+    join() method.
+    """
+    if timeout is None:
+        timeout = support.SHORT_TIMEOUT
+    old_count = _thread._count()
+    try:
+        yield
+    finally:
+        start_time = time.monotonic()
+        deadline = start_time + timeout
+        while True:
+            count = _thread._count()
+            if count <= old_count:
+                break
+            if time.monotonic() > deadline:
+                dt = time.monotonic() - start_time
+                msg = (f"wait_threads() failed to cleanup {count - old_count} "
+                       f"threads after {dt:.1f} seconds "
+                       f"(count: {count}, old count: {old_count})")
+                raise AssertionError(msg)
+            time.sleep(0.010)
+            gc_collect()
+
+
+def join_thread(thread, timeout=None):
+    """Join a thread. Raise an AssertionError if the thread is still alive
+    after timeout seconds.
+    """
+    if timeout is None:
+        timeout = support.SHORT_TIMEOUT
+    thread.join(timeout)
+    if thread.is_alive():
+        msg = f"failed to join the thread in {timeout:.1f} seconds"
+        raise AssertionError(msg)
+
+
+ at contextlib.contextmanager
+def start_threads(threads, unlock=None):
+    import faulthandler
+    threads = list(threads)
+    started = []
+    try:
+        try:
+            for t in threads:
+                t.start()
+                started.append(t)
+        except:
+            if verbose:
+                print("Can't start %d threads, only %d threads started" %
+                      (len(threads), len(started)))
+            raise
+        yield
+    finally:
+        try:
+            if unlock:
+                unlock()
+            endtime = starttime = time.monotonic()
+            for timeout in range(1, 16):
+                endtime += 60
+                for t in started:
+                    t.join(max(endtime - time.monotonic(), 0.01))
+                started = [t for t in started if t.is_alive()]
+                if not started:
+                    break
+                if verbose:
+                    print('Unable to join %d threads during a period of '
+                          '%d minutes' % (len(started), timeout))
+        finally:
+            started = [t for t in started if t.is_alive()]
+            if started:
+                faulthandler.dump_traceback(sys.stdout)
+                raise AssertionError('Unable to join %d threads' % len(started))
+
+
+class catch_threading_exception:
+    """
+    Context manager catching threading.Thread exception using
+    threading.excepthook.
+
+    Attributes set when an exception is catched:
+
+    * exc_type
+    * exc_value
+    * exc_traceback
+    * thread
+
+    See threading.excepthook() documentation for these attributes.
+
+    These attributes are deleted at the context manager exit.
+
+    Usage:
+
+        with threading_helper.catch_threading_exception() as cm:
+            # code spawning a thread which raises an exception
+            ...
+
+            # check the thread exception, use cm attributes:
+            # exc_type, exc_value, exc_traceback, thread
+            ...
+
+        # exc_type, exc_value, exc_traceback, thread attributes of cm no longer
+        # exists at this point
+        # (to avoid reference cycles)
+    """
+
+    def __init__(self):
+        self.exc_type = None
+        self.exc_value = None
+        self.exc_traceback = None
+        self.thread = None
+        self._old_hook = None
+
+    def _hook(self, args):
+        self.exc_type = args.exc_type
+        self.exc_value = args.exc_value
+        self.exc_traceback = args.exc_traceback
+        self.thread = args.thread
+
+    def __enter__(self):
+        self._old_hook = threading.excepthook
+        threading.excepthook = self._hook
+        return self
+
+    def __exit__(self, *exc_info):
+        threading.excepthook = self._old_hook
+        del self.exc_type
+        del self.exc_value
+        del self.exc_traceback
+        del self.thread
diff --git a/Lib/test/test_asynchat.py b/Lib/test/test_asynchat.py
index 004d368d76312..b32edddc7d550 100644
--- a/Lib/test/test_asynchat.py
+++ b/Lib/test/test_asynchat.py
@@ -2,6 +2,7 @@
 
 from test import support
 from test.support import socket_helper
+from test.support import threading_helper
 
 import asynchat
 import asyncore
@@ -103,10 +104,10 @@ class TestAsynchat(unittest.TestCase):
     usepoll = False
 
     def setUp(self):
-        self._threads = support.threading_setup()
+        self._threads = threading_helper.threading_setup()
 
     def tearDown(self):
-        support.threading_cleanup(*self._threads)
+        threading_helper.threading_cleanup(*self._threads)
 
     def line_terminator_check(self, term, server_chunk):
         event = threading.Event()
@@ -122,7 +123,7 @@ def line_terminator_check(self, term, server_chunk):
         c.push(b"I'm not dead yet!" + term)
         c.push(SERVER_QUIT)
         asyncore.loop(use_poll=self.usepoll, count=300, timeout=.01)
-        support.join_thread(s)
+        threading_helper.join_thread(s)
 
         self.assertEqual(c.contents, [b"hello world", b"I'm not dead yet!"])
 
@@ -153,7 +154,7 @@ def numeric_terminator_check(self, termlen):
         c.push(data)
         c.push(SERVER_QUIT)
         asyncore.loop(use_poll=self.usepoll, count=300, timeout=.01)
-        support.join_thread(s)
+        threading_helper.join_thread(s)
 
         self.assertEqual(c.contents, [data[:termlen]])
 
@@ -173,7 +174,7 @@ def test_none_terminator(self):
         c.push(data)
         c.push(SERVER_QUIT)
         asyncore.loop(use_poll=self.usepoll, count=300, timeout=.01)
-        support.join_thread(s)
+        threading_helper.join_thread(s)
 
         self.assertEqual(c.contents, [])
         self.assertEqual(c.buffer, data)
@@ -185,7 +186,7 @@ def test_simple_producer(self):
         p = asynchat.simple_producer(data+SERVER_QUIT, buffer_size=8)
         c.push_with_producer(p)
         asyncore.loop(use_poll=self.usepoll, count=300, timeout=.01)
-        support.join_thread(s)
+        threading_helper.join_thread(s)
 
         self.assertEqual(c.contents, [b"hello world", b"I'm not dead yet!"])
 
@@ -195,7 +196,7 @@ def test_string_producer(self):
         data = b"hello world\nI'm not dead yet!\n"
         c.push_with_producer(data+SERVER_QUIT)
         asyncore.loop(use_poll=self.usepoll, count=300, timeout=.01)
-        support.join_thread(s)
+        threading_helper.join_thread(s)
 
         self.assertEqual(c.contents, [b"hello world", b"I'm not dead yet!"])
 
@@ -206,7 +207,7 @@ def test_empty_line(self):
         c.push(b"hello world\n\nI'm not dead yet!\n")
         c.push(SERVER_QUIT)
         asyncore.loop(use_poll=self.usepoll, count=300, timeout=.01)
-        support.join_thread(s)
+        threading_helper.join_thread(s)
 
         self.assertEqual(c.contents,
                          [b"hello world", b"", b"I'm not dead yet!"])
@@ -225,7 +226,7 @@ def test_close_when_done(self):
         # where the server echoes all of its data before we can check that it
         # got any down below.
         s.start_resend_event.set()
-        support.join_thread(s)
+        threading_helper.join_thread(s)
 
         self.assertEqual(c.contents, [])
         # the server might have been able to send a byte or two back, but this
@@ -246,7 +247,7 @@ def test_push(self):
         self.assertRaises(TypeError, c.push, 'unicode')
         c.push(SERVER_QUIT)
         asyncore.loop(use_poll=self.usepoll, count=300, timeout=.01)
-        support.join_thread(s)
+        threading_helper.join_thread(s)
         self.assertEqual(c.contents, [b'bytes', b'bytes', b'bytes'])
 
 
diff --git a/Lib/test/test_asyncio/test_events.py b/Lib/test/test_asyncio/test_events.py
index 0fb361884185e..e7324d2e4811b 100644
--- a/Lib/test/test_asyncio/test_events.py
+++ b/Lib/test/test_asyncio/test_events.py
@@ -33,6 +33,7 @@
 from test.test_asyncio import utils as test_utils
 from test import support
 from test.support import socket_helper
+from test.support import threading_helper
 from test.support import ALWAYS_EQ, LARGEST, SMALLEST
 
 
@@ -706,7 +707,7 @@ def client():
         proto.transport.close()
         lsock.close()
 
-        support.join_thread(thread)
+        threading_helper.join_thread(thread)
         self.assertFalse(thread.is_alive())
         self.assertEqual(proto.state, 'CLOSED')
         self.assertEqual(proto.nbytes, len(message))
diff --git a/Lib/test/test_asyncio/utils.py b/Lib/test/test_asyncio/utils.py
index 804db9166fe7f..34da7390e1b16 100644
--- a/Lib/test/test_asyncio/utils.py
+++ b/Lib/test/test_asyncio/utils.py
@@ -34,6 +34,7 @@
 from asyncio import tasks
 from asyncio.log import logger
 from test import support
+from test.support import threading_helper
 
 
 def data_file(filename):
@@ -546,7 +547,7 @@ def unpatch_get_running_loop(self):
     def setUp(self):
         self._get_running_loop = events._get_running_loop
         events._get_running_loop = lambda: None
-        self._thread_cleanup = support.threading_setup()
+        self._thread_cleanup = threading_helper.threading_setup()
 
     def tearDown(self):
         self.unpatch_get_running_loop()
@@ -558,7 +559,7 @@ def tearDown(self):
         self.assertEqual(sys.exc_info(), (None, None, None))
 
         self.doCleanups()
-        support.threading_cleanup(*self._thread_cleanup)
+        threading_helper.threading_cleanup(*self._thread_cleanup)
         support.reap_children()
 
 
diff --git a/Lib/test/test_asyncore.py b/Lib/test/test_asyncore.py
index 3c3abe4191788..2cee6fb2e996a 100644
--- a/Lib/test/test_asyncore.py
+++ b/Lib/test/test_asyncore.py
@@ -11,6 +11,7 @@
 
 from test import support
 from test.support import socket_helper
+from test.support import threading_helper
 from io import BytesIO
 
 if support.PGO:
@@ -323,7 +324,7 @@ def setUp(self):
     def tearDown(self):
         asyncore.close_all()
 
-    @support.reap_threads
+    @threading_helper.reap_threads
     def test_send(self):
         evt = threading.Event()
         sock = socket.socket()
@@ -360,7 +361,7 @@ def test_send(self):
 
             self.assertEqual(cap.getvalue(), data*2)
         finally:
-            support.join_thread(t)
+            threading_helper.join_thread(t)
 
 
 @unittest.skipUnless(hasattr(asyncore, 'file_wrapper'),
@@ -766,7 +767,7 @@ def test_set_reuse_addr(self):
                 self.assertTrue(s.socket.getsockopt(socket.SOL_SOCKET,
                                                      socket.SO_REUSEADDR))
 
-    @support.reap_threads
+    @threading_helper.reap_threads
     def test_quick_connect(self):
         # see: http://bugs.python.org/issue10340
         if self.family not in (socket.AF_INET, getattr(socket, "AF_INET6", object())):
@@ -788,7 +789,7 @@ def test_quick_connect(self):
                 except OSError:
                     pass
         finally:
-            support.join_thread(t)
+            threading_helper.join_thread(t)
 
 class TestAPI_UseIPv4Sockets(BaseTestAPI):
     family = socket.AF_INET
diff --git a/Lib/test/test_bz2.py b/Lib/test/test_bz2.py
index 78b95d88faafa..91ccff2d0c07f 100644
--- a/Lib/test/test_bz2.py
+++ b/Lib/test/test_bz2.py
@@ -12,6 +12,7 @@
 import shutil
 import subprocess
 import threading
+from test.support import threading_helper
 from test.support import unlink
 import _compression
 import sys
@@ -502,7 +503,7 @@ def comp():
                 for i in range(5):
                     f.write(data)
             threads = [threading.Thread(target=comp) for i in range(nthreads)]
-            with support.start_threads(threads):
+            with threading_helper.start_threads(threads):
                 pass
 
     def testMixedIterationAndReads(self):
diff --git a/Lib/test/test_capi.py b/Lib/test/test_capi.py
index 5c7526aa7ec29..44693b8fdd717 100644
--- a/Lib/test/test_capi.py
+++ b/Lib/test/test_capi.py
@@ -17,6 +17,7 @@
 import importlib.util
 from test import support
 from test.support import MISSING_C_DOCSTRINGS
+from test.support import threading_helper
 from test.support.script_helper import assert_python_failure, assert_python_ok
 try:
     import _posixsubprocess
@@ -575,7 +576,7 @@ class foo(object):pass
         threads = [threading.Thread(target=self.pendingcalls_thread,
                                     args=(context,))
                    for i in range(context.nThreads)]
-        with support.start_threads(threads):
+        with threading_helper.start_threads(threads):
             self.pendingcalls_wait(context.l, n, context)
 
     def pendingcalls_thread(self, context):
@@ -634,7 +635,7 @@ def test_mutate_exception(self):
 
 class TestThreadState(unittest.TestCase):
 
-    @support.reap_threads
+    @threading_helper.reap_threads
     def test_thread_state(self):
         # some extra thread-state tests driven via _testcapi
         def target():
diff --git a/Lib/test/test_concurrent_futures.py b/Lib/test/test_concurrent_futures.py
index 40597ffee7378..3b74949a5f61d 100644
--- a/Lib/test/test_concurrent_futures.py
+++ b/Lib/test/test_concurrent_futures.py
@@ -1,4 +1,5 @@
 from test import support
+from test.support import threading_helper
 
 # Skip tests if _multiprocessing wasn't built.
 support.import_module('_multiprocessing')
@@ -100,11 +101,11 @@ def make_dummy_object(_):
 
 class BaseTestCase(unittest.TestCase):
     def setUp(self):
-        self._thread_key = support.threading_setup()
+        self._thread_key = threading_helper.threading_setup()
 
     def tearDown(self):
         support.reap_children()
-        support.threading_cleanup(*self._thread_key)
+        threading_helper.threading_cleanup(*self._thread_key)
 
 
 class ExecutorMixin:
@@ -1496,11 +1497,11 @@ def test_multiple_set_exception(self):
 
 def setUpModule():
     global _threads_key
-    _threads_key = support.threading_setup()
+    _threads_key = threading_helper.threading_setup()
 
 
 def tearDownModule():
-    support.threading_cleanup(*_threads_key)
+    threading_helper.threading_cleanup(*_threads_key)
     multiprocessing.util._cleanup_tests()
 
 
diff --git a/Lib/test/test_email/test_email.py b/Lib/test/test_email/test_email.py
index 59eabb0092194..1d28e26dec681 100644
--- a/Lib/test/test_email/test_email.py
+++ b/Lib/test/test_email/test_email.py
@@ -37,7 +37,8 @@
 from email import base64mime
 from email import quoprimime
 
-from test.support import unlink, start_threads
+from test.support import threading_helper
+from test.support import unlink
 from test.test_email import openfile, TestEmailBase
 
 # These imports are documented to work, but we are testing them using a
@@ -3241,7 +3242,7 @@ def run(self):
                     append(make_msgid(domain='testdomain-string'))
 
         threads = [MsgidsThread() for i in range(5)]
-        with start_threads(threads):
+        with threading_helper.start_threads(threads):
             pass
         all_ids = sum([t.msgids for t in threads], [])
         self.assertEqual(len(set(all_ids)), len(all_ids))
diff --git a/Lib/test/test_enum.py b/Lib/test/test_enum.py
index 1df0313da0a7e..e7bad62406773 100644
--- a/Lib/test/test_enum.py
+++ b/Lib/test/test_enum.py
@@ -10,6 +10,7 @@
 from pickle import dumps, loads, PicklingError, HIGHEST_PROTOCOL
 from test import support
 from test.support import ALWAYS_EQ
+from test.support import threading_helper
 from datetime import timedelta
 
 
@@ -2333,7 +2334,7 @@ class Color(StrMixin, AllMixin, Flag):
         self.assertEqual(Color.ALL.value, 7)
         self.assertEqual(str(Color.BLUE), 'blue')
 
-    @support.reap_threads
+    @threading_helper.reap_threads
     def test_unique_composite(self):
         # override __eq__ to be identity only
         class TestFlag(Flag):
@@ -2363,7 +2364,7 @@ def cycle_enum():
                 threading.Thread(target=cycle_enum)
                 for _ in range(8)
                 ]
-        with support.start_threads(threads):
+        with threading_helper.start_threads(threads):
             pass
         # check that only 248 members were created
         self.assertFalse(
@@ -2751,7 +2752,7 @@ class Color(StrMixin, AllMixin, IntFlag):
         self.assertEqual(Color.ALL.value, 7)
         self.assertEqual(str(Color.BLUE), 'blue')
 
-    @support.reap_threads
+    @threading_helper.reap_threads
     def test_unique_composite(self):
         # override __eq__ to be identity only
         class TestFlag(IntFlag):
@@ -2781,7 +2782,7 @@ def cycle_enum():
                 threading.Thread(target=cycle_enum)
                 for _ in range(8)
                 ]
-        with support.start_threads(threads):
+        with threading_helper.start_threads(threads):
             pass
         # check that only 248 members were created
         self.assertFalse(
diff --git a/Lib/test/test_ftplib.py b/Lib/test/test_ftplib.py
index e424076d7d317..cb43573318b6a 100644
--- a/Lib/test/test_ftplib.py
+++ b/Lib/test/test_ftplib.py
@@ -19,6 +19,7 @@
 
 from unittest import TestCase, skipUnless
 from test import support
+from test.support import threading_helper
 from test.support import socket_helper
 from test.support.socket_helper import HOST, HOSTv6
 
@@ -1117,11 +1118,11 @@ def test_main():
              TestTLS_FTPClassMixin, TestTLS_FTPClass,
              MiscTestCase]
 
-    thread_info = support.threading_setup()
+    thread_info = threading_helper.threading_setup()
     try:
         support.run_unittest(*tests)
     finally:
-        support.threading_cleanup(*thread_info)
+        threading_helper.threading_cleanup(*thread_info)
 
 
 if __name__ == '__main__':
diff --git a/Lib/test/test_functools.py b/Lib/test/test_functools.py
index e122fe0b33340..72b7765853bc0 100644
--- a/Lib/test/test_functools.py
+++ b/Lib/test/test_functools.py
@@ -19,6 +19,7 @@
 from weakref import proxy
 import contextlib
 
+from test.support import threading_helper
 from test.support.script_helper import assert_python_ok
 
 import functools
@@ -1798,7 +1799,7 @@ def clear():
             # create n threads in order to fill cache
             threads = [threading.Thread(target=full, args=[k])
                        for k in range(n)]
-            with support.start_threads(threads):
+            with threading_helper.start_threads(threads):
                 start.set()
 
             hits, misses, maxsize, currsize = f.cache_info()
@@ -1816,7 +1817,7 @@ def clear():
             threads += [threading.Thread(target=full, args=[k])
                         for k in range(n)]
             start.clear()
-            with support.start_threads(threads):
+            with threading_helper.start_threads(threads):
                 start.set()
         finally:
             sys.setswitchinterval(orig_si)
@@ -1838,7 +1839,7 @@ def test():
                 self.assertEqual(f(i), 3 * i)
                 stop.wait(10)
         threads = [threading.Thread(target=test) for k in range(n)]
-        with support.start_threads(threads):
+        with threading_helper.start_threads(threads):
             for i in range(m):
                 start.wait(10)
                 stop.reset()
@@ -1858,7 +1859,7 @@ def test(i, x):
                 self.assertEqual(f(x), 3 * x, i)
         threads = [threading.Thread(target=test, args=(i, v))
                    for i, v in enumerate([1, 2, 2, 3, 2])]
-        with support.start_threads(threads):
+        with threading_helper.start_threads(threads):
             pass
 
     def test_need_for_rlock(self):
@@ -2792,7 +2793,7 @@ def test_threaded(self):
                 threading.Thread(target=lambda: item.cost)
                 for k in range(num_threads)
             ]
-            with support.start_threads(threads):
+            with threading_helper.start_threads(threads):
                 go.set()
         finally:
             sys.setswitchinterval(orig_si)
diff --git a/Lib/test/test_gc.py b/Lib/test/test_gc.py
index acb6391944bc0..c82970827c672 100644
--- a/Lib/test/test_gc.py
+++ b/Lib/test/test_gc.py
@@ -1,10 +1,10 @@
 import unittest
 import unittest.mock
 from test.support import (verbose, refcount_test, run_unittest,
-                          cpython_only, start_threads,
-                          temp_dir, TESTFN, unlink,
+                          cpython_only, temp_dir, TESTFN, unlink,
                           import_module)
 from test.support.script_helper import assert_python_ok, make_script
+from test.support import threading_helper
 
 import gc
 import sys
@@ -415,7 +415,7 @@ def run_thread():
             for i in range(N_THREADS):
                 t = threading.Thread(target=run_thread)
                 threads.append(t)
-            with start_threads(threads, lambda: exit.append(1)):
+            with threading_helper.start_threads(threads, lambda: exit.append(1)):
                 time.sleep(1.0)
         finally:
             sys.setswitchinterval(old_switchinterval)
diff --git a/Lib/test/test_hashlib.py b/Lib/test/test_hashlib.py
index 6088307f8410b..2f79244748e68 100644
--- a/Lib/test/test_hashlib.py
+++ b/Lib/test/test_hashlib.py
@@ -19,6 +19,7 @@
 import warnings
 from test import support
 from test.support import _4G, bigmemtest, import_fresh_module
+from test.support import threading_helper
 from http.client import HTTPException
 
 # Were we compiled --with-pydebug or with #define Py_DEBUG?
@@ -870,7 +871,7 @@ def test_gil(self):
             '1cfceca95989f51f658e3f3ffe7f1cd43726c9e088c13ee10b46f57cef135b94'
         )
 
-    @support.reap_threads
+    @threading_helper.reap_threads
     def test_threaded_hashing(self):
         # Updating the same hash object from several threads at once
         # using data chunk sizes containing the same byte sequences.
diff --git a/Lib/test/test_httpservers.py b/Lib/test/test_httpservers.py
index c442f5571a868..71a0511e53a72 100644
--- a/Lib/test/test_httpservers.py
+++ b/Lib/test/test_httpservers.py
@@ -30,6 +30,7 @@
 
 import unittest
 from test import support
+from test.support import threading_helper
 
 
 class NoLogRequestHandler:
@@ -64,7 +65,7 @@ def stop(self):
 
 class BaseTestCase(unittest.TestCase):
     def setUp(self):
-        self._threads = support.threading_setup()
+        self._threads = threading_helper.threading_setup()
         os.environ = support.EnvironmentVarGuard()
         self.server_started = threading.Event()
         self.thread = TestServerThread(self, self.request_handler)
@@ -75,7 +76,7 @@ def tearDown(self):
         self.thread.stop()
         self.thread = None
         os.environ.__exit__()
-        support.threading_cleanup(*self._threads)
+        threading_helper.threading_cleanup(*self._threads)
 
     def request(self, uri, method='GET', body=None, headers={}):
         self.connection = http.client.HTTPConnection(self.HOST, self.PORT)
diff --git a/Lib/test/test_imaplib.py b/Lib/test/test_imaplib.py
index d1e3550868059..0fcc1fb99a289 100644
--- a/Lib/test/test_imaplib.py
+++ b/Lib/test/test_imaplib.py
@@ -10,9 +10,10 @@
 import threading
 import socket
 
-from test.support import (reap_threads, verbose,
+from test.support import (verbose,
                           run_with_tz, run_with_locale, cpython_only)
 from test.support import hashlib_helper
+from test.support import threading_helper
 import unittest
 from unittest import mock
 from datetime import datetime, timezone, timedelta
@@ -252,7 +253,7 @@ def _cleanup(self):
         # cleanup the server
         self.server.shutdown()
         self.server.server_close()
-        support.join_thread(self.thread)
+        threading_helper.join_thread(self.thread)
         # Explicitly clear the attribute to prevent dangling thread
         self.thread = None
 
@@ -641,13 +642,13 @@ def reaped_pair(self, hdlr):
             finally:
                 client.logout()
 
-    @reap_threads
+    @threading_helper.reap_threads
     def test_connect(self):
         with self.reaped_server(SimpleIMAPHandler) as server:
             client = self.imap_class(*server.server_address)
             client.shutdown()
 
-    @reap_threads
+    @threading_helper.reap_threads
     def test_bracket_flags(self):
 
         # This violates RFC 3501, which disallows ']' characters in tag names,
@@ -696,7 +697,7 @@ def cmd_STORE(self, tag, args):
             typ, [data] = client.response('PERMANENTFLAGS')
             self.assertIn(b'[test]', data)
 
-    @reap_threads
+    @threading_helper.reap_threads
     def test_issue5949(self):
 
         class EOFHandler(socketserver.StreamRequestHandler):
@@ -708,7 +709,7 @@ def handle(self):
             self.assertRaises(imaplib.IMAP4.abort,
                               self.imap_class, *server.server_address)
 
-    @reap_threads
+    @threading_helper.reap_threads
     def test_line_termination(self):
 
         class BadNewlineHandler(SimpleIMAPHandler):
@@ -732,7 +733,7 @@ def cmd_AUTHENTICATE(self, tag, args):
             self.server.response = yield
             self._send_tagged(tag, 'OK', 'FAKEAUTH successful')
 
-    @reap_threads
+    @threading_helper.reap_threads
     def test_enable_raises_error_if_not_AUTH(self):
         with self.reaped_pair(self.UTF8Server) as (server, client):
             self.assertFalse(client.utf8_enabled)
@@ -741,14 +742,14 @@ def test_enable_raises_error_if_not_AUTH(self):
 
     # XXX Also need a test that enable after SELECT raises an error.
 
-    @reap_threads
+    @threading_helper.reap_threads
     def test_enable_raises_error_if_no_capability(self):
         class NoEnableServer(self.UTF8Server):
             capabilities = 'AUTH'
         with self.reaped_pair(NoEnableServer) as (server, client):
             self.assertRaises(imaplib.IMAP4.error, client.enable, 'foo')
 
-    @reap_threads
+    @threading_helper.reap_threads
     def test_enable_UTF8_raises_error_if_not_supported(self):
         class NonUTF8Server(SimpleIMAPHandler):
             pass
@@ -759,7 +760,7 @@ class NonUTF8Server(SimpleIMAPHandler):
                 client.enable('UTF8=ACCEPT')
                 pass
 
-    @reap_threads
+    @threading_helper.reap_threads
     def test_enable_UTF8_True_append(self):
 
         class UTF8AppendServer(self.UTF8Server):
@@ -789,7 +790,7 @@ def cmd_APPEND(self, tag, args):
     # XXX also need a test that makes sure that the Literal and Untagged_status
     # regexes uses unicode in UTF8 mode instead of the default ASCII.
 
-    @reap_threads
+    @threading_helper.reap_threads
     def test_search_disallows_charset_in_utf8_mode(self):
         with self.reaped_pair(self.UTF8Server) as (server, client):
             typ, _ = client.authenticate('MYAUTH', lambda x: b'fake')
@@ -799,7 +800,7 @@ def test_search_disallows_charset_in_utf8_mode(self):
             self.assertTrue(client.utf8_enabled)
             self.assertRaises(imaplib.IMAP4.error, client.search, 'foo', 'bar')
 
-    @reap_threads
+    @threading_helper.reap_threads
     def test_bad_auth_name(self):
 
         class MyServer(SimpleIMAPHandler):
@@ -812,7 +813,7 @@ def cmd_AUTHENTICATE(self, tag, args):
             with self.assertRaises(imaplib.IMAP4.error):
                 client.authenticate('METHOD', lambda: 1)
 
-    @reap_threads
+    @threading_helper.reap_threads
     def test_invalid_authentication(self):
 
         class MyServer(SimpleIMAPHandler):
@@ -826,7 +827,7 @@ def cmd_AUTHENTICATE(self, tag, args):
             with self.assertRaises(imaplib.IMAP4.error):
                 code, data = client.authenticate('MYAUTH', lambda x: b'fake')
 
-    @reap_threads
+    @threading_helper.reap_threads
     def test_valid_authentication(self):
 
         class MyServer(SimpleIMAPHandler):
@@ -848,7 +849,7 @@ def cmd_AUTHENTICATE(self, tag, args):
             self.assertEqual(server.response,
                              b'ZmFrZQ==\r\n')  # b64 encoded 'fake'
 
-    @reap_threads
+    @threading_helper.reap_threads
     @hashlib_helper.requires_hashdigest('md5')
     def test_login_cram_md5(self):
 
@@ -877,7 +878,7 @@ def cmd_AUTHENTICATE(self, tag, args):
             self.assertEqual(ret, "OK")
 
 
-    @reap_threads
+    @threading_helper.reap_threads
     def test_aborted_authentication(self):
 
         class MyServer(SimpleIMAPHandler):
@@ -906,14 +907,14 @@ def handle(self):
             self.assertRaises(imaplib.IMAP4.error,
                               self.imap_class, *server.server_address)
 
-    @reap_threads
+    @threading_helper.reap_threads
     def test_simple_with_statement(self):
         # simplest call
         with self.reaped_server(SimpleIMAPHandler) as server:
             with self.imap_class(*server.server_address):
                 pass
 
-    @reap_threads
+    @threading_helper.reap_threads
     def test_with_statement(self):
         with self.reaped_server(SimpleIMAPHandler) as server:
             with self.imap_class(*server.server_address) as imap:
@@ -921,7 +922,7 @@ def test_with_statement(self):
                 self.assertEqual(server.logged, 'user')
             self.assertIsNone(server.logged)
 
-    @reap_threads
+    @threading_helper.reap_threads
     def test_with_statement_logout(self):
         # what happens if already logout in the block?
         with self.reaped_server(SimpleIMAPHandler) as server:
@@ -938,7 +939,7 @@ class ThreadedNetworkedTestsSSL(ThreadedNetworkedTests):
     server_class = SecureTCPServer
     imap_class = IMAP4_SSL
 
-    @reap_threads
+    @threading_helper.reap_threads
     def test_ssl_verified(self):
         ssl_context = ssl.SSLContext(ssl.PROTOCOL_TLS_CLIENT)
         ssl_context.load_verify_locations(CAFILE)
diff --git a/Lib/test/test_import/__init__.py b/Lib/test/test_import/__init__.py
index d50befc030a48..060d145970ee9 100644
--- a/Lib/test/test_import/__init__.py
+++ b/Lib/test/test_import/__init__.py
@@ -25,6 +25,7 @@
     unlink, unload, cpython_only, TESTFN_UNENCODABLE,
     temp_dir, DirsOnSysPath)
 from test.support import script_helper
+from test.support import threading_helper
 from test.test_importlib.util import uncache
 from types import ModuleType
 
@@ -459,7 +460,7 @@ def run():
                 event = threading.Event()
                 threads = [threading.Thread(target=run) for x in range(2)]
                 try:
-                    with test.support.start_threads(threads, event.set):
+                    with threading_helper.start_threads(threads, event.set):
                         time.sleep(0)
                 finally:
                     sys.modules.pop('package', None)
diff --git a/Lib/test/test_importlib/test_locks.py b/Lib/test/test_importlib/test_locks.py
index 21794d911ef69..0e94ce91801d6 100644
--- a/Lib/test/test_importlib/test_locks.py
+++ b/Lib/test/test_importlib/test_locks.py
@@ -7,6 +7,7 @@
 import weakref
 
 from test import support
+from test.support import threading_helper
 from test import lock_tests
 
 
@@ -138,7 +139,7 @@ def test_all_locks(self):
  ) = test_util.test_both(LifetimeTests, init=init)
 
 
- at support.reap_threads
+ at threading_helper.reap_threads
 def test_main():
     support.run_unittest(Frozen_ModuleLockAsRLockTests,
                          Source_ModuleLockAsRLockTests,
diff --git a/Lib/test/test_importlib/test_threaded_import.py b/Lib/test/test_importlib/test_threaded_import.py
index d1f64c70fac80..06da18ed396d9 100644
--- a/Lib/test/test_importlib/test_threaded_import.py
+++ b/Lib/test/test_importlib/test_threaded_import.py
@@ -15,8 +15,9 @@
 import unittest
 from unittest import mock
 from test.support import (
-    verbose, run_unittest, TESTFN, reap_threads,
-    forget, unlink, rmtree, start_threads)
+    verbose, run_unittest, TESTFN,
+    forget, unlink, rmtree)
+from test.support import threading_helper
 
 def task(N, done, done_tasks, errors):
     try:
@@ -124,9 +125,9 @@ def check_parallel_module_init(self, mock_os):
             done_tasks = []
             done.clear()
             t0 = time.monotonic()
-            with start_threads(threading.Thread(target=task,
-                                                args=(N, done, done_tasks, errors,))
-                               for i in range(N)):
+            with threading_helper.start_threads(
+                    threading.Thread(target=task, args=(N, done, done_tasks, errors,))
+                    for i in range(N)):
                 pass
             completed = done.wait(10 * 60)
             dt = time.monotonic() - t0
@@ -245,7 +246,7 @@ def target():
         del sys.modules[TESTFN]
 
 
- at reap_threads
+ at threading_helper.reap_threads
 def test_main():
     old_switchinterval = None
     try:
diff --git a/Lib/test/test_io.py b/Lib/test/test_io.py
index fe07b56880bbf..7b8511b66bf10 100644
--- a/Lib/test/test_io.py
+++ b/Lib/test/test_io.py
@@ -40,6 +40,7 @@
 from test import support
 from test.support.script_helper import (
     assert_python_ok, assert_python_failure, run_python_until_end)
+from test.support import threading_helper
 from test.support import FakePath
 
 import codecs
@@ -1472,7 +1473,7 @@ def f():
                         errors.append(e)
                         raise
                 threads = [threading.Thread(target=f) for x in range(20)]
-                with support.start_threads(threads):
+                with threading_helper.start_threads(threads):
                     time.sleep(0.02) # yield
                 self.assertFalse(errors,
                     "the following exceptions were caught: %r" % errors)
@@ -1836,7 +1837,7 @@ def f():
                         errors.append(e)
                         raise
                 threads = [threading.Thread(target=f) for x in range(20)]
-                with support.start_threads(threads):
+                with threading_helper.start_threads(threads):
                     time.sleep(0.02) # yield
                 self.assertFalse(errors,
                     "the following exceptions were caught: %r" % errors)
@@ -3270,7 +3271,7 @@ def run(n):
                 f.write(text)
             threads = [threading.Thread(target=run, args=(x,))
                        for x in range(20)]
-            with support.start_threads(threads, event.set):
+            with threading_helper.start_threads(threads, event.set):
                 time.sleep(0.02)
         with self.open(support.TESTFN) as f:
             content = f.read()
diff --git a/Lib/test/test_logging.py b/Lib/test/test_logging.py
index 9a114451913e8..275ce2e45f169 100644
--- a/Lib/test/test_logging.py
+++ b/Lib/test/test_logging.py
@@ -43,6 +43,7 @@
 from test.support.script_helper import assert_python_ok, assert_python_failure
 from test import support
 from test.support import socket_helper
+from test.support import threading_helper
 from test.support.logging_helper import TestHandler
 import textwrap
 import threading
@@ -79,7 +80,7 @@ class BaseTest(unittest.TestCase):
     def setUp(self):
         """Setup the default logging stream to an internal StringIO instance,
         so that we can examine log output as we want."""
-        self._threading_key = support.threading_setup()
+        self._threading_key = threading_helper.threading_setup()
 
         logger_dict = logging.getLogger().manager.loggerDict
         logging._acquireLock()
@@ -150,7 +151,7 @@ def tearDown(self):
             logging._releaseLock()
 
         self.doCleanups()
-        support.threading_cleanup(*self._threading_key)
+        threading_helper.threading_cleanup(*self._threading_key)
 
     def assert_log_lines(self, expected_values, stream=None, pat=None):
         """Match the collected log lines against the regular expression
@@ -865,7 +866,7 @@ def stop(self):
         Wait for the server thread to terminate.
         """
         self.close()
-        support.join_thread(self._thread)
+        threading_helper.join_thread(self._thread)
         self._thread = None
         asyncore.close_all(map=self._map, ignore_all=True)
 
@@ -915,7 +916,7 @@ def stop(self):
         """
         self.shutdown()
         if self._thread is not None:
-            support.join_thread(self._thread)
+            threading_helper.join_thread(self._thread)
             self._thread = None
         self.server_close()
         self.ready.clear()
@@ -3212,7 +3213,7 @@ def setup_via_listener(self, text, verify=None):
         finally:
             t.ready.wait(2.0)
             logging.config.stopListening()
-            support.join_thread(t)
+            threading_helper.join_thread(t)
 
     def test_listen_config_10_ok(self):
         with support.captured_stdout() as output:
diff --git a/Lib/test/test_os.py b/Lib/test/test_os.py
index 0db7d30f6385e..7d4376aed89bd 100644
--- a/Lib/test/test_os.py
+++ b/Lib/test/test_os.py
@@ -31,6 +31,7 @@
 import warnings
 from test import support
 from test.support import socket_helper
+from test.support import threading_helper
 from platform import win32_is_iot
 
 try:
@@ -3163,12 +3164,12 @@ class TestSendfile(unittest.TestCase):
 
     @classmethod
     def setUpClass(cls):
-        cls.key = support.threading_setup()
+        cls.key = threading_helper.threading_setup()
         create_file(support.TESTFN, cls.DATA)
 
     @classmethod
     def tearDownClass(cls):
-        support.threading_cleanup(*cls.key)
+        threading_helper.threading_cleanup(*cls.key)
         support.unlink(support.TESTFN)
 
     def setUp(self):
diff --git a/Lib/test/test_poll.py b/Lib/test/test_poll.py
index ef966bf0f5608..a14c69a5723a2 100644
--- a/Lib/test/test_poll.py
+++ b/Lib/test/test_poll.py
@@ -7,7 +7,8 @@
 import threading
 import time
 import unittest
-from test.support import TESTFN, run_unittest, reap_threads, cpython_only
+from test.support import TESTFN, run_unittest, cpython_only
+from test.support import threading_helper
 
 try:
     select.poll
@@ -175,7 +176,7 @@ def test_poll_c_limits(self):
         self.assertRaises(OverflowError, pollster.poll, INT_MAX + 1)
         self.assertRaises(OverflowError, pollster.poll, UINT_MAX + 1)
 
-    @reap_threads
+    @threading_helper.reap_threads
     def test_threaded_poll(self):
         r, w = os.pipe()
         self.addCleanup(os.close, r)
@@ -204,7 +205,7 @@ def test_threaded_poll(self):
             t.join()
 
     @unittest.skipUnless(threading, 'Threading required for this test.')
-    @reap_threads
+    @threading_helper.reap_threads
     def test_poll_blocks_with_negative_ms(self):
         for timeout_ms in [None, -1000, -1, -1.0, -0.1, -1e-100]:
             # Create two file descriptors. This will be used to unlock
diff --git a/Lib/test/test_poplib.py b/Lib/test/test_poplib.py
index b670afcf4e62e..2ac345ddd68a9 100644
--- a/Lib/test/test_poplib.py
+++ b/Lib/test/test_poplib.py
@@ -15,6 +15,7 @@
 from test import support as test_support
 from test.support import hashlib_helper
 from test.support import socket_helper
+from test.support import threading_helper
 
 HOST = socket_helper.HOST
 PORT = 0
@@ -536,11 +537,11 @@ def testTimeoutValue(self):
 def test_main():
     tests = [TestPOP3Class, TestTimeouts,
              TestPOP3_SSLClass, TestPOP3_TLSClass]
-    thread_info = test_support.threading_setup()
+    thread_info = threading_helper.threading_setup()
     try:
         test_support.run_unittest(*tests)
     finally:
-        test_support.threading_cleanup(*thread_info)
+        threading_helper.threading_cleanup(*thread_info)
 
 
 if __name__ == '__main__':
diff --git a/Lib/test/test_pydoc.py b/Lib/test/test_pydoc.py
index ffabb7f1b9407..f0d7ffd562c9d 100644
--- a/Lib/test/test_pydoc.py
+++ b/Lib/test/test_pydoc.py
@@ -24,9 +24,10 @@
 from io import StringIO
 from collections import namedtuple
 from test.support.script_helper import assert_python_ok
+from test.support import threading_helper
 from test.support import (
     TESTFN, rmtree,
-    reap_children, reap_threads, captured_output, captured_stdout,
+    reap_children, captured_output, captured_stdout,
     captured_stderr, unlink, requires_docstrings
 )
 from test import pydoc_mod
@@ -1575,7 +1576,7 @@ def test_sys_path_adjustment_when_curdir_already_included(self):
                 self.assertIsNone(self._get_revised_path(trailing_argv0dir))
 
 
- at reap_threads
+ at threading_helper.reap_threads
 def test_main():
     try:
         test.support.run_unittest(PydocDocTest,
diff --git a/Lib/test/test_queue.py b/Lib/test/test_queue.py
index d88e28a9146ef..7b23699a00f1d 100644
--- a/Lib/test/test_queue.py
+++ b/Lib/test/test_queue.py
@@ -7,6 +7,7 @@
 import unittest
 import weakref
 from test import support
+from test.support import threading_helper
 
 py_queue = support.import_fresh_module('queue', blocked=['_queue'])
 c_queue = support.import_fresh_module('queue', fresh=['_queue'])
@@ -63,7 +64,7 @@ def do_blocking_test(self, block_func, block_args, trigger_func, trigger_args):
                           block_func)
             return self.result
         finally:
-            support.join_thread(thread) # make sure the thread terminates
+            threading_helper.join_thread(thread) # make sure the thread terminates
 
     # Call this instead if block_func is supposed to raise an exception.
     def do_exceptional_blocking_test(self,block_func, block_args, trigger_func,
@@ -79,7 +80,7 @@ def do_exceptional_blocking_test(self,block_func, block_args, trigger_func,
                 self.fail("expected exception of kind %r" %
                                  expected_exception_class)
         finally:
-            support.join_thread(thread) # make sure the thread terminates
+            threading_helper.join_thread(thread) # make sure the thread terminates
             if not thread.startedEvent.is_set():
                 self.fail("trigger thread ended but event never set")
 
@@ -484,7 +485,7 @@ def wrapper(*args, **kwargs):
                                       args=(q, results, sentinel))
                      for i in range(n_consumers)]
 
-        with support.start_threads(feeders + consumers):
+        with threading_helper.start_threads(feeders + consumers):
             pass
 
         self.assertFalse(exceptions)
diff --git a/Lib/test/test_robotparser.py b/Lib/test/test_robotparser.py
index a3112b8fdf473..b0bed431d4b05 100644
--- a/Lib/test/test_robotparser.py
+++ b/Lib/test/test_robotparser.py
@@ -5,6 +5,7 @@
 import urllib.robotparser
 from test import support
 from test.support import socket_helper
+from test.support import threading_helper
 from http.server import BaseHTTPRequestHandler, HTTPServer
 
 
@@ -330,7 +331,7 @@ def tearDown(self):
         self.t.join()
         self.server.server_close()
 
-    @support.reap_threads
+    @threading_helper.reap_threads
     def testPasswordProtectedSite(self):
         addr = self.server.server_address
         url = 'http://' + socket_helper.HOST + ':' + str(addr[1])
diff --git a/Lib/test/test_sched.py b/Lib/test/test_sched.py
index 26cb4be81e5e4..491d7b3a745b4 100644
--- a/Lib/test/test_sched.py
+++ b/Lib/test/test_sched.py
@@ -4,6 +4,7 @@
 import time
 import unittest
 from test import support
+from test.support import threading_helper
 
 
 TIMEOUT = support.SHORT_TIMEOUT
@@ -82,7 +83,7 @@ def test_enter_concurrent(self):
         self.assertEqual(q.get(timeout=TIMEOUT), 5)
         self.assertTrue(q.empty())
         timer.advance(1000)
-        support.join_thread(t)
+        threading_helper.join_thread(t)
         self.assertTrue(q.empty())
         self.assertEqual(timer.time(), 5)
 
@@ -137,7 +138,7 @@ def test_cancel_concurrent(self):
         self.assertEqual(q.get(timeout=TIMEOUT), 4)
         self.assertTrue(q.empty())
         timer.advance(1000)
-        support.join_thread(t)
+        threading_helper.join_thread(t)
         self.assertTrue(q.empty())
         self.assertEqual(timer.time(), 4)
 
diff --git a/Lib/test/test_smtplib.py b/Lib/test/test_smtplib.py
index 576299900318d..7816ed34886e9 100644
--- a/Lib/test/test_smtplib.py
+++ b/Lib/test/test_smtplib.py
@@ -22,7 +22,7 @@
 from test import support, mock_socket
 from test.support import hashlib_helper
 from test.support import socket_helper
-from test.support import threading_setup, threading_cleanup, join_thread
+from test.support import threading_helper
 from unittest.mock import Mock
 
 HOST = socket_helper.HOST
@@ -217,7 +217,7 @@ class DebuggingServerTests(unittest.TestCase):
     maxDiff = None
 
     def setUp(self):
-        self.thread_key = threading_setup()
+        self.thread_key = threading_helper.threading_setup()
         self.real_getfqdn = socket.getfqdn
         socket.getfqdn = mock_socket.getfqdn
         # temporarily replace sys.stdout to capture DebuggingServer output
@@ -249,7 +249,7 @@ def tearDown(self):
         self.client_evt.set()
         # wait for the server thread to terminate
         self.serv_evt.wait()
-        join_thread(self.thread)
+        threading_helper.join_thread(self.thread)
         # restore sys.stdout
         sys.stdout = self.old_stdout
         # restore DEBUGSTREAM
@@ -257,7 +257,7 @@ def tearDown(self):
         smtpd.DEBUGSTREAM = self.old_DEBUGSTREAM
         del self.thread
         self.doCleanups()
-        threading_cleanup(*self.thread_key)
+        threading_helper.threading_cleanup(*self.thread_key)
 
     def get_output_without_xpeer(self):
         test_output = self.output.getvalue()
@@ -704,7 +704,7 @@ class TooLongLineTests(unittest.TestCase):
     respdata = b'250 OK' + (b'.' * smtplib._MAXLINE * 2) + b'\n'
 
     def setUp(self):
-        self.thread_key = threading_setup()
+        self.thread_key = threading_helper.threading_setup()
         self.old_stdout = sys.stdout
         self.output = io.StringIO()
         sys.stdout = self.output
@@ -722,10 +722,10 @@ def setUp(self):
     def tearDown(self):
         self.evt.wait()
         sys.stdout = self.old_stdout
-        join_thread(self.thread)
+        threading_helper.join_thread(self.thread)
         del self.thread
         self.doCleanups()
-        threading_cleanup(*self.thread_key)
+        threading_helper.threading_cleanup(*self.thread_key)
 
     def testLineTooLong(self):
         self.assertRaises(smtplib.SMTPResponseException, smtplib.SMTP,
@@ -955,7 +955,7 @@ def handle_error(self):
 class SMTPSimTests(unittest.TestCase):
 
     def setUp(self):
-        self.thread_key = threading_setup()
+        self.thread_key = threading_helper.threading_setup()
         self.real_getfqdn = socket.getfqdn
         socket.getfqdn = mock_socket.getfqdn
         self.serv_evt = threading.Event()
@@ -978,10 +978,10 @@ def tearDown(self):
         self.client_evt.set()
         # wait for the server thread to terminate
         self.serv_evt.wait()
-        join_thread(self.thread)
+        threading_helper.join_thread(self.thread)
         del self.thread
         self.doCleanups()
-        threading_cleanup(*self.thread_key)
+        threading_helper.threading_cleanup(*self.thread_key)
 
     def testBasic(self):
         # smoke test
@@ -1268,7 +1268,7 @@ class SMTPUTF8SimTests(unittest.TestCase):
     maxDiff = None
 
     def setUp(self):
-        self.thread_key = threading_setup()
+        self.thread_key = threading_helper.threading_setup()
         self.real_getfqdn = socket.getfqdn
         socket.getfqdn = mock_socket.getfqdn
         self.serv_evt = threading.Event()
@@ -1293,10 +1293,10 @@ def tearDown(self):
         self.client_evt.set()
         # wait for the server thread to terminate
         self.serv_evt.wait()
-        join_thread(self.thread)
+        threading_helper.join_thread(self.thread)
         del self.thread
         self.doCleanups()
-        threading_cleanup(*self.thread_key)
+        threading_helper.threading_cleanup(*self.thread_key)
 
     def test_test_server_supports_extensions(self):
         smtp = smtplib.SMTP(
@@ -1397,7 +1397,7 @@ class SimSMTPAUTHInitialResponseServer(SimSMTPServer):
 
 class SMTPAUTHInitialResponseSimTests(unittest.TestCase):
     def setUp(self):
-        self.thread_key = threading_setup()
+        self.thread_key = threading_helper.threading_setup()
         self.real_getfqdn = socket.getfqdn
         socket.getfqdn = mock_socket.getfqdn
         self.serv_evt = threading.Event()
@@ -1421,10 +1421,10 @@ def tearDown(self):
         self.client_evt.set()
         # wait for the server thread to terminate
         self.serv_evt.wait()
-        join_thread(self.thread)
+        threading_helper.join_thread(self.thread)
         del self.thread
         self.doCleanups()
-        threading_cleanup(*self.thread_key)
+        threading_helper.threading_cleanup(*self.thread_key)
 
     def testAUTH_PLAIN_initial_response_login(self):
         self.serv.add_feature('AUTH PLAIN')
diff --git a/Lib/test/test_socket.py b/Lib/test/test_socket.py
index dc1330735df10..cff07b46c7a2a 100755
--- a/Lib/test/test_socket.py
+++ b/Lib/test/test_socket.py
@@ -1,6 +1,7 @@
 import unittest
 from test import support
 from test.support import socket_helper
+from test.support import threading_helper
 
 import errno
 import io
@@ -336,7 +337,7 @@ def serverExplicitReady(self):
         self.server_ready.set()
 
     def _setUp(self):
-        self.wait_threads = support.wait_threads_exit()
+        self.wait_threads = threading_helper.wait_threads_exit()
         self.wait_threads.__enter__()
 
         self.server_ready = threading.Event()
@@ -6665,9 +6666,9 @@ def test_main():
     ])
     tests.append(TestMSWindowsTCPFlags)
 
-    thread_info = support.threading_setup()
+    thread_info = threading_helper.threading_setup()
     support.run_unittest(*tests)
-    support.threading_cleanup(*thread_info)
+    threading_helper.threading_cleanup(*thread_info)
 
 
 if __name__ == "__main__":
diff --git a/Lib/test/test_socketserver.py b/Lib/test/test_socketserver.py
index c663cc95889c9..5db8cec567afb 100644
--- a/Lib/test/test_socketserver.py
+++ b/Lib/test/test_socketserver.py
@@ -14,8 +14,9 @@
 import socketserver
 
 import test.support
-from test.support import reap_children, reap_threads, verbose
+from test.support import reap_children, verbose
 from test.support import socket_helper
+from test.support import threading_helper
 
 
 test.support.requires("network")
@@ -120,7 +121,7 @@ def handle(self):
         self.assertEqual(server.server_address, server.socket.getsockname())
         return server
 
-    @reap_threads
+    @threading_helper.reap_threads
     def run_server(self, svrcls, hdlrbase, testfunc):
         server = self.make_server(self.pickaddr(svrcls.address_family),
                                   svrcls, hdlrbase)
@@ -249,7 +250,7 @@ def test_ForkingUnixDatagramServer(self):
                         socketserver.DatagramRequestHandler,
                         self.dgram_examine)
 
-    @reap_threads
+    @threading_helper.reap_threads
     def test_shutdown(self):
         # Issue #2302: shutdown() should always succeed in making an
         # other thread leave serve_forever().
diff --git a/Lib/test/test_ssl.py b/Lib/test/test_ssl.py
index 5d496c6687614..ecb6049a6750f 100644
--- a/Lib/test/test_ssl.py
+++ b/Lib/test/test_ssl.py
@@ -5,6 +5,7 @@
 import unittest.mock
 from test import support
 from test.support import socket_helper
+from test.support import threading_helper
 import socket
 import select
 import time
@@ -4429,7 +4430,7 @@ def test_pha_required_nocert(self):
 
         # Ignore expected SSLError in ConnectionHandler of ThreadedEchoServer
         # (it is only raised sometimes on Windows)
-        with support.catch_threading_exception() as cm:
+        with threading_helper.catch_threading_exception() as cm:
             server = ThreadedEchoServer(context=server_context, chatty=False)
             with server:
                 with client_context.wrap_socket(socket.socket(),
@@ -4750,11 +4751,11 @@ def test_main(verbose=False):
     if support.is_resource_enabled('network'):
         tests.append(NetworkedTests)
 
-    thread_info = support.threading_setup()
+    thread_info = threading_helper.threading_setup()
     try:
         support.run_unittest(*tests)
     finally:
-        support.threading_cleanup(*thread_info)
+        threading_helper.threading_cleanup(*thread_info)
 
 if __name__ == "__main__":
     test_main()
diff --git a/Lib/test/test_sys.py b/Lib/test/test_sys.py
index 33b34593a0af9..2f93eaae560db 100644
--- a/Lib/test/test_sys.py
+++ b/Lib/test/test_sys.py
@@ -1,5 +1,3 @@
-from test import support
-from test.support.script_helper import assert_python_ok, assert_python_failure
 import builtins
 import codecs
 import gc
@@ -11,6 +9,9 @@
 import sys
 import sysconfig
 import test.support
+from test import support
+from test.support.script_helper import assert_python_ok, assert_python_failure
+from test.support import threading_helper
 import textwrap
 import unittest
 import warnings
@@ -365,7 +366,7 @@ def test_getframe(self):
         )
 
     # sys._current_frames() is a CPython-only gimmick.
-    @test.support.reap_threads
+    @threading_helper.reap_threads
     def test_current_frames(self):
         import threading
         import traceback
diff --git a/Lib/test/test_thread.py b/Lib/test/test_thread.py
index 77e46f2c2f15a..62b57fa338837 100644
--- a/Lib/test/test_thread.py
+++ b/Lib/test/test_thread.py
@@ -2,6 +2,7 @@
 import unittest
 import random
 from test import support
+from test.support import threading_helper
 import _thread as thread
 import time
 import weakref
@@ -32,8 +33,8 @@ def setUp(self):
         self.running = 0
         self.next_ident = 0
 
-        key = support.threading_setup()
-        self.addCleanup(support.threading_cleanup, *key)
+        key = threading_helper.threading_setup()
+        self.addCleanup(threading_helper.threading_cleanup, *key)
 
 
 class ThreadRunningTests(BasicThreadTest):
@@ -58,7 +59,7 @@ def task(self, ident):
                 self.done_mutex.release()
 
     def test_starting_threads(self):
-        with support.wait_threads_exit():
+        with threading_helper.wait_threads_exit():
             # Basic test for thread creation.
             for i in range(NUMTASKS):
                 self.newtask()
@@ -94,7 +95,7 @@ def test_nt_and_posix_stack_size(self):
             verbose_print("trying stack_size = (%d)" % tss)
             self.next_ident = 0
             self.created = 0
-            with support.wait_threads_exit():
+            with threading_helper.wait_threads_exit():
                 for i in range(NUMTASKS):
                     self.newtask()
 
@@ -116,7 +117,7 @@ def task():
             mut.acquire()
             mut.release()
 
-        with support.wait_threads_exit():
+        with threading_helper.wait_threads_exit():
             thread.start_new_thread(task, ())
             while not started:
                 time.sleep(POLL_SLEEP)
@@ -140,7 +141,7 @@ def task():
 
         started = thread.allocate_lock()
         with support.catch_unraisable_exception() as cm:
-            with support.wait_threads_exit():
+            with threading_helper.wait_threads_exit():
                 started.acquire()
                 thread.start_new_thread(task, ())
                 started.acquire()
@@ -180,7 +181,7 @@ def enter(self):
 class BarrierTest(BasicThreadTest):
 
     def test_barrier(self):
-        with support.wait_threads_exit():
+        with threading_helper.wait_threads_exit():
             self.bar = Barrier(NUMTASKS)
             self.running = NUMTASKS
             for i in range(NUMTASKS):
@@ -223,7 +224,7 @@ def setUp(self):
         self.read_fd, self.write_fd = os.pipe()
 
     @unittest.skipUnless(hasattr(os, 'fork'), 'need os.fork')
-    @support.reap_threads
+    @threading_helper.reap_threads
     def test_forkinthread(self):
         pid = None
 
@@ -243,7 +244,7 @@ def fork_thread(read_fd, write_fd):
             finally:
                 os._exit(0)
 
-        with support.wait_threads_exit():
+        with threading_helper.wait_threads_exit():
             thread.start_new_thread(fork_thread, (self.read_fd, self.write_fd))
             self.assertEqual(os.read(self.read_fd, 2), b"OK")
             os.close(self.write_fd)
diff --git a/Lib/test/test_threadedtempfile.py b/Lib/test/test_threadedtempfile.py
index e1d7a10179cc1..fe63c9e91437b 100644
--- a/Lib/test/test_threadedtempfile.py
+++ b/Lib/test/test_threadedtempfile.py
@@ -15,7 +15,7 @@
 
 import tempfile
 
-from test.support import start_threads
+from test.support import threading_helper
 import unittest
 import io
 import threading
@@ -50,7 +50,7 @@ def run(self):
 class ThreadedTempFileTest(unittest.TestCase):
     def test_main(self):
         threads = [TempFileGreedy() for i in range(NUM_THREADS)]
-        with start_threads(threads, startEvent.set):
+        with threading_helper.start_threads(threads, startEvent.set):
             pass
         ok = sum(t.ok_count for t in threads)
         errors = [str(t.name) + str(t.errors.getvalue())
diff --git a/Lib/test/test_threading.py b/Lib/test/test_threading.py
index 81e5f70d6d6ae..ad82e304e32f3 100644
--- a/Lib/test/test_threading.py
+++ b/Lib/test/test_threading.py
@@ -3,6 +3,7 @@
 """
 
 import test.support
+from test.support import threading_helper
 from test.support import verbose, import_module, cpython_only
 from test.support.script_helper import assert_python_ok, assert_python_failure
 
@@ -75,10 +76,10 @@ def run(self):
 
 class BaseTestCase(unittest.TestCase):
     def setUp(self):
-        self._threads = test.support.threading_setup()
+        self._threads = threading_helper.threading_setup()
 
     def tearDown(self):
-        test.support.threading_cleanup(*self._threads)
+        threading_helper.threading_cleanup(*self._threads)
         test.support.reap_children()
 
 
@@ -130,7 +131,7 @@ def f():
             done.set()
         done = threading.Event()
         ident = []
-        with support.wait_threads_exit():
+        with threading_helper.wait_threads_exit():
             tid = _thread.start_new_thread(f, ())
             done.wait()
             self.assertEqual(ident[0], tid)
@@ -171,7 +172,7 @@ def f(mutex):
 
         mutex = threading.Lock()
         mutex.acquire()
-        with support.wait_threads_exit():
+        with threading_helper.wait_threads_exit():
             tid = _thread.start_new_thread(f, (mutex,))
             # Wait for the thread to finish.
             mutex.acquire()
diff --git a/Lib/test/test_threading_local.py b/Lib/test/test_threading_local.py
index 2fd14ae2e16f3..9862094eaccd8 100644
--- a/Lib/test/test_threading_local.py
+++ b/Lib/test/test_threading_local.py
@@ -2,6 +2,7 @@
 import unittest
 from doctest import DocTestSuite
 from test import support
+from test.support import threading_helper
 import weakref
 import gc
 
@@ -65,8 +66,8 @@ def f(i):
             # Simply check that the variable is correctly set
             self.assertEqual(local.x, i)
 
-        with support.start_threads(threading.Thread(target=f, args=(i,))
-                                   for i in range(10)):
+        with threading_helper.start_threads(threading.Thread(target=f, args=(i,))
+                                            for i in range(10)):
             pass
 
     def test_derived_cycle_dealloc(self):
diff --git a/Lib/test/test_threadsignals.py b/Lib/test/test_threadsignals.py
index eeacd3698cb13..15e8078e93662 100644
--- a/Lib/test/test_threadsignals.py
+++ b/Lib/test/test_threadsignals.py
@@ -5,6 +5,7 @@
 import os
 import sys
 from test import support
+from test.support import threading_helper
 import _thread as thread
 import time
 
@@ -39,7 +40,7 @@ def send_signals():
 class ThreadSignals(unittest.TestCase):
 
     def test_signals(self):
-        with support.wait_threads_exit():
+        with threading_helper.wait_threads_exit():
             # Test signal handling semantics of threads.
             # We spawn a thread, have the thread send two signals, and
             # wait for it to finish. Check that we got both signals
@@ -129,7 +130,7 @@ def test_rlock_acquire_interruption(self):
             def other_thread():
                 rlock.acquire()
 
-            with support.wait_threads_exit():
+            with threading_helper.wait_threads_exit():
                 thread.start_new_thread(other_thread, ())
                 # Wait until we can't acquire it without blocking...
                 while rlock.acquire(blocking=False):
@@ -165,7 +166,7 @@ def other_thread():
                 time.sleep(0.5)
                 lock.release()
 
-            with support.wait_threads_exit():
+            with threading_helper.wait_threads_exit():
                 thread.start_new_thread(other_thread, ())
                 # Wait until we can't acquire it without blocking...
                 while lock.acquire(blocking=False):
@@ -212,7 +213,7 @@ def send_signals():
                     os.kill(process_pid, signal.SIGUSR1)
                 done.release()
 
-            with support.wait_threads_exit():
+            with threading_helper.wait_threads_exit():
                 # Send the signals from the non-main thread, since the main thread
                 # is the only one that can process signals.
                 thread.start_new_thread(send_signals, ())
diff --git a/Lib/test/test_urllib2_localnet.py b/Lib/test/test_urllib2_localnet.py
index ed426b05a7198..e568cc4575549 100644
--- a/Lib/test/test_urllib2_localnet.py
+++ b/Lib/test/test_urllib2_localnet.py
@@ -10,6 +10,7 @@
 
 from test import support
 from test.support import hashlib_helper
+from test.support import threading_helper
 
 try:
     import ssl
@@ -666,11 +667,11 @@ def setUpModule():
     # Store the threading_setup in a key and ensure that it is cleaned up
     # in the tearDown
     global threads_key
-    threads_key = support.threading_setup()
+    threads_key = threading_helper.threading_setup()
 
 def tearDownModule():
     if threads_key:
-        support.threading_cleanup(*threads_key)
+        threading_helper.threading_cleanup(*threads_key)
 
 if __name__ == "__main__":
     unittest.main()
diff --git a/Lib/test/test_xmlrpc.py b/Lib/test/test_xmlrpc.py
index f68af527eae85..79f702d0a75d3 100644
--- a/Lib/test/test_xmlrpc.py
+++ b/Lib/test/test_xmlrpc.py
@@ -16,6 +16,7 @@
 import contextlib
 from test import support
 from test.support import socket_helper
+from test.support import threading_helper
 from test.support import ALWAYS_EQ, LARGEST, SMALLEST
 
 try:
@@ -1464,7 +1465,7 @@ def test_xmlrpcserver_has_use_builtin_types_flag(self):
         self.assertTrue(server.use_builtin_types)
 
 
- at support.reap_threads
+ at threading_helper.reap_threads
 def test_main():
     support.run_unittest(XMLRPCTestCase, HelperTestCase, DateTimeTestCase,
             BinaryTestCase, FaultTestCase, UseBuiltinTypesTestCase,



More information about the Python-checkins mailing list