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
Use weak references in AggregateSensor #51
Use weak references in AggregateSensor #51
Conversation
This will allow aggregates to die once they're no longer part of a SensorSet or otherwise referenced, even if the sensors they aggregate are still live.
The closure they returned referenced `self`. Copying the original function into a local variable should prevent that.
This tests the otherwise-unused code paths in _weak_callback.
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.
So I'll confess to still not quite understanding why this works. I decided to check whether it's necessary by running the unit tests without your weakref modifications, and I get the following:
def test_aggregate_garbage_collection(self, ss, sensors):
"""Check that the aggregate can be garbage collected."""
# Don't use the agg_sensor fixture, because pytest will hold its own
# references to it.
my_agg = MyAgg(target=ss, sensor_type=int, name="garbage")
ss.add(sensors[1])
weak = weakref.ref(my_agg)
del my_agg
# Some Python implementations need multiple rounds to garbage-collect
# everything.
for _ in range(5):
gc.collect()
> assert weak() is None # i.e. my_agg was garbage-collected
E AssertionError: assert <test_sensor_updated.MyAgg object at 0x7fba41553c70> is None
E + where <test_sensor_updated.MyAgg object at 0x7fba41553c70> = <weakref at 0x7fba414eef90; to 'MyAgg' at 0x7fba41553c70>()
So clearly the changes are necessary. It's going to take me a while to understand why though, and I don't want to hold up the process.
|
||
@functools.wraps(self._func) | ||
def wrapper(*args, **kwargs): | ||
strong_instance = weak_instance() |
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.
This line reads rather weirdly.
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.
The __call__
on a weakref gives you the object it's referencing (or None if it is dead).
tests/test_sensor.py
Outdated
# Don't use the fixtures, because they have mocks that might | ||
# record things and keep them alive. | ||
# The noqa is to suppress | ||
# "local variable '_my_agg' is assigned to but never used" |
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.
# "local variable '_my_agg' is assigned to but never used" | |
# "local variable 'my_agg' is assigned to but never used" |
Either that or add an underscore to the name of the variable that you create? I don't have a preference, they should probably just be consistent.
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.
Good catch - I'd tried using a leading underscore in case it shut up flake8 (it didn't) and copy-pasted the error from that version. Fixed now.
In case it helps: without the code changes, |
This ensures that an aggregate sensor can be garbage collected once it's no longer explicitly referenced, even if its target sensor set is still live. It also makes
__del__
more robust to exceptions occurring during__init__
.