Thanks to visit codestin.com
Credit goes to github.com

Skip to content

CallbackRegistry fix #4118

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

Merged
merged 6 commits into from
Mar 12, 2015
Merged
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Prev Previous commit
Next Next commit
Nits
  • Loading branch information
OceanWolf committed Feb 19, 2015
commit 8864e5c2d22899ca111efcc269eddd299e5ef70c
16 changes: 9 additions & 7 deletions lib/matplotlib/cbook.py
Original file line number Diff line number Diff line change
Expand Up @@ -362,7 +362,7 @@ class _BoundMethodProxy(object):
'''
def __init__(self, cb):
self._hash = hash(cb)
self._callbacks = []
self._destroy_callbacks = []
try:
try:
self.inst = ref(cb.im_self, self._destroy)
Copy link
Member

Choose a reason for hiding this comment

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

What are the life cycle rules on the reference to this object that the ref assigned to self.inst holds?

[edited for clarity]

Copy link
Member Author

Choose a reason for hiding this comment

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

Still not crystal clear, I think you ask about the life-cycle of cb.im_self?

In which case, the same life-cycle as if it weren't here, from the definition of a weakref, it doesn't affect the life-cycle of the object it holds, i.e. as soon as the object looses all non-weakref-references it will get destroyed and GC'ed.

from weakref import ref

class Foo(object):
    pass

def make_ref(var):
    print var # 1st print
    r = ref(var)
    print r # 2nd print
    return r

print make_ref(Foo()) # 3rd print

prints

<__main__.Foo object at 0x7f2600f06650>
<weakref at 0x7f2600ef2e68; to 'Foo' at 0x7f2600f06650>
<weakref at 0x7f2600ef2e68; dead>

because var looses scope as soon as the function ends and thus gets destroyed.

Copy link
Member Author

Choose a reason for hiding this comment

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

Even fuller example:

from weakref import ref

class Foo(object):
    pass

def say_goodbye(wr):
    print 'Goodbye!', wr

def make_ref(var):
    print var
    r = ref(var, say_goodbye)
    print r
    return r

print make_ref(Foo())
print 'Done'

prints

<__main__.Foo object at 0x7ffe2aef0610>
<weakref at 0x7ffe2aedce68; to 'Foo' at 0x7ffe2aef0610>
Goodbye! <weakref at 0x7ffe2aedce68; dead>
<weakref at 0x7ffe2aedce68; dead>
Done

Copy link
Member

Choose a reason for hiding this comment

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

I suspect that I am just being dense here. I am worried about the circular reference that the Proxy objects holds a (hard) ref to the weakref and the weakref holds a (I assume) hard ref back to the object. But I think that is ok as if the weakref dies the call-back will be be triggered and presumably released. However in the case where we lose all refs to the Proxy object, there is the 3 element cycle (proxy.inst -> ref -> proxy._destroy). But, again, I think that is the correct behaviour as if you register a call-back function you expect it to always run and keeping the proxy object around in probably the right move.

Copy link
Member Author

Choose a reason for hiding this comment

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

If I understand you correctly, by object you refer to CallbackRegistry. Proxy holds no hardrefs back to CallbackRegistry, you see in _BoundMethodProxy.add_callback() I used a _BoundMethodProxy, aka weakref back to CallbackRegistry, only level one recursion used here (as we don't add a callback to the callback's callback), so no worries.

In the case where we lose all hardrefs to the object held in the Proxy object, the 3 element-cycle will run, and callback to all the callbacks still there (I added the try except here to deal with that).

In the case where we loose all hardrefs to the proxy object itself, then nothing happens because nothing needs to happen, it has no hardrefs to it so it just gets GC'ed like any other object.

Expand All @@ -379,14 +379,15 @@ def __init__(self, cb):
self.func = cb
self.klass = None

def add_callback(self, callback):
self._callbacks.append(_BoundMethodProxy(callback))
def add_destroy_callback(self, callback):
self._destroy_callbacks.append(_BoundMethodProxy(callback))

def _destroy(self, wk):
for callback in self._callbacks:
for callback in self._destroy_callbacks:
try:
callback(self)
except ReferenceError: pass
except ReferenceError:
pass

def __getstate__(self):
d = self.__dict__.copy()
Expand Down Expand Up @@ -510,7 +511,7 @@ def connect(self, s, func):
if proxy in self._func_cid_map[s]:
return self._func_cid_map[s][proxy]

proxy.add_callback(self._remove_proxy) # Remove the proxy when it dies.
proxy.add_destroy_callback(self._remove_proxy) # Remove the proxy when it dies.
self._cid += 1
cid = self._cid
self._func_cid_map[s][proxy] = cid
Expand All @@ -522,7 +523,8 @@ def _remove_proxy(self, proxy):
for category, proxies in list(six.iteritems(self._func_cid_map)):
try:
del self.callbacks[category][proxies[proxy]]
except KeyError: pass
except KeyError:
pass

def disconnect(self, cid):
"""
Expand Down