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

Fix tests that leak threads (pytest 8) #3358

Merged
merged 9 commits into from Mar 26, 2024
Merged

Conversation

ecerulm
Copy link
Contributor

@ecerulm ecerulm commented Mar 4, 2024

Bumping the pytest version to 8.0.2 revealed that some tests are leaking threads. The tests create a SocketServerThread and those threads never finish. These unterminated threads confuse pytest-memray since while running a test one of these threads can do an allocation that will be counted as part of this test.

In particular the following tests currently do not properly terminate the threads:

  • test_socket_close_socket_then_file
  • test_socket_close_stays_open_with_makefile_open
  • test_close_after_handshake
  • test_total_timeout
  • test_socket_close_socket_then_file
  • test_socket_close_stays_open_with_makefile_open
  • test_multipart_assert_header_parsing_no_defects
  • test_chunked_specified[bytes-POST-False] (which is SKIPPED but it start the server thread, then skips itself using pytest.skip())
  • test_chunked_specified[bytes-PUT-False] (which is SKIPPED but it start the server thread, then skips itself using pytest.skip())
  • test_chunked_specified[bytes-PATCH-False] (which is SKIPPED but it start the server thread, then skips itself using pytest.skip())

This PR addresses the problem by

  • waiting after each test for the thread to finish, if it does not finish it time, generate an exception. Silently continuing only hides the problem.
  • introducing a quit_event , on the teardown_method we signal the quit_event and the socket_handlers can read it, and acts appropriately.
    • this unfortunately complicates the code since now we can't just block indefinitely, we need to timeout, to check for the quit_event and terminate in that case.
  • introduces a new pytest marker server_threads to allow to test funcionality. I can remove this marker after but right now you can test this with nox -rs test-3.10 -- -m server_threads

Closes #3335

Alternatives

  • Ask pytest-memray to give an option to only count the allocations made on the current test thread (and not on the server threads). See option to count only memory used in current thread / limit_memory("10MB", current_thread_only=True), bloomberg/pytest-memray#111
  • Increase the limit to @pytest.mark.limit_memory("10.10 MB"). This does not fix the problem but it's way easier and less convoluted. But ideally we should not leave threads lingering from test to test.
  • Make sure each server threads dies of natural causes. Most of the server thread close down gracefully since the code path servers a single request and then returns. But the tests mentioned above they are not doing that.
    • some of the tests like test_socket_close_socket_then_file I believe they really need a outside signal
    • some test like * test_chunked_specified[bytes-POST-False], should not even create a server thread in the first place, the whole test can be SKIPPED before self._start_server(socket_handler).

@ecerulm ecerulm added the Skip Changelog Pull requests that don't require a changelog entry label Mar 4, 2024
@ecerulm ecerulm marked this pull request as ready for review March 4, 2024 14:58
@ecerulm ecerulm mentioned this pull request Mar 4, 2024
Copy link
Member

@pquentin pquentin left a comment

Choose a reason for hiding this comment

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

Thanks for this work! My main worry is the reliability of the time-based logic. You would expect LONG_TIMEOUT * 3 to be enough, but CI always surprises me in this regard. We'll have to be ready to bump that constant if needed. Thankfully bumping it will not make the happy path slower.

test/test_ssltransport.py Outdated Show resolved Hide resolved
test/test_ssltransport.py Outdated Show resolved Hide resolved
test/with_dummyserver/test_socketlevel.py Outdated Show resolved Hide resolved
@ecerulm
Copy link
Contributor Author

ecerulm commented Mar 5, 2024

Thanks for this work! My main worry is the reliability of the time-based logic. You would expect LONG_TIMEOUT * 3 to be enough, but CI always surprises me in this regard. We'll have to be ready to bump that constant if needed. =

I've increased it to LONG_TIMEOUT *2 + 5.0 , in principle the maximum time that the thread can take to notice the quit_event is LONG_TIMEOUT and the thread should terminate immediately (0.1s) after that. If it takes more than 5 seconds something must be wrong.

Thankfully bumping it will not make the happy path slower.
yeah, the if the thread actually terminates fast the .join() will also return fast. It won't make the happy path slower.

Copy link
Contributor Author

@ecerulm ecerulm left a comment

Choose a reason for hiding this comment

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

I've removed the print statements, extraneous, comments, and the unnecessary time.sleep()

test/test_ssltransport.py Outdated Show resolved Hide resolved
@ecerulm ecerulm requested a review from pquentin March 6, 2024 06:52
illia-v
illia-v previously approved these changes Mar 11, 2024
Copy link
Member

@illia-v illia-v left a comment

Choose a reason for hiding this comment

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

LGTM, thanks for this!

Thanks for this work! My main worry is the reliability of the time-based logic. You would expect LONG_TIMEOUT * 3 to be enough, but CI always surprises me in this regard. We'll have to be ready to bump that constant if needed. =

I've increased it to LONG_TIMEOUT *2 + 5.0 , in principle the maximum time that the thread can take to notice the quit_event is LONG_TIMEOUT and the thread should terminate immediately (0.1s) after that. If it takes more than 5 seconds something must be wrong.

What do you think about adding a comment to the code describing why LONG_TIMEOUT * 2 + 5.0 was chosen?

  • introduces a new pytest marker server_threads to allow to test funcionality. I can remove this marker after but right now you can test this with nox -rs test-3.10 -- -m server_threads

I'd drop server_threads before the PR is merged if there is no expected use for the marker.

@ecerulm
Copy link
Contributor Author

ecerulm commented Mar 12, 2024

What do you think about adding a comment to the code describing why LONG_TIMEOUT * 2 + 5.0 was chosen?

Done, in latest version

I'd drop server_threads before the PR is merged if there is no expected use for the marker.

Done

pyproject.toml Outdated Show resolved Hide resolved
dummyserver/testcase.py Outdated Show resolved Hide resolved
ecerulm and others added 2 commits March 12, 2024 13:55
Co-authored-by: Illia Volochii <illia.volochii@gmail.com>
Co-authored-by: Illia Volochii <illia.volochii@gmail.com>
@illia-v
Copy link
Member

illia-v commented Mar 26, 2024

I'll merge this to include the changes in a base for a #3366 fix

@illia-v illia-v merged commit 8c20886 into urllib3:main Mar 26, 2024
35 of 36 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Skip Changelog Pull requests that don't require a changelog entry
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

3 participants