Skip to content

Commit ef10487

Browse files
committed
Migrate threading functions and class to helper
1 parent e41f43d commit ef10487

1 file changed

Lines changed: 0 additions & 193 deletions

File tree

Lib/test/support/__init__.py

Lines changed: 0 additions & 193 deletions
Original file line numberDiff line numberDiff line change
@@ -104,8 +104,6 @@
104104
'temp_umask', "reap_children",
105105
# logging
106106
"TestHandler",
107-
# threads
108-
"threading_setup", "threading_cleanup", "reap_threads", "start_threads",
109107
# miscellaneous
110108
"check_warnings", "check_no_resource_warning", "check_no_warnings",
111109
"EnvironmentVarGuard",
@@ -1541,104 +1539,6 @@ def print_warning(msg):
15411539
# to cleanup threads.
15421540
environment_altered = False
15431541

1544-
# NOTE: we use thread._count() rather than threading.enumerate() (or the
1545-
# moral equivalent thereof) because a threading.Thread object is still alive
1546-
# until its __bootstrap() method has returned, even after it has been
1547-
# unregistered from the threading module.
1548-
# thread._count(), on the other hand, only gets decremented *after* the
1549-
# __bootstrap() method has returned, which gives us reliable reference counts
1550-
# at the end of a test run.
1551-
1552-
def threading_setup():
1553-
return _thread._count(), threading._dangling.copy()
1554-
1555-
def threading_cleanup(*original_values):
1556-
global environment_altered
1557-
1558-
_MAX_COUNT = 100
1559-
1560-
for count in range(_MAX_COUNT):
1561-
values = _thread._count(), threading._dangling
1562-
if values == original_values:
1563-
break
1564-
1565-
if not count: # Display a warning at the first iteration
1566-
environment_altered = True
1567-
dangling_threads = values[1]
1568-
print_warning(f"threading_cleanup() failed to cleanup "
1569-
f"{values[0] - original_values[0]} threads "
1570-
f"(count: {values[0]}, "
1571-
f"dangling: {len(dangling_threads)})")
1572-
for thread in dangling_threads:
1573-
print_warning(f"Dangling thread: {thread!r}")
1574-
1575-
# Don't hold references to threads
1576-
dangling_threads = None
1577-
values = None
1578-
1579-
time.sleep(0.01)
1580-
gc_collect()
1581-
1582-
1583-
def reap_threads(func):
1584-
"""Use this function when threads are being used. This will
1585-
ensure that the threads are cleaned up even when the test fails.
1586-
"""
1587-
@functools.wraps(func)
1588-
def decorator(*args):
1589-
key = threading_setup()
1590-
try:
1591-
return func(*args)
1592-
finally:
1593-
threading_cleanup(*key)
1594-
return decorator
1595-
1596-
1597-
@contextlib.contextmanager
1598-
def wait_threads_exit(timeout=60.0):
1599-
"""
1600-
bpo-31234: Context manager to wait until all threads created in the with
1601-
statement exit.
1602-
1603-
Use _thread.count() to check if threads exited. Indirectly, wait until
1604-
threads exit the internal t_bootstrap() C function of the _thread module.
1605-
1606-
threading_setup() and threading_cleanup() are designed to emit a warning
1607-
if a test leaves running threads in the background. This context manager
1608-
is designed to cleanup threads started by the _thread.start_new_thread()
1609-
which doesn't allow to wait for thread exit, whereas thread.Thread has a
1610-
join() method.
1611-
"""
1612-
old_count = _thread._count()
1613-
try:
1614-
yield
1615-
finally:
1616-
start_time = time.monotonic()
1617-
deadline = start_time + timeout
1618-
while True:
1619-
count = _thread._count()
1620-
if count <= old_count:
1621-
break
1622-
if time.monotonic() > deadline:
1623-
dt = time.monotonic() - start_time
1624-
msg = (f"wait_threads() failed to cleanup {count - old_count} "
1625-
f"threads after {dt:.1f} seconds "
1626-
f"(count: {count}, old count: {old_count})")
1627-
raise AssertionError(msg)
1628-
time.sleep(0.010)
1629-
gc_collect()
1630-
1631-
1632-
def join_thread(thread, timeout=30.0):
1633-
"""Join a thread. Raise an AssertionError if the thread is still alive
1634-
after timeout seconds.
1635-
"""
1636-
thread.join(timeout)
1637-
if thread.is_alive():
1638-
msg = f"failed to join the thread in {timeout:.1f} seconds"
1639-
raise AssertionError(msg)
1640-
1641-
16421542
def reap_children():
16431543
"""Use this function at the end of test_main() whenever sub-processes
16441544
are started. This will help ensure that no extra children (zombies)
@@ -1667,42 +1567,6 @@ def reap_children():
16671567
environment_altered = True
16681568

16691569

1670-
@contextlib.contextmanager
1671-
def start_threads(threads, unlock=None):
1672-
threads = list(threads)
1673-
started = []
1674-
try:
1675-
try:
1676-
for t in threads:
1677-
t.start()
1678-
started.append(t)
1679-
except:
1680-
if verbose:
1681-
print("Can't start %d threads, only %d threads started" %
1682-
(len(threads), len(started)))
1683-
raise
1684-
yield
1685-
finally:
1686-
try:
1687-
if unlock:
1688-
unlock()
1689-
endtime = starttime = time.monotonic()
1690-
for timeout in range(1, 16):
1691-
endtime += 60
1692-
for t in started:
1693-
t.join(max(endtime - time.monotonic(), 0.01))
1694-
started = [t for t in started if t.is_alive()]
1695-
if not started:
1696-
break
1697-
if verbose:
1698-
print('Unable to join %d threads during a period of '
1699-
'%d minutes' % (len(started), timeout))
1700-
finally:
1701-
started = [t for t in started if t.is_alive()]
1702-
if started:
1703-
faulthandler.dump_traceback(sys.stdout)
1704-
raise AssertionError('Unable to join %d threads' % len(started))
1705-
17061570
@contextlib.contextmanager
17071571
def swap_attr(obj, attr, new_val):
17081572
"""Temporary swap out an attribute with a new object.
@@ -2507,63 +2371,6 @@ def __exit__(self, *exc_info):
25072371
del self.unraisable
25082372

25092373

2510-
class catch_threading_exception:
2511-
"""
2512-
Context manager catching threading.Thread exception using
2513-
threading.excepthook.
2514-
2515-
Attributes set when an exception is catched:
2516-
2517-
* exc_type
2518-
* exc_value
2519-
* exc_traceback
2520-
* thread
2521-
2522-
See threading.excepthook() documentation for these attributes.
2523-
2524-
These attributes are deleted at the context manager exit.
2525-
2526-
Usage:
2527-
2528-
with support.catch_threading_exception() as cm:
2529-
# code spawning a thread which raises an exception
2530-
...
2531-
2532-
# check the thread exception, use cm attributes:
2533-
# exc_type, exc_value, exc_traceback, thread
2534-
...
2535-
2536-
# exc_type, exc_value, exc_traceback, thread attributes of cm no longer
2537-
# exists at this point
2538-
# (to avoid reference cycles)
2539-
"""
2540-
2541-
def __init__(self):
2542-
self.exc_type = None
2543-
self.exc_value = None
2544-
self.exc_traceback = None
2545-
self.thread = None
2546-
self._old_hook = None
2547-
2548-
def _hook(self, args):
2549-
self.exc_type = args.exc_type
2550-
self.exc_value = args.exc_value
2551-
self.exc_traceback = args.exc_traceback
2552-
self.thread = args.thread
2553-
2554-
def __enter__(self):
2555-
self._old_hook = threading.excepthook
2556-
threading.excepthook = self._hook
2557-
return self
2558-
2559-
def __exit__(self, *exc_info):
2560-
threading.excepthook = self._old_hook
2561-
del self.exc_type
2562-
del self.exc_value
2563-
del self.exc_traceback
2564-
del self.thread
2565-
2566-
25672374
def wait_process(pid, *, exitcode, timeout=None):
25682375
"""
25692376
Wait until process pid completes and check that the process exit code is

0 commit comments

Comments
 (0)