-
-
Notifications
You must be signed in to change notification settings - Fork 695
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
Automatic unregistering of BindableProperty
objects
#4122
Automatic unregistering of BindableProperty
objects
#4122
Conversation
Thanks a lot for this pull request, @andybayer! I'm just wondering: Have you tried replacing the |
Yes, I tried replacing the I added the cleanup action as a new value to the |
So what would you suggest as the next step(s) @falkoschindler? Shall I change something? |
…oring references in binding.bindable_properties until explicitly removed
19ffaf0
to
54f096d
Compare
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Hi @andybayer,
Sorry for taking sooo long to get back to you. Let me try to recap where we were:
Assigning a value to a BindableProperty
for the first time stores the owner
object in a bindable_properties
dictionary:
Line 176 in 786e24f
bindable_properties[(id(owner), self.name)] = owner |
For NiceGUI's UI elements this is probably ok, because bindings get cleaned up when removing the UI object. But for custom objects the user must need to call bindings.remove()
.
This is demonstrated with the following demo:
import gc
import weakref
from nicegui import binding, ui
clients: list[weakref.ReferenceType] = []
models: list[weakref.ReferenceType] = []
class Model:
answer = binding.BindableProperty()
def __init__(self) -> None:
self.answer = 42
@ui.page('/')
def main() -> None:
client = ui.context.client
clients.append(weakref.ref(client)) # to count the number of clients
weakref.finalize(client, lambda: print('Client finalized')) # to print when the client is finalized
print(f'Number of clients: {len([c for c in clients if c() is not None])}')
model = Model()
models.append(weakref.ref(model)) # to count the number of models
weakref.finalize(model, lambda: print('Model finalized')) # to print when the model is finalized
print(f'Number of models: {len([m for m in models if m() is not None])}')
ui.button('GC', on_click=gc.collect)
ui.button('Remove bindings', on_click=lambda: binding.remove([m() for m in models if m() is not None]))
print(f'Number of bindings: {len(binding.bindable_properties)}')
ui.run()
- Reloading the page increases both, the number of clients as well as the number of models.
- Running the garbage collector ("GC") reduces the number of clients (need a few seconds to disconnect), but does not affect the number of models.
- Only removing the bindings reduces the number of models as well.
I suggested to resolve this problem by replacing the bindable_properties
dictionary with a set, because we don't really need the dictionary values. For clarity, I sketched this approach in PR #4332.
After pushing this PR, I suddenly understood your problem with this approach: Even though the models are not held in the bindable_properties
dictionary anymore, the set still grows if not pruned via bindings.remove()
. This can be seen in the "Number of bindings" output in the demo above. This number grows rapidly with the number of clients, and even when removing clients via "GC" the number doesn't fall back to where it was.
So finally I fully understand the need for your weakrefs like in your approach. I just refactored the code a little and shorten it here and there. Tests are still green. Let's merge!
# Conflicts: # tests/test_binding.py
First draft to fix the issue reported in #4109.
Replaces the values in the
binding.bindable_properties
data structure, which acts as a "registry" for available bindable properties, withweakref.finalize
objects. Previously, there was a permanent reference to the owner of theBindableProperty
, which was never cleared unless explicitly removed withbinding.remove
.I also added some very basic tests for this behavior. You may need to refactor these slightly.
What I did not test, and what in theory should still be a problem, is when 2 models have bindable properties and one model binds to a value of the other. Then permanent references to the models are kept in
binding.bindings
, which are never automatically cleaned up.