Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

threadexception enhancements #13016

Merged
merged 16 commits into from
Dec 4, 2024
Merged

threadexception enhancements #13016

merged 16 commits into from
Dec 4, 2024

Conversation

graingert
Copy link
Member

@graingert graingert commented Dec 1, 2024

A number of 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 tracemalloc allocation traceback (if available).
  • Avoid using a generator based hook to allow handling StopIteration in test failures.
  • Report the thread exception as the cause of the pytest.PytestUnhandledThreadExceptionWarning exception if raised.
  • Extract the name of the thread object in the excepthook which should help with resurrection of the thread.

@psf-chronographer psf-chronographer bot added the bot:chronographer:provided (automation) changelog entry is part of PR label Dec 1, 2024
@graingert graingert marked this pull request as ready for review December 1, 2024 08:24
# 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
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this is the bit of the PR I'm most unsure about. I think maybe we want to raise a deprecation warning for straggling threads - and make it into a error in pytest 9?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah, that seems reasonable to me.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think it is a reasonable thing for someone to run some thread unrelated to pytest, like a monitoring thread or similar (there are many which do this), and then run pytest.main(). If I understand correctly, this would then cause an unnecessary 1s delay to the pytest run. Best case, they report a bug, worst case they just think pytest is slow and suffer through it.

So IMO, unless we can somehow restrict join_threads only to threads started by tests, we shouldn't do it.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

removed in #13027

Copy link
Member

@Zac-HD Zac-HD left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks like a clear improvement to me! Merge when comments are addressed to your satisfaction 👍

# 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
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah, that seems reasonable to me.


append(
ThreadExceptionMeta(
# Compute these strings here as they might change later
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

stale/misplaced comment?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think this is valid, I meant compute the strings in the hook rather than later on in the collect-from-deque phase

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I've moved the comment to the correct place. And I've gone through and checked the comments match the unraisablehook (or diverge where changes are needed)

Note the first commit in this PR was generated by claude - given the unraisablehook patch and the existing threadexception.py. It did well on the code but not so well on the comments!

Comment on lines +220 to +221
# TODO: should be a test failure or error
assert result.ret == pytest.ExitCode.INTERNAL_ERROR
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should we wait for this? Or open an issue to track it?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think open an issue to track this, the unraisablehook code has the same issue

@graingert
Copy link
Member Author

Hmm the coverage is looking low, I'll look into it

if len(errors) == 1:
raise errors[0]
if errors:
raise ExceptionGroup("multiple thread exception warnings", errors)
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this is absolutely definitely covered by tests:

@pytest.mark.filterwarnings("error::pytest.PytestUnhandledThreadExceptionWarning")
def test_threadexception_warning_multiple_errors(pytester: Pytester) -> None:
pytester.makepyfile(
test_it="""
import threading
def test_it():
def oops():
raise ValueError("Oops")
t = threading.Thread(target=oops, name="MyThread")
t.start()
t.join()
t = threading.Thread(target=oops, name="MyThread2")
t.start()
t.join()
def test_2(): pass
"""
)
result = pytester.runpytest()
assert result.ret == pytest.ExitCode.TESTS_FAILED
result.assert_outcomes(passed=1, failed=1)
result.stdout.fnmatch_lines(
[" | *ExceptionGroup: multiple thread exception warnings (2 sub-exceptions)"]
)

what's going on?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

resolved, yeah? Looks like 100% coverage wherever I look

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Screenshot_20241204-114858

Copy link
Member

@jakkdl jakkdl Dec 4, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Oh, in the review window codecov is being dumb. idk what exactly triggers it but the codecov app/bot/thing is fairly often behind (not just in this repo), and the CI run is the best place to go look for coverage status.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

CI refers to https://github.com/pytest-dev/pytest/pull/13016/checks?check_run_id=33901516563, but the "Files Changed" codecov alerts refers to https://github.com/pytest-dev/pytest/pull/13016/checks?check_run_id=33900516688 - which while on the same commit it displays an orange "refresh" button that will redirect to 33901516563. Not sure how those runs differ

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hmm, I think this is due to 2024-12-04T13:24:59,188624264+01:00
as seen on https://docs.codecov.com/docs/github-checks

Which I think means we (and every project) needs to disable annotations

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

opened codecov/codecov-action#1710 requesting the default be changed, but will otherwise open PR's/edit the org-level yaml to disable them.

@graingert graingert requested a review from Zac-HD December 4, 2024 09:23
@graingert
Copy link
Member Author

I've done a bit more than just resolve your comments, @Zac-HD, so I'd like a re-review - if that's ok

Copy link
Member

@Zac-HD Zac-HD left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Reviewed further changes, confirming that I'm happy to merge 🙂

@graingert graingert merged commit ea1a79a into main Dec 4, 2024
28 checks passed
@graingert graingert deleted the threadexception-enhancements branch December 4, 2024 21:19
nicoddemus added a commit that referenced this pull request Dec 5, 2024
As per review comment: #13016 (comment).

Closes #13028.

---------

Co-authored-by: Bruno Oliveira <[email protected]>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
bot:chronographer:provided (automation) changelog entry is part of PR
Projects
None yet
Development

Successfully merging this pull request may close these issues.

4 participants