◐ Shell
reader mode source ↗
Skip to content

gh-129280: Avoid cyclic reference in contextlib.contextmanager#129276

Draft
user202729 wants to merge 1 commit into
python:mainfrom
user202729:contextmanager-avoid-cyclic-ref
Draft

gh-129280: Avoid cyclic reference in contextlib.contextmanager#129276
user202729 wants to merge 1 commit into
python:mainfrom
user202729:contextmanager-avoid-cyclic-ref

Conversation

@user202729

@user202729 user202729 commented Jan 25, 2025

Copy link
Copy Markdown

By deleting the cyclic reference between the frame object and the exception object, this allows the objects to be deleted more quickly (it doesn't need to wait until the next GC cycle)

This also has the practical impact of some packages (cysignals) using reference counting to detect whether the exception has been handled, which breaks in the presence of this situation. sagemath/sage#39142

@user202729 user202729 requested a review from 1st1 as a code owner January 25, 2025 03:12
@ghost

ghost commented Jan 25, 2025

Copy link
Copy Markdown

All commit authors signed the Contributor License Agreement.
CLA signed

@bedevere-app

bedevere-app Bot commented Jan 25, 2025

Copy link
Copy Markdown

Most changes to Python require a NEWS entry. Add one using the blurb_it web app or the blurb command-line tool.

If this change has little impact on Python users, wait for a maintainer to apply the skip news label instead.

@user202729 user202729 mentioned this pull request Jan 25, 2025
5 tasks

@ZeroIntensity ZeroIntensity left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Hide comment

At an overview:

  • This seems issue-worthy to me; could you make one, and then link to it in the PR title?
  • Per the bot, we do need a blurb entry, as this is a user-facing change.
  • A test case would be good too (see the devguide).

However, going back to the use-case:

This also has the practical impact of some packages (cysignals) using reference counting to detect whether the exception has been handled

I'd be very careful with that. We don't have any guarantees about reference counting, especially with built-in objects. (For example, on the free-threaded build, we have plenty of reference counting hacks that allow for multithreaded scalability--I suspect this kind of thing would break over there.)

@user202729

user202729 commented Jan 25, 2025

Copy link
Copy Markdown
Author

We don't have any guarantees about reference counting,

I know that very well. But…

6 years ago, there was a difficult to debug issue with SageMath sagemath/sage#24986 , and the developers concluded using that hacky reference counting solution is the best available workaround.

Turns out it causes additional issues later: sagemath/sage#39224

There exists a better solution, see sagemath/cysignals#215 (comment) , but while it is not yet implemented (there are a lot of places that need to be changed in the code base) I try to make the best of what is available.

If the developers originally implemented the proper solution, we wouldn't be in this situation. But removing the workaround entirely before implementing the proper solution seems impractical at this point.


(in the meantime, I'll try to figure out how to write test case and blurb, but I'd appreciate any help if possible)

@user202729 user202729 changed the title Avoid cyclic reference in contextlib.contextmanager Jan 25, 2025
@ZeroIntensity

Copy link
Copy Markdown
Member

(in the meantime, I'll try to figure out how to write test case and blurb, but I'd appreciate any help.)

Feel free to tag me if you need any help. The devguide should have everything you need, though :)

@user202729 user202729 marked this pull request as draft February 4, 2025 11:31
@user202729

Copy link
Copy Markdown
Author

Feel best if I leave this as draft until I figure out what's the root cause of the change in behavior Python 3.11 and Python 3.12.

@user202729

user202729 commented Feb 4, 2025

Copy link
Copy Markdown
Author

Bisecting results in 1e197e6 being the first bad commit.

Seems to make sense…? If more references to frames are added then it's more likely to create cyclic references…?

issue: #96421
pull request: #96319

@user202729

Copy link
Copy Markdown
Author

More investigation shows the frame object has f_back cleared when the function exits in Python 3.11, while this is not the case in Python 3.12.

I haven't fully understood what the linked pull request does yet, but maybe if only ensure the shim frame is cleared without clearing the actual frame…?

@markshannon Any idea about this?

Code
from contextlib import contextmanager

class MyException(Exception):
	def __del__(self):
		print(self.__traceback__.tb_frame.f_back)
		print("deleted")

@contextmanager
def f():
	try:
		yield
	except MyException as e:
		global g
		g = e
		print(e.__traceback__.tb_frame.f_back)

with f():
	raise MyException()

print(g.__traceback__.tb_frame.f_back)  # ⟵ this prints None in Python 3.11 and <frame…> in Python 3.12

print("done")

@serhiy-storchaka

Copy link
Copy Markdown
Member

I think that this PR (which likely will be closed in favor of more general solution) is not the best place for discussion.

@github-actions

Copy link
Copy Markdown

This PR is stale because it has been open for 30 days with no activity.

@github-actions github-actions Bot added the stale Stale PR or inactive for long period of time. label Apr 21, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

stale Stale PR or inactive for long period of time.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

5 participants