Changeset 844

Show
Ignore:
Timestamp:
03/25/11 16:46:17 (14 months ago)
Author:
stsci_embray
Message:

This should solve the memory leak reported in ticket #49.

After trying several options, I found this to be the simplest solution
requiring the fewest changes that works in the most scenarios.

This solution replaces the internal FITS_rec._coldefs with
FITS_rec.__coldefs which may be a weak reference. FITS_rec._coldefs is
now a property that either returns FITS_rec.__coldefs directly, or, if it's
a weakref, dereferences it and returns the hard reference. Every assignment
to FITS_rec._coldefs that created a reference cycle has been replaced by
assigning FITS_rec._coldefs with a weak reference. But it doesn't have
to be a weakref either. Making FITS_rec._coldefs into a property makes this
transparent.

Another possibility, rather than making _coldefs into a property, would have
been to use weakref.proxy(), but there was a scenario where this wouldn't
have worked:

In #44 I added support for a FITS_rec.columns attribute that gives users
more obvious access to the ColDefs object underlying a FITS_rec (some
users rightly stayed away from using FITS_rec._coldefs directly). I wanted
to preserve this functionality. Making FITS_rec._coldefs a weakref proxy
would have had suprising results for users. For example.

>>> a = pyfits.new_table(...)
>>> c = a.data.columns  # I may want to do something with this later
>>> del a # I don't care about a, or I carelessly let it go out of scope
>>> print c
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
ReferenceError: weakly-referenced object no longer exists
>>> # Oops!  Where did my ColDefs go?

If FITS_rec._coldefs were a proxy there would be no easy way to prevent
this, since I don't think it's possible to get a hard reference directly from
a proxy (without some sort of ugly hack).

So this solution is overall pretty transparent to both users and to most
internal code as well.

Files:
1 modified

Legend:

Unmodified
Added
Removed
  • trunk/lib/core.py

    r833 r844  
    45054505                self._file.seek(self._datLoc) 
    45064506                data = GroupData(_get_tbdata(self)) 
    4507                 data.columns = data._coldefs = self.columns 
     4507                data._coldefs = weakref.ref(self.columns) 
    45084508                data.formats = self.columns.formats 
    45094509                data.parnames = self.columns._pnames 
     
    56675667                                      names=tmp.names, shape=nrows)) 
    56685668 
    5669     hdu.data._coldefs = hdu.columns 
     5669    hdu.data._coldefs = weakref.ref(hdu.columns) 
    56705670    hdu.data.formats = hdu.columns.formats 
    56715671 
     
    58875887        self._nfields = len(self.dtype.names) 
    58885888        self._convert = [None]*len(self.dtype.names) 
    5889         self.columns = self._coldefs = None 
     5889        self.__coldefs = None 
    58905890        self._gap = 0 
    58915891        self.names = list(self.dtype.names) 
     
    59025902        if isinstance(obj, FITS_rec): 
    59035903            self._convert = obj._convert 
    5904             self.columns = self._coldefs = obj._coldefs 
     5904            self.__coldefs = obj.__coldefs 
    59055905            self._nfields = obj._nfields 
    59065906            self.names = obj.names 
     
    59175917            self._file = getattr(obj,'_file', None) 
    59185918 
    5919             self.columns = self._coldefs = None 
     5919            self.__coldefs = None 
    59205920            self._gap = 0 
    59215921            self.names = list(obj.dtype.names) 
     
    59235923            self.formats = None 
    59245924 
    5925             attrs=['_convert', '_coldefs', 'names', '_names', '_gap', 'formats'] 
     5925            attrs=['_convert', '__coldefs', 'names', '_names', '_gap', 
     5926                   'formats'] 
    59265927            for attr in attrs: 
    59275928                if hasattr(obj, attr): 
     
    59315932                    setattr(self, attr, value) 
    59325933 
     5934    def columns(self): 
     5935        """ 
     5936        Users may find it convenient to access the columns through the data 
     5937        object, and might not notice they can since _coldefs is 'private'.  Go 
     5938        ahead and give them read access to it.  (See ticket #44.) 
     5939        """ 
     5940 
     5941        return self._coldefs 
     5942    columns = property(columns) 
     5943 
     5944    def _get_coldefs(self): 
     5945        if isinstance(self.__coldefs, weakref.ReferenceType): 
     5946            return self.__coldefs() 
     5947        else: 
     5948            return self.__coldefs 
     5949 
     5950    def _set_coldefs(self, val): 
     5951        self.__coldefs = val 
     5952    _coldefs = property(_get_coldefs, _set_coldefs) 
     5953 
    59335954    def _clone(self, shape): 
    59345955        """ 
     
    59515972        elif isinstance(key, slice) or isinstance(key,np.ndarray): 
    59525973            out = rec.recarray.__getitem__(self, key) 
    5953             out.columns = out._coldefs = ColDefs(self._coldefs) 
     5974            out._coldefs = ColDefs(self._coldefs) 
    59545975            arrays = [] 
    59555976            out._convert = [None]*len(self.dtype.names) 
     
    65906611                raise TypeError, "table data has incorrect type" 
    65916612 
     6613    #def __del__(self): 
     6614        #for c in self.columns: 
     6615        #    if hasattr(c, 'array'): 
     6616        #        del c.array 
     6617    #    if hasattr(self.data, '_coldefs'): 
     6618    #        del self.data._coldefs 
     6619 
    65926620    def __getattr__(self, attr): 
    65936621        """ 
     
    65996627                self._file.seek(self._datLoc) 
    66006628                data = _get_tbdata(self) 
    6601                 data.columns = data._coldefs = self.columns 
     6629                data._coldefs = weakref.ref(self.columns) 
    66026630                data.formats = self.columns.formats 
    66036631#                print "Got data?" 
     
    79958023                                             formats=",".join(cols._recformats), 
    79968024                                             names=cols.names, shape=nrows)) 
    7997             self.compData._coldefs = self.columns 
     8025            self.compData._coldefs = weakref.ref(self.columns) 
    79988026            self.compData.formats = self.columns.formats 
    79998027 
     
    1116111189    hdulist, _ext = _getext(filename, mode, *ext, **extkeys) 
    1116211190    hdu = hdulist[_ext] 
     11191    # Before hdu goes out of scope, convert the hdu.data._coldefs weakref to a 
     11192    # hard reference 
     11193    if hasattr(hdu.data, '_coldefs'): 
     11194        hdu.data._coldefs = hdu.columns 
    1116311195    _data = hdu.data 
    1116411196    if _data is None and isinstance(_ext, _Zero):