Skip to content

weakref reference lets go too soon #3896

Description

@taldcroft

The Table and Column and InfoDescriptor classes use weak references (weakref.ref) in various places to hold a reference to a parent object. For instance a column knows the table it lives in (if it exists), and an InfoDescriptor knows the parent data object that provides information about. We started using weak references at some point because there was a problem with cyclic references not getting garbage-collected #2879.

This was all good, but now I've stumbled onto a problem that seems kind of fundamental in this approach. If an object is created without a permanent reference, then it seems the parent ref is DOA. Presumably the object got garbage-collected before the _parent_ref attribute access occurred (will I'm just speculating here).

In [31]: c = Column([1,2])

In [32]: c.info._parent_ref
Out[32]: <weakref at 0x105ccbe68; to 'Column' at 0x105cc9aa0>

In [33]: c.copy().info._parent_ref
Out[33]: <weakref at 0x105cde050; dead>

In [41]: c[1:2].info._parent_ref
Out[41]: <weakref at 0x105cde628; dead>

In [42]: c[1:2].info  # obscure error message
Out[42]: <repr(<astropy.table.column.ColumnInfo at 0x105c7f190>) failed: AttributeError: 'NoneType' object has no attribute 'info'>

In [43]: c12 = c[1:2]

In [44]: c12.info
Out[44]: 
dtype = int64
class = Column
n_bad = 0
length = 1

This can be seen in other (more well-established) places:

In[18]: t = Table([[1]], names=['a'])
In [19]: print(t.copy()['a'].parent_table)
None

I'm wondering about backing off on using weakref for all parent refs. Python is supposed to handle these cyclic references OK as long as none of the classes have a __del__ method. I can't tell from #2879 if that patch actually reduced memory use for the original problem report.

Activity

  1. embray commented on Jun 26, 2015

    @embray
    Member

    We could give it a try and see if it has any problems with reference cycles loading to memory leaks. I agree it shouldn't be a problem for all cases. My main concern (and maybe I'm misunderstanding--getting pretty 💤) would be cases like:

    t = Table.read(...)
    col = t['colname']
    del t

    Where in the above example the idea is the user only wants to keep one column from the table and junk the rest of the table to be garbage collected. I don't even know if this currently works, but if the only reference to a table is from a Column's _parent_ref I don't think the table should hang around (instead it should run a callback to give the Column a chance to get any last bits of information it needs from the Table, then let it go...)

  2. mhvk commented on Jun 27, 2015

    @mhvk
    Contributor

    @taldcroft -- is this actually a problem? In both cases, the object you create is never used, so isn't it fine for the link to disappear? Anytime I do create an object that stays (as in your c12 example), it seems to work:

    d = c.copy()
    d.info._parent_ref
    # --> <weakref at 0x7f4a52203688; to 'Column' at 0x7f4a52204438>
    
  3. taldcroft commented on Jun 27, 2015

    @taldcroft
    MemberAuthor

    @mhvk - perhaps I'm being too perfectionist... but this did come up for me in just messing around trying to do development work and it really confused me (esp. when it occurs in a repr and you don't get a traceback). Maybe the answer is just to catch the case where the weakref resolves to None and issue an exception that explains that they are using a temporary object and should make a permanent ref instead.

  4. taldcroft commented on Jun 27, 2015

    @taldcroft
    MemberAuthor

    @embray - your example would indeed be problematic, thanks for the insight. I think explicitly deleting the table would be slightly unusual, but having a function that reads a table and returns one column is perfectly natural and would do the same thing.

  5. mhvk commented on Jun 28, 2015

    @mhvk
    Contributor

    @taldcroft - Maybe the simplest for now is simply to add a comment (or docstring??) to _parent_ref, perhaps giving the above examples as a case where "just messing around trying to do development work" can be quite confusing!

  6. embray commented on Jun 29, 2015

    @embray
    Member

    It wouldn't be the only code that breaks in the midst of debugging. I do have a few places where I put in assertions or the like to check for things like None in spots that have bitten me in debugging. (usually with a comment indicating that such a condition shouldn't occur during normal use)

  7. taldcroft commented on Jun 30, 2015

    @taldcroft
    MemberAuthor

    I'm going to split the difference here. For the info._parent_ref I have changed this to a direct reference info._parent:
    taldcroft@f112b35

    There was a real reason, which you can see in that commit if you look at the diffs for io/ascii/html.py. This commit backs out a workaround that I needed (storing objects in a temporary list) in order to force the reference count to stay above zero for new_col objects.

    But we will leave parent_table as a weakref. And with that I am closing the issue. Thanks!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions