-
Notifications
You must be signed in to change notification settings - Fork 69
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
usb1: Clean up poll fd finalizer #88
Conversation
Thanks for identifying this bug. I think I see a reference loop in the fix, unfortunately: In other uses, the watched object should never be the one holding the finalizer mapping, which should avoid such reference loop. This is also why there are all those "Note: staticmethod" comments everywhere. So while this change is in the right direction, I suspect it could be because the finalizer is never called as the reference loop would keep it alive. This is probably not the whole story, as IIRC the python GC has the ability to break reference loops, and then I do not know what happens to finalizers. I think the following minor change to your fix should allow breaking the loop: c548206 (pushed to a temporary branch). Could you test with this change if this does not cause regressions ? If everything is fine, please feel free to squash my commit into your and push the result to this merge request. |
2e795e7
to
967de54
Compare
It appears to still work with the change. |
TL;DR: could you replace the lambda with a Now, in a lot more details, my train of thoughts. Bikeshed alert ! The reason why several finalizers take a callable as argument is because that finalizer is a method on another class (the class which created the one building the finalizer). Such methods touch protected/private members of their own instances, so such code does not belong on the class which creates the finalizer, and instead these methods are given to be passed on to the finalizer. In the case of Then again, because in this specific case the called code is quite simple, the difference is not much. Here are the alternatives I can identify which should work just as well, from the version I pushed to your current version: My original suggestion, perhaps the least surprising: handle=finalizer_handle,
finalizer_dict=self.__finalizer_dict, Variant which only lets the finalizer access the pop method on finalizer dict, as it really does not need anything else: handle=finalizer_handle,
finalizer_dict_pop=self.__finalizer_dict.pop, Squashing both arguments by building a partial: unregisterFinalizer=partial(self.__finalizer_dict.pop, finalizer_handle), Your current version, using a lambda: finalizer_dict = self.__finalizer_dict
# ...
unregisterFinalizer=lambda: finalizer_dict.pop(finalizer_handle), There is one thing I am worried about with a lambda, but could not reproduce in a few quick tries: I worry it could keep a reference to the functions' stack cell, which has a reference to To be complete, I tend to have a slight preference for the |
There is a bug in
usb1.USBContext.setPollFDNotifiers()
that makes it impossible to be used correctly. During cleanup you always hit an assert.Minimal repro:
Resulting stacktrace:
setPollFDNotifiers()
registers a finalizer, but the finalizer does not remove itself from thefinalizer_dict
. This PR resolves the issue following the same pattern that was used inUSBTranser.__init__()
,USBDeviceHandle.__init__()
andUSBDevice.__init__()
to solve the exact same problem.