You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
Strange reference cycle (?) introduced with shim frames #100964
<trio.Nursery object at 0x7f3f5585d4f0> [NurseryManager(strict_exception_groups=False), <cell at 0x7f3f5585d540: Nursery object at 0x7f3f5585d4f0>, <frame at 0x7f3f55e220c0, file '/workspaces/trio/trio/_core/_run.py', line 1005, code _nested_child_finished>]
I'm not certain this should be a bug, but raising because I'm confused as to the cause.
changed the title [-]Strange reference cycle introduced with shim frames[/-][+]Strange reference cycle (?) introduced with shim frames[/+]on Jan 12, 2023
I believe that commit slightly changed how/when generators clear their frames upon raising or returning. I forget exactly how, though... I think it might be that the generator used to link, unlink, and clear its own frame in gen_send_ex2 but now all that work happens in the VM? Or something.
Sorry, I think you might be misunderstanding me. In order to minimize stalls, in trio we've put in some extra work to remove reference cycles (to make GC run less often). This new shim frames mechanism adds some reference cycles which probably shouldn't be added -- the news message says "The extra frame should be invisible to all Python and most C extensions." Adding gc.collect() "fixes" this but that's not really the ideal solution for us; users probably shouldn't have to think about GC.
(Pdb) p ref
<built-in method send of coroutine object at 0x104bf6c30>
(Pdb) p gc.get_referrers(ref)[1]
<coroutine object f at 0x104bf6c30>
(Pdb) p gc.get_referrers(gc.get_referrers(ref)[1])[1]
<built-in method send of coroutine object at 0x104bf6c30>
The cycle is r is r.__traceback__.tb_frame.f_back.f_locals["r"]. This cycle keeps the locals, including o, alive.
On 3.11 and earlier, f_back is None. On 3.12, there is a full chain of frames. That's not great, since we're potentially keeping huge graphs of objects alive every time one of these exceptions is stashed away...
defg():
yieldgenerator=g()
frame=generator.gi_frame# None on 3.11, None on 3.12:print(f"Started: {frame.f_back=}")
next(generator)
# None on 3.11, None on 3.12:print(f"Suspended: {frame.f_back=}")
next(generator, None)
# None on 3.11, non-None on 3.12:print(f"Exhausted: {frame.f_back=}")
So, something about transferring ownership of the _PyInterpreterFrame from the exhausted generator to the living frame object re-links it in 3.12.
Activity
[-]Strange reference cycle introduced with shim frames[/-][+]Strange reference cycle (?) introduced with shim frames[/+]sobolevn commentedon Jan 12, 2023
cc @markshannon
brandtbucher commentedon Jan 17, 2023
I believe that commit slightly changed how/when generators clear their frames upon raising or returning. I forget exactly how, though... I think it might be that the generator used to link, unlink, and clear its own frame in
gen_send_ex2but now all that work happens in the VM? Or something.Seems like it could definitely be related.
iritkatriel commentedon Apr 10, 2023
If you add gc.collect() before the print you get "None []" in the output, so there's a cycle but no leak.
A5rocks commentedon Apr 10, 2023
Yup, this issue is just about new cycles being created where there were none before.
The context for the failing testcase is a regression test for python-trio/trio#1770 (which kinda explains why we care about this in the first place).
iritkatriel commentedon Apr 10, 2023
You can add gc.collect() to the test.
A5rocks commentedon Apr 10, 2023
Sorry, I think you might be misunderstanding me. In order to minimize stalls, in trio we've put in some extra work to remove reference cycles (to make GC run less often). This new shim frames mechanism adds some reference cycles which probably shouldn't be added -- the news message says "The extra frame should be invisible to all Python and most C extensions." Adding
gc.collect()"fixes" this but that's not really the ideal solution for us; users probably shouldn't have to think about GC.iritkatriel commentedon Apr 11, 2023
Ok, so I'll label it performance instead of bug.
markshannon commentedon Apr 12, 2023
Note to self:
It is possible that we need to clear
gi_exc_state.exc_valueinclear_gen_frame()iritkatriel commentedon May 18, 2023
Is this our cycle?
GH-100964: Break cycles involving exception state when returning from…
markshannon commentedon Aug 3, 2023
@A5rocks #100964 may fix this issue. Can you test it for me?
I get an error trying to pip install
triowith the latest CPython build.A5rocks commentedon Aug 3, 2023
Unfortunately that didn't seem to work:
(using WSL since atm
import triofails on windows on 3.12 at least on a cffi segfault :( )(as for the previous comment about whether that's the cycle here, I don't know.)
A5rocks commentedon Oct 24, 2023
This was fixed in trio but it turns out that fix was because python-trio/trio@a3d9d25 (specifically due to this change: python-trio/trio@a3d9d25#diff-568b66d9830d9516b524413eb041a1d4c8ccf0b76af4e10d96098888ce323235R937)
... I'll dedicate some more time to hopefully pinning this down because even though this isn't very important it still bothers me!
A5rocks commentedon Oct 25, 2023
hi.
i've found a minimal reproduction that only relies on the standard library. this feels so cursed.
sorry for taking so long.
brandtbucher commentedon Oct 26, 2023
Thanks, this is super helpful. Smaller reproducer that prints
Trueon 3.11 and andFalseon 3.12:brandtbucher commentedon Oct 27, 2023
New minimal reproducer, using a generator instead of
asyncio:The cycle is
r is r.__traceback__.tb_frame.f_back.f_locals["r"]. This cycle keeps the locals, includingo, alive.On 3.11 and earlier,
f_backisNone. On 3.12, there is a full chain of frames. That's not great, since we're potentially keeping huge graphs of objects alive every time one of these exceptions is stashed away...brandtbucher commentedon Oct 27, 2023
New minimal test:
So, something about transferring ownership of the
_PyInterpreterFramefrom the exhausted generator to the living frame object re-links it in 3.12.GH-100964: Fix reference cycle in exhausted generator frames (#141112)
pythonGH-100964: Fix reference cycle in exhausted generator frames (p…
[3.14] GH-100964: Fix reference cycle in exhausted generator frames (G…
[3.13] GH-100964: Fix reference cycle in exhausted generator frames (…