Uh oh!
There was an error while loading. Please reload this page.
- Notifications
You must be signed in to change notification settings - Fork 34k
gh-135552: Make the GC clear weakrefs later#136189
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
Uh oh!
There was an error while loading. Please reload this page.
Conversation
nascheme commented Jul 1, 2025 • edited
Loading Uh oh!
There was an error while loading. Please reload this page.
edited
Uh oh!
There was an error while loading. Please reload this page.
Clear the weakrefs to unreachable objects after finalizers are called.
neonene commented Jul 1, 2025
I can confirm this PR fixes the gh-132413 issue as well. |
nascheme commented Jul 1, 2025
I think this fixes (or mostly fixes) gh-91636 as well. |
bedevere-bot commented Jul 1, 2025
🤖 New build scheduled with the buildbot fleet by @nascheme for commit 12f0b5c 🤖 Results will be shown at: https://buildbot.python.org/all/#/grid?branch=refs%2Fpull%2F136189%2Fmerge If you want to schedule another build, you need to add the 🔨 test-with-buildbots label again. |
nascheme commented Jul 2, 2025
This introduces refleaks, it seems. One of the leaking tests: |
nascheme commented Jul 2, 2025
This is the smallest leaking example I could make so far. Something with |
neonene commented Jul 2, 2025 • edited
Loading Uh oh!
There was an error while loading. Please reload this page.
edited
Uh oh!
There was an error while loading. Please reload this page.
Other leaking examples (on Windows): 1. test_logging:importloggingimportlogging.configimportlogging.handlersfrommultiprocessingimportQueue, ManagerclassConfigDictTest(unittest.TestCase): deftest_multiprocessing_queues_XXX(self): config={'version': 1, 'handlers' :{'spam' :{'class': 'logging.handlers.QueueHandler', 'queue': Manager().Queue() , # Leak# 'queue': Manager().JoinableQueue() # Leak# 'queue': Queue(), # No leak }, }, 'root':{'handlers': ['spam']} } logger=logging.getLogger() logging.config.dictConfig(config) whilelogger.handlers: h=logger.handlers[0] logger.removeHandler(h) h.close()2. test_interpreters.test_api:importcontextlibimportthreadingimporttypesfromconcurrentimportinterpretersdeffunc(): raiseException('spam!') @contextlib.contextmanagerdefcaptured_thread_exception(): ctx=types.SimpleNamespace(caught=None) defexcepthook(args): ctx.caught=argsorig_excepthook=threading.excepthookthreading.excepthook=excepthooktry: yieldctxfinally: threading.excepthook=orig_excepthookclassTestInterpreterCall(unittest.TestCase): deftest_call_in_thread_XXX(self): interp=interpreters.create() call= (interp._call, interp.call)[1] # 0: No leak, 1: Leakwithcaptured_thread_exception() as_: t=threading.Thread(target=call, args=(interp, func, (),{})) t.start() t.join()UPDATE: importweakref, _interpreterswd=weakref.WeakValueDictionary() classTestInterpreterCall(unittest.TestCase): deftest_call_in_thread_XXX(self): id=_interpreters.create() wd[id] =type("", (),{}) _interpreters.destroy(id) |
nascheme commented Jul 2, 2025
The majority (maybe all) of these leaks are caused by the |
This avoids breaking tests while fixing the issue with tp_subclasses. In the long term, it would be better to defer the clear of all weakrefs, not just the ones referring to classes. However, that is a more distruptive change and would seem to have a higher chance of breaking user code. So, it would not be something to do in a bugfix release.
nascheme commented Jul 3, 2025
Nope, that doesn't fix all the leaks. And having to explicitly clean the weakrefs from the WeakValueDictionary really shouldn't be needed, I think. The For the purposes of having a fix that we can backport (should probably be backported to all maintained Python versions), a less disruptive fix would be better. To that end, I've changed this PR to only defer clearing weakrefs to class objects. That fixes the |
bedevere-bot commented Jul 3, 2025
🤖 New build scheduled with the buildbot fleet by @nascheme for commit 2f3daba 🤖 Results will be shown at: https://buildbot.python.org/all/#/grid?branch=refs%2Fpull%2F136189%2Fmerge If you want to schedule another build, you need to add the 🔨 test-with-buildbots label again. |
We need to clear those before executing the callback. Since this ensures they can't run a second time, we don't need _PyGC_SET_FINALIZED(). Revise comments.
nascheme commented Jul 3, 2025
Ah, the I revised this PR to be something that is potentially suitable for backporting. To minimize the behaviour change, I'm only deferring the clear of weakrefs that refer to types (in order to allow tp_subclasses to work) or with callbacks. I still have an extra clearing pass that gets done after the finalizers are called. That avoids bugs like #91636. If this gets merged, I think we should consider deferring clearing all weakrefs (without callbacks) until after finalizers are executed. I think that's more correct since it gives the finalizers a better opportunity to do their job. |
Uh oh!
There was an error while loading. Please reload this page.
Uh oh!
There was an error while loading. Please reload this page.
| * *not* be cleared so that caches based on the type version are correctly | ||
| * invalidated (see GH-135552 as a bug caused by this). | ||
| */ | ||
| clear_weakrefs(&final_unreachable); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ough! Nice trick with final_unreachable!
This is a bit trickly because the wrlist can be changing as we iterate over it.
nascheme commented Jul 8, 2025
GH-136401 was merged. I think it should be backported as well since it should be pretty low risk. The main branch has been merged into this PR. I've updated the PR to defer clearing all weakrefs without callbacks (not just weakrefs to classes). This seems more correct to me (rather than special casing weakrefs to classes) and seems the best way to fix GH-132413. I think GH-132413 is actually more likely to be encountered in real code. For example, if you have some logging function in a |
Uh oh!
There was an error while loading. Please reload this page.
sergey-miryanov commented Jul 9, 2025
Looks good to me. Thanks for detailed comments! |
Uh oh!
There was an error while loading. Please reload this page.
Uh oh!
There was an error while loading. Please reload this page.
Uh oh!
There was an error while loading. Please reload this page.
Uh oh!
There was an error while loading. Please reload this page.
nascheme commented Jul 14, 2025
@pablogsal are you planning to take another look at this? If so, no problem, there is no great rush. If not, I think I'll do another review pass myself and then merge. |
pablogsal commented Jul 14, 2025 • edited
Loading Uh oh!
There was an error while loading. Please reload this page.
edited
Uh oh!
There was an error while loading. Please reload this page.
Yeah i have it pending for this week (sorry fixing a lot of bugs for 3.14 currently) but if you feel I am taking too much time I am happy if you go ahead I don't want to block it on this |
sergey-miryanov commented Jul 14, 2025
@nascheme I see you updated comment for Would you mind also removing another one from Lines 572 to 576 in 9363703
|
efimov-mikhail commented Jul 14, 2025
Do we want to merge this PR w/o tests on subclasses destruction from #135552? |
nascheme commented Jul 14, 2025
Sergey has tests in another PR and I'll merge that right after I merge this one. |
Uh oh!
There was an error while loading. Please reload this page.
Consolidate most of the comments related to handling of weakrefs. This avoids having the information repeated in different comments.
350c58b into python:mainUh oh!
There was an error while loading. Please reload this page.
nascheme commented Aug 7, 2025
@pablogsal I'm merging this since I want to get it into the alpha release. However, if you have time to make another review, that would still be appreciated. |
These are tests to ensure behaviour introduced by GH-136189 is working as expected. Co-authored-by: Mikhail Borisov <43937008+fxeqxmulfx@users.noreply.github.com> Co-authored-by: Kumar Aditya <kumaraditya@python.org> Co-authored-by: Neil Schemenauer <nas-github@arctrix.com>
sergey-miryanov commented Aug 8, 2025
Hooray! Thanks all! |
Fix a bug caused by the garbage collector clearing weakrefs too early. The weakrefs in the ``tp_subclasses`` dictionary are needed in order to correctly invalidate type caches (for example, by calling ``PyType_Modified()``). Clearing weakrefs before calling finalizers causes the caches to not be correctly invalidated. That can cause crashes since the caches can refer to invalid objects. Defer the clearing of weakrefs without callbacks until after finalizers are executed.
These are tests to ensure behaviour introduced by pythonGH-136189 is working as expected. Co-authored-by: Mikhail Borisov <43937008+fxeqxmulfx@users.noreply.github.com> Co-authored-by: Kumar Aditya <kumaraditya@python.org> Co-authored-by: Neil Schemenauer <nas-github@arctrix.com>
Fix two bugs related to the interaction of weakrefs and the garbage
collector. The weakrefs in the
tp_subclassesdictionary are needed inorder to correctly invalidate type caches (for example, by calling
PyType_Modified()). Clearing weakrefs before calling finalizers causesthe caches to not be correctly invalidated. That can cause crashes since the
caches can refer to invalid objects. This is fixed by deferring the
clearing of weakrefs to classes and without callbacks until after finalizers
are executed.
The second bug is caused by weakrefs created while running finalizers. Those
weakrefs can be outside the set of unreachable garbage and therefore survive
the
delete_garbage()phase (wheretp_clear()is called on objects).Those weakrefs can expose to Python-level code objects that have had
tp_clear()called on them. See GH-91636 as an example of this kind ofbug. This is fixed be clearing all weakrefs to unreachable objects after
finalizers have been executed.
datetime.timedelta(possibly from datetime's Cdelta_new) #132413