bpo-37788: Fix reference leak when Thread is never joined#26103
bpo-37788: Fix reference leak when Thread is never joined#26103miss-islington merged 1 commit intopython:mainpython/cpython:mainfrom pitrou:bpo-37788-thread-join-leakpitrou/cpython:bpo-37788-thread-join-leakCopy head branch name to clipboard
Conversation
|
Note the unit test is originally from PR #25226. |
|
I started a buildbot run here: https://buildbot.python.org/all/#/changes/4460 (edit: previous run was on a bogus changeset) |
|
I also ran @vstinner 's regression test and it seems to work fine here: |
When a Thread is not joined after it has stopped, its lock may remain in the _shutdown_locks set until interpreter shutdown. If many threads are created this way, the _shutdown_locks set could therefore grow endlessly. To avoid such a situation, purge expired locks each time a new one is added or removed.
dc79edc to
16edc6a
Compare
| # does not leak. Test written for reference leak checks. | ||
| def noop(): pass | ||
| with threading_helper.wait_threads_exit(): | ||
| threading.Thread(target=noop).start() |
There was a problem hiding this comment.
Would you mind to add a test case that the target function isn't just only the pass
There was a problem hiding this comment.
Hmm, what do you mean exactly? I'm not sure I understand your suggestion.
There was a problem hiding this comment.
Oh, sorry. I didn't explain it clearly. I suggest that the target of Thread could be more complex ;)
Something like:
def noop():
# we don't what code will run in the thread in fact.
time.sleep(random.random())
with threading_helper.wait_threads_exit():
threading.Thread(target=noop).start()There was a problem hiding this comment.
But what would that change? What matters is what happens after the thread ends.
There was a problem hiding this comment.
Hm, give me a second.
No matter how complex of the target of thread, the thread will finish in time in python level. So we don't adding a complex testcase in here is fine. right?
Thanks for checking with the buildbots! Seems that the failures are unrelated to this, so I think we are good to go |
|
Ok, merging then. |
|
Thanks @pitrou for the PR 🌮🎉.. I'm working now to backport this PR to: 3.10, 3.8, 3.9. |
|
Sorry, @pitrou, I could not cleanly backport this to |
|
GH-26138 is a backport of this pull request to the 3.10 branch. |
|
Sorry @pitrou, I had trouble checking out the |
…6103) When a Thread is not joined after it has stopped, its lock may remain in the _shutdown_locks set until interpreter shutdown. If many threads are created this way, the _shutdown_locks set could therefore grow endlessly. To avoid such a situation, purge expired locks each time a new one is added or removed. (cherry picked from commit c10c2ec) Co-authored-by: Antoine Pitrou <antoine@python.org>
|
Thanks for the fix @pitrou, it looks simple :-) |
…onGH-26103) When a Thread is not joined after it has stopped, its lock may remain in the _shutdown_locks set until interpreter shutdown. If many threads are created this way, the _shutdown_locks set could therefore grow endlessly. To avoid such a situation, purge expired locks each time a new one is added or removed.. (cherry picked from commit c10c2ec) Co-authored-by: Antoine Pitrou <antoine@python.org>
…26103) (GH-26138) When a Thread is not joined after it has stopped, its lock may remain in the _shutdown_locks set until interpreter shutdown. If many threads are created this way, the _shutdown_locks set could therefore grow endlessly. To avoid such a situation, purge expired locks each time a new one is added or removed. (cherry picked from commit c10c2ec) Co-authored-by: Antoine Pitrou <antoine@python.org>
…6103) (GH-26142) When a Thread is not joined after it has stopped, its lock may remain in the _shutdown_locks set until interpreter shutdown. If many threads are created this way, the _shutdown_locks set could therefore grow endlessly. To avoid such a situation, purge expired locks each time a new one is added or removed.. (cherry picked from commit c10c2ec) Co-authored-by: Antoine Pitrou <antoine@python.org> Automerge-Triggered-By: GH:pitrou
|
|
Hey there! |
Note that the previous code was not thread-safe. A thread-safe version would have been:
for t in list(threads):
if not t.is_alive():
# A set is used for `threads`, because `set.discard()` is O(1) but `list.remove()` is O(n).
threads.discard(t)
# Python 3.8 and below has a memory leak. python/cpython#26103
t.join()
…thon/cpython#26103" This reverts commit cd4a74a.
When a Thread is not joined after it has stopped, its lock may remain in the _shutdown_locks set until interpreter shutdown. If many threads are created this way, the _shutdown_locks set could therefore grow endlessly. To avoid such a situation, purge expired locks each time a new one is added or removed.
https://bugs.python.org/issue37788
Automerge-Triggered-By: GH:pitrou