From 124b9ed3338574f9b56e9c27a956050671bad3f5 Mon Sep 17 00:00:00 2001 From: Thomas Grainger Date: Thu, 5 Dec 2024 05:55:39 +0000 Subject: [PATCH 1/6] remove 1-second join from threadexception plugin --- changelog/13016.improvement.rst | 1 - src/_pytest/threadexception.py | 20 +++----------------- testing/test_threadexception.py | 3 +-- 3 files changed, 4 insertions(+), 20 deletions(-) diff --git a/changelog/13016.improvement.rst b/changelog/13016.improvement.rst index 22642fc6f4f..634672ab69b 100644 --- a/changelog/13016.improvement.rst +++ b/changelog/13016.improvement.rst @@ -1,7 +1,6 @@ A number of :ref:`threadexception ` enhancements: * Set the excepthook as early as possible and unset it as late as possible, to collect the most possible number of unhandled exceptions from threads. -* join threads for 1 second just before unsetting the excepthook, to collect any straggling exceptions * Collect multiple thread exceptions per test phase. * Report the :mod:`tracemalloc` allocation traceback (if available). * Avoid using a generator based hook to allow handling :class:`StopIteration` in test failures. diff --git a/src/_pytest/threadexception.py b/src/_pytest/threadexception.py index 38665d2731f..2c45b59a01e 100644 --- a/src/_pytest/threadexception.py +++ b/src/_pytest/threadexception.py @@ -25,22 +25,6 @@ from exceptiongroup import ExceptionGroup -def join_threads() -> None: - start = time.monotonic() - current_thread = threading.current_thread() - # This function is executed right at the end of the pytest run, just - # before we return an exit code, which is where the interpreter joins - # any remaining non-daemonic threads anyway, so it's ok to join all the - # threads. However there might be threads that depend on some shutdown - # signal that happens after pytest finishes, so we want to limit the - # join time somewhat. A one second timeout seems reasonable. - timeout = 1 - for thread in threading.enumerate(): - if thread is not current_thread and not thread.daemon: - # TODO: raise an error/warning if there's dangling threads. - thread.join(timeout - (time.monotonic() - start)) - - class ThreadExceptionMeta(NamedTuple): msg: str cause_msg: str @@ -96,7 +80,9 @@ def cleanup( ) -> None: try: try: - join_threads() + # we don't join threads here, so exceptions raised from any + # threads still running by the time _threading_atexits joins them + # do not get captured. collect_thread_exception(config) finally: threading.excepthook = prev_hook diff --git a/testing/test_threadexception.py b/testing/test_threadexception.py index 6ee93ab9c22..5dad07b8b85 100644 --- a/testing/test_threadexception.py +++ b/testing/test_threadexception.py @@ -195,17 +195,16 @@ def test_2(): pass def test_unhandled_thread_exception_after_teardown(pytester: Pytester) -> None: pytester.makepyfile( test_it=""" - import time import threading import pytest def thread(): def oops(): - time.sleep(0.5) raise ValueError("Oops") t = threading.Thread(target=oops, name="MyThread") t.start() + t.join() def test_it(request): request.config.add_cleanup(thread) From dedbcbd9a85d7d31d3a3b55c563849a140b6dc2c Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Thu, 5 Dec 2024 05:58:29 +0000 Subject: [PATCH 2/6] [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci --- src/_pytest/threadexception.py | 1 - 1 file changed, 1 deletion(-) diff --git a/src/_pytest/threadexception.py b/src/_pytest/threadexception.py index 2c45b59a01e..8ce00457ef5 100644 --- a/src/_pytest/threadexception.py +++ b/src/_pytest/threadexception.py @@ -5,7 +5,6 @@ import functools import sys import threading -import time import traceback from typing import NamedTuple from typing import TYPE_CHECKING From 370eabfabae3f0e23758a0c25198769d62e8b02e Mon Sep 17 00:00:00 2001 From: Thomas Grainger Date: Thu, 5 Dec 2024 06:08:28 +0000 Subject: [PATCH 3/6] add news --- changelog/13028.improvement.rst | 3 +++ 1 file changed, 3 insertions(+) create mode 100644 changelog/13028.improvement.rst diff --git a/changelog/13028.improvement.rst b/changelog/13028.improvement.rst new file mode 100644 index 00000000000..1d57e74a58e --- /dev/null +++ b/changelog/13028.improvement.rst @@ -0,0 +1,3 @@ +Remove (unreleased) 1-second join from threadexception plugin. +Exceptions in threads that terminate after the pytest run will +continue to not be captured. From cda445bda06e262e1854f5bff2e822605615e4d4 Mon Sep 17 00:00:00 2001 From: Bruno Oliveira Date: Thu, 5 Dec 2024 07:37:09 -0300 Subject: [PATCH 4/6] Delete changelog/13028.improvement.rst --- changelog/13028.improvement.rst | 3 --- 1 file changed, 3 deletions(-) delete mode 100644 changelog/13028.improvement.rst diff --git a/changelog/13028.improvement.rst b/changelog/13028.improvement.rst deleted file mode 100644 index 1d57e74a58e..00000000000 --- a/changelog/13028.improvement.rst +++ /dev/null @@ -1,3 +0,0 @@ -Remove (unreleased) 1-second join from threadexception plugin. -Exceptions in threads that terminate after the pytest run will -continue to not be captured. From bcb5dababc4b8dc96c3ca6acee9e7f8a6854c80e Mon Sep 17 00:00:00 2001 From: Bruno Oliveira Date: Thu, 5 Dec 2024 07:38:00 -0300 Subject: [PATCH 5/6] Update src/_pytest/threadexception.py --- src/_pytest/threadexception.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/_pytest/threadexception.py b/src/_pytest/threadexception.py index 8ce00457ef5..08ab465326b 100644 --- a/src/_pytest/threadexception.py +++ b/src/_pytest/threadexception.py @@ -79,7 +79,7 @@ def cleanup( ) -> None: try: try: - # we don't join threads here, so exceptions raised from any + # We don't join threads here, so exceptions raised from any # threads still running by the time _threading_atexits joins them # do not get captured. collect_thread_exception(config) From 5f2e6b168497970ac03f91c977dbf9e754944348 Mon Sep 17 00:00:00 2001 From: Bruno Oliveira Date: Thu, 5 Dec 2024 08:30:33 -0300 Subject: [PATCH 6/6] Update src/_pytest/threadexception.py --- src/_pytest/threadexception.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/_pytest/threadexception.py b/src/_pytest/threadexception.py index 08ab465326b..eb57783be26 100644 --- a/src/_pytest/threadexception.py +++ b/src/_pytest/threadexception.py @@ -81,7 +81,7 @@ def cleanup( try: # We don't join threads here, so exceptions raised from any # threads still running by the time _threading_atexits joins them - # do not get captured. + # do not get captured (see #13027). collect_thread_exception(config) finally: threading.excepthook = prev_hook