Movatterモバイル変換


[0]ホーム

URL:


Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Sign up
Appearance settings

gh-127750: Improve caching in singledispatchmethod#127751

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 ourterms of service andprivacy statement. We’ll occasionally send you account related emails.

Already on GitHub?Sign in to your account

Closed
vodik wants to merge1 commit intopython:mainfromvodik:fix-issue-12345

Conversation

@vodik
Copy link

@vodikvodik commentedDec 9, 2024
edited
Loading

The problem with using aWeakRefDictionary is it's possible for distinct object instances to collide in the cache depending on their implementation of__eq__/__hash__. In particular, it prevents it from being used at all on Django models.

@ghost
Copy link

ghost commentedDec 9, 2024
edited by ghost
Loading

All commit authors signed the Contributor License Agreement.
CLA signed

@vodik
Copy link
Author

vodik commentedDec 9, 2024
edited
Loading

I'm not an expert atweakref's, but is this even the right thing to use here? Wouldn't the closure we're caching hold a reference toobj alive?

def_method(*args,**kwargs):ifnotargs:raiseTypeError(f'{funcname} requires at least ''1 positional argument')returndispatch(args[0].__class__).__get__(obj,cls)(*args,**kwargs)

Wouldn't this hold a strong reference toobj and thus prevent either the originalWeakKeyDictionary or my changed approach from cleaning upobj?

@vodikvodik changed the titlegh-85160: Improve cacheing in singledispatchmethodgh-127750: Improve cacheing in singledispatchmethodDec 9, 2024
@vodikvodikforce-pushed thefix-issue-12345 branch 2 times, most recently from2e08bd9 toa81b83eCompareDecember 9, 2024 02:26
@vodik
Copy link
Author

vodik commentedDec 9, 2024
edited
Loading

Yes! Tracing through the logic manually, I can confirm that theweakref's callback does't fireunless I break the strong reference in_method.

So there's a bit of a space leak here (I assume Python's garbage collector might be able to figure it out though?)

I just put up a different that tried to solve this problem as well, but deviates further from the original. If we settle on an approach I'll figure out how to write tests...

'1 positional argument')
# Make sure to use the weakref here to prevent storing
# a strong reference to obj in the cache
returndispatch(args[0].__class__).__get__(obj_ref(),cls)(*args,**kwargs)
Copy link
Author

@vodikvodikDec 9, 2024
edited
Loading

Choose a reason for hiding this comment

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

Doesn't this introduce a data race though? If I assigned the method and then deleted the underlying obj? I'll have to try it.

Is a weakref even appropriate for this cache then? Or should I throw aReferenceError?

-                return dispatch(args[0].__class__).__get__(obj_ref(), cls)(*args, **kwargs)+                obj = obj_ref()+                if obj is None:+                    raise ReferenceError++                return dispatch(args[0].__class__).__get__(obj, cls)(*args, **kwargs)

Or is this whole approach maybe not correct enough?

Copy link
Member

@sobolevnsobolevn left a comment

Choose a reason for hiding this comment

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

Please, provide a regression test.

@eendebakpt
Copy link
Contributor

If the main issue is that different objects are colliding in the cache (due to equal__hash__ and__eq__ being equal), perhaps the only solution is to remove the cache altogether (e.g. revert#107148). Using theid in the cache can also cause collisions (for objects with non-overlapping life timeshttps://docs.python.org/3/library/functions.html#id)

AlexWaygood and vodik reacted with thumbs up emojivodik reacted with eyes emoji

@AlexWaygood
Copy link
Member

Unless we can find a solution that is both robust and has less complexity than this patch, I'm inclined to agree with@eendebakpt — I think a clean revert would be better. It's sad, but it may be the best we can do :(

@vodik
Copy link
Author

vodik commentedDec 9, 2024
edited
Loading

I'm coming to the same conclusion. This patch now makes this code break:

foo=Foo()dispatcher=foo.mysingledismatchmethoddelfoodispatcher(...)# fails

What if we revert#107148 and I instead contribute a test case specifically for this situation? That'll help if someone wants to tackle this again. I personally wasn't worried about the performance of this feature.

Either way I'll give it another attempt tonight. Maybe there's another approach that can work.

@AlexWaygood
Copy link
Member

Possibly one way to make this work would be to use theid for the key but useweakref.finalize to register a callback for objects whoseids are stored in the cache such that the objects are evicted from the cache when they're garbage collected

@AlexWaygood
Copy link
Member

But yes, whether we think of a clever solution or just revert the feature, we should clearly add a regression test.

@AlexWaygoodAlexWaygood changed the titlegh-127750: Improve cacheing in singledispatchmethodgh-127750: Improve caching in singledispatchmethodDec 9, 2024
@vodik
Copy link
Author

vodik commentedDec 9, 2024
edited
Loading

@AlexWaygood Weakref aside, just to be clear, while working on this patch I found another problem with the caching - that the closure put in the cache also holds a reference to the same obj we key the cache with. So it's circular and won't cleanup. There's a memory leak here too.

I can't think of a way to breakthat without breaking the semantics of the language. And I think that's the deeper problem that requires this to be reverted rather than fixed. I don't think there is a fix for this.

AlexWaygood reacted with thumbs up emoji

@eendebakpt
Copy link
Contributor

One more try: what if we change the cache to aWeakValueDictionary, cache based on theid of the object and modify the definition of_method to

        def _method(*args, **kwargs):            if not args:                raise TypeError(f'{funcname} requires at least '                                '1 positional argument')            return dispatch(args[0].__class__).__get__(obj, cls)(*args, **kwargs)                _method.__isabstractmethod__ = self.__isabstractmethod__        _method.register = self.register        update_wrapper(_method, self.func)        _method._self_ref = (obj, _method) # holds a strong reference to the value _method in the WeakValueDictionary
  • The caching based onid avoids the collisions in the issue report
  • As long as the user has references to the objectobj, the tuple_self_ref ensures there is a strong reference to the dictionary value, so the value is kept in theWeakValueDictionary
  • Once the only references toobj are internal in the_method, the garbage collector kicks in and removes the object
  • Since_obj and_method are collected at the same time, theid(obj) is a safe key to use.

It seems to work locally for me, but definitely needs a second look. I did not measure performance yet.

vodik reacted with thumbs up emoji

@rhettingerrhettinger removed their request for reviewDecember 10, 2024 05:31
@ZeroIntensityZeroIntensity added the needs backport to 3.13bugs and security fixes labelDec 11, 2024
Copy link
Member

@ZeroIntensityZeroIntensity left a comment

Choose a reason for hiding this comment

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

Before we discuss approaches, I'd like to get a test down to make sure things are working.

@eendebakpt
Copy link
Contributor

Before we discuss approaches, I'd like to get a test down to make sure things are working.

In#127839 I added tests for the two issues observed here. They are in commits6c6e8b8 andef3f8c9

ZeroIntensity and vodik reacted with thumbs up emoji

@vodik
Copy link
Author

It seems reasonable to me too.

@vodik
Copy link
Author

#127839 is a better approach

@vodikvodik closed thisDec 12, 2024
Sign up for freeto join this conversation on GitHub. Already have an account?Sign in to comment

Reviewers

@sobolevnsobolevnsobolevn left review comments

@ZeroIntensityZeroIntensityZeroIntensity left review comments

Assignees

No one assigned

Labels

Projects

None yet

Milestone

No milestone

Development

Successfully merging this pull request may close these issues.

5 participants

@vodik@eendebakpt@AlexWaygood@sobolevn@ZeroIntensity

[8]ページ先頭

©2009-2025 Movatter.jp