bpo-38356: Fix ThreadedChildWatcher thread leak in test_asyncio#16552
bpo-38356: Fix ThreadedChildWatcher thread leak in test_asyncio#16552miss-islington merged 5 commits into
Conversation
|
A Python core developer has requested some changes be made to your pull request before we can consider merging it. If you could please address their requests along with any other requests in other reviews from core developers that would be appreciated. Once you have made the requested changes, please leave a comment on this pull request containing the phrase And if you don't make the requested changes, you will be put in the comfy chair! |
Sorry, something went wrong.
asvetlov
left a comment
There was a problem hiding this comment.
I disagree with the PR.
As @vstinner mentioned watcher.close() is not called normally.
Moreover, the watcher is designed to work with multiple event loops.
The lifecycle of watcher normally is equal to event loop policy lifetime, so typically the watcher lives forever.
However, the CPython test suite requires that any test has no side effects. That's why we explicitly call set_event_loop_policy(None) after every asyncio test. We should clean up a watcher after this step (again, explicitly).
Please note, asyncio watchers are part of public API.
I never love it but we cannot make wild changes. Watchers potentially can be used not only by asyncio but by third-party loops as well. That's why ThreadedChildWatcher was designed to strongly keep the backward compatibility.
Third-party watcher is also possible, we don't restrict that. The watcher may have no proposed _close_loop() method as well.
P.S.
I believe after polishing ThreadedChildWatcher we can start deprecation of watchers subsystem at all, it generates more problems than solves. Removing this part from public API can help but this is another thing.
Sorry, something went wrong.
This was primarily based upon fixing the thread leak in the unittest class SubprocessWatcherMixin(SubprocessMixin):
Watcher = None
def setUp(self):
super().setUp()
policy = asyncio.get_event_loop_policy()
self.loop = policy.new_event_loop()
self.set_event_loop(self.loop)
watcher = self.Watcher()
watcher.attach_loop(self.loop)
policy.set_child_watcher(watcher)
def tearDown(self):
super().tearDown()
policy = asyncio.get_event_loop_policy()
watcher = policy.get_child_watcher()
policy.set_child_watcher(None)
watcher.attach_loop(None)
watcher.close()I figured the easiest way to have the fix conform to the existing tests would be to have all of the threads joined in def tearDown(self):
super().tearDown()
policy = asyncio.get_event_loop_policy()
watcher = policy.get_child_watcher()
policy.set_child_watcher(None)
watcher.attach_loop(None)
if isinstance(watcher, ThreadedChildWatcher):
watcher._join_threads()
watcher.close()But, this would work even worse in terms to compatibility with other implementations, and adds an unneeded conditional to the other watcher class tests.
Do you have an implementation idea as to how this could be done? Would each watcher have it's own cleanup method separate from
Would this still fix the dangling thread issue in the SubprocessThreadedWatcherTests? As far as I can tell, only the watcher itself is closed, not the event loop. |
Sorry, something went wrong.
After reading through your comment, I also disagree with the current state of it. As I mentioned in the bpo comments, I really didn't have an idea of the intended public API design for ThreadedChildWatcher (beyond what is said in the docs), this was mostly just an attempt to fix the dangling thread issue. I'd be more than glad to modify this PR though in any recommended manner that you think would fit the intended API design. |
Sorry, something went wrong.
Also, there's something else that's not clear to me about this. In the current docs, it says the So where else other than |
Sorry, something went wrong.
|
Well, joining spawn threads in |
Sorry, something went wrong.
Just to make sure it's clear, So if we're in agreement about joining the threads in |
Sorry, something went wrong.
Okay, I'll just add the docstring then after I finish working on the 3.8 asyncio "What's New" changes. |
Sorry, something went wrong.
|
In the recently added commits, I fixed the formatting, added a docstring as requested by @1st1, and made a slight modification to not join daemon threads in This is because daemon threads don't need to be joined as part of this cleanup process. When the program exits, they're terminated automatically. See #16552 (comment) for more details. |
Sorry, something went wrong.
|
@asvetlov Are there any changes you'd like to suggest for this PR or is it good to go? |
Sorry, something went wrong.
asvetlov
left a comment
There was a problem hiding this comment.
LGTM
Sorry, something went wrong.
|
Thanks @aeros for the PR 🌮🎉.. I'm working now to backport this PR to: 3.8. |
Sorry, something went wrong.
…onGH-16552) Motivation for this PR (comment from @vstinner in bpo issue): ``` Warning seen o AMD64 Ubuntu Shared 3.x buildbot: https://buildbot.python.org/all/GH-/builders/141/builds/2593 test_devnull_output (test.test_a=syncio.test_subprocess.SubprocessThreadedWatcherTests) ... Warning -- threading_cleanup() failed to cleanup 1 threads (count: 1, dangling: 2) ``` The following implementation details for the new method are TBD: 1) Public vs private 2) Inclusion in `close()` 3) Name 4) Coroutine vs subroutine method 5) *timeout* parameter If it's a private method, 3, 4, and 5 are significantly less important. I started with the most minimal implementation that fixes the dangling threads without modifying the regression tests, which I think is particularly important. I typically try to avoid directly modifying existing tests as much as possible unless it's necessary to do so. However, I am open to changing any part of this. https://bugs.python.org/issue38356 (cherry picked from commit 0ca7cc7) Co-authored-by: Kyle Stanley <aeros167@gmail.com>
…6552) Motivation for this PR (comment from @vstinner in bpo issue): ``` Warning seen o AMD64 Ubuntu Shared 3.x buildbot: https://buildbot.python.org/all/GH-/builders/141/builds/2593 test_devnull_output (test.test_a=syncio.test_subprocess.SubprocessThreadedWatcherTests) ... Warning -- threading_cleanup() failed to cleanup 1 threads (count: 1, dangling: 2) ``` The following implementation details for the new method are TBD: 1) Public vs private 2) Inclusion in `close()` 3) Name 4) Coroutine vs subroutine method 5) *timeout* parameter If it's a private method, 3, 4, and 5 are significantly less important. I started with the most minimal implementation that fixes the dangling threads without modifying the regression tests, which I think is particularly important. I typically try to avoid directly modifying existing tests as much as possible unless it's necessary to do so. However, I am open to changing any part of this. https://bugs.python.org/issue38356 (cherry picked from commit 0ca7cc7) Co-authored-by: Kyle Stanley <aeros167@gmail.com>
…onGH-16552) Motivation for this PR (comment from @vstinner in bpo issue): ``` Warning seen o AMD64 Ubuntu Shared 3.x buildbot: https://buildbot.python.org/all/#/builders/141/builds/2593 test_devnull_output (test.test_a=syncio.test_subprocess.SubprocessThreadedWatcherTests) ... Warning -- threading_cleanup() failed to cleanup 1 threads (count: 1, dangling: 2) ``` The following implementation details for the new method are TBD: 1) Public vs private 2) Inclusion in `close()` 3) Name 4) Coroutine vs subroutine method 5) *timeout* parameter If it's a private method, 3, 4, and 5 are significantly less important. I started with the most minimal implementation that fixes the dangling threads without modifying the regression tests, which I think is particularly important. I typically try to avoid directly modifying existing tests as much as possible unless it's necessary to do so. However, I am open to changing any part of this. https://bugs.python.org/issue38356
Motivation for this PR (comment from @vstinner in bpo issue):
The following implementation details for the new method are TBD:
Public vs private
Inclusion in
close()Name
Coroutine vs subroutine method
timeout parameter
If it's a private method, 3, 4, and 5 are significantly less important.
I started with the most minimal implementation that fixes the dangling threads without modifying the regression tests, which I think is particularly important. I typically try to avoid directly modifying existing tests as much as possible unless it's necessary to do so. However, I am open to changing any part of this.
https://bugs.python.org/issue38356
Automerge-Triggered-By: @asvetlov