Skip to content

better handling in deduperreload for patching functions with freevars #14960

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

Open
wants to merge 1 commit into
base: main
Choose a base branch
from

Conversation

smacke
Copy link
Contributor

@smacke smacke commented Aug 14, 2025

This PR should fix #14958. When patching a decorated function, rather than giving it the union of the old version's and new version's freevars, we give it the old version's for anything callable, and the new version's for everything else. This ensure we use the old __class__ attribute and avoid errors such as "TypeError: super(type, obj): obj must be an instance or subtype of type". Using old versions of other callables is also better since these references are the ones we maintain in deduperreload, rather than the new references. It does, however, mean that we need to reload changed decorated functions twice: once without decorators (to ensure the old version gets patched), and once with. This PR handles that as well.

Test plan: all the other unit tests pass + additionally added a new one which exercises this case.

@smacke smacke force-pushed the fix-deduperreload-freevar-segfault branch from 7d5f1dd to f6fd3f5 Compare August 14, 2025 20:58
Copy link
Member

@krassowski krassowski left a comment

Choose a reason for hiding this comment

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

Verified that this fixes the segfault for me:

image

@smacke smacke force-pushed the fix-deduperreload-freevar-segfault branch from 60060d1 to f6fd3f5 Compare August 15, 2025 23:20
@smacke smacke requested a review from krassowski August 16, 2025 03:06
Copy link
Member

@krassowski krassowski left a comment

Choose a reason for hiding this comment

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

This looks good to me, thank you @smacke. I have one question before merging regarding the pickle vs deepcopy usage.

)
func_asts = [ast.parse(func_code)]
if len(cast(ast.FunctionDef, func_asts[0].body[0]).decorator_list) > 0:
without_decorator_list = pickle.loads(pickle.dumps(func_asts[0]))
Copy link
Member

Choose a reason for hiding this comment

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

Is it preferred over copy.deepcopy? It feels like deepcopy would be faster?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Hey @krassowski thanks for the review and sorry I missed this! It turns out that when it is applicable, composing pickle.loads and pickle.dumps should always be faster than copy.deepcopy, because the former uses a fast C implementation while the latter uses a slower Python implementation for recursively traversing objects. The drawback of pickle / unpickle is that it only works for pickle-able objects, but fortunately Python ASTs happen to be one such type of object.

This is coming from my memory so let me know if you'd prefer to see it for yourself via some benchmarks ;)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

segmentation fault with autoreload due to not updating function __closure__
2 participants