[IronPython] object lifecycle issues

William Reade william at resolversystems.com
Tue Jul 21 14:30:53 CEST 2009


Hi Dino

I don't *think* that's the case -- the stub has a __del__ method, that 
should never get called, purely to work around that bug (or possibly a 
subtly different one: I internally characterised it as "an object won't 
get __del__ed unless the type it was created as had a __del__ method, at 
the time it was created", which doesn't *quite* match your example). I 
guess it's one of the ones that never made it onto Codeplex... it's 
there now, at 
http://ironpython.codeplex.com/WorkItem/View.aspx?WorkItemId=23564

The repro for for issue #2 -- which requires current ironclad with 
appropriate logging -- is attached as x.py. The output, which you can 
see a bit further down, shows what happens when you 
construct-then-delete instances of various numpy data types. In short, 
the int8 and float32 dtypes don't include a CLR type among their 
immediate bases, and do get deleted correctly, while the int32 and 
float64 dtypes do and don't respectively.

Anyway, I'll look into your earlier suggestions -- thank you very much!

x.py output (after a lot of noise from the numpy import)
------------------------------------------------------------
========================================

start <class 'numpy.int8'>

real new1 <class 'numpy.int8'>
fake new1 _ironclad_class_stub (<class 'numpy.signedinteger'>,)
fake new2 (object) 2107
fake init <class 'unknown._ironclad_class_stub'> 2107
real new2 <class 'numpy.int8'> 2107

constructed; id is 2107

real del 2107

finished <class 'numpy.int8'>

========================================

start <class 'numpy.int32'>

real new1 <class 'numpy.int32'>
fake new1 _ironclad_class_stub (<class 'numpy.signedinteger'>, <type 'int'>)
fake new2 (int) 2111
fake init <class 'unknown._ironclad_class_stub'> 2111
real new2 <class 'numpy.int32'> 2111

constructed; id is 2111


finished <class 'numpy.int32'>

========================================

start <class 'numpy.float32'>

real new1 <class 'numpy.float32'>
fake new1 _ironclad_class_stub (<class 'numpy.floating'>,)
fake new2 (object) 2116
fake init <class 'unknown._ironclad_class_stub'> 2116
real new2 <class 'numpy.float32'> 2116

constructed; id is 2116

real del 2116

finished <class 'numpy.float32'>

========================================

start <class 'numpy.float64'>

real new1 <class 'numpy.float64'>
fake new1 _ironclad_class_stub (<class 'numpy.floating'>, <type 'float'>)
fake new2 (float) 2121
fake init <class 'unknown._ironclad_class_stub'> 2121
real new2 <class 'numpy.float64'> 2121

constructed; id is 2121


finished <class 'numpy.float64'>
------------------------------------------------------------

...and the internal logging is as follows:

------------------------------------------------------------
        public const string CLASS_STUB_CODE = @"
def __new__(cls, *args, **kwargs):
    print 'fake new1', cls.__name__, cls.__bases__
    if issubclass(cls, int):
        result = int.__new__(cls, args[0])
        print 'fake new2 (int)', id(result)
        return result
    if issubclass(cls, float):
        result = float.__new__(cls, args[0])
        print 'fake new2 (float)', id(result)
        return result
    result = object.__new__(cls)
    print 'fake new2 (object)', id(result)
    return result

def __init__(self, *args, **kwargs):
    print 'fake init', type(self), id(self)

def __del__(self):
    print 'fake del', id(self)

def __setattr__(self, name, value):
    object.__setattr__(self, name, value)

_ironclad_class_stub = _ironclad_metaclass('_ironclad_class_stub', 
_ironclad_bases, {
    '__new__': __new__,
    '__init__': __init__,
    '__del__': __del__,
    '__setattr__': __setattr__,
})
";

        public const string CLASS_CODE = @"
def __new__(cls, *args, **kwargs):
    print 'real new1', cls
    result = cls._dispatcher.newfunc('{0}.tp_new', cls, args, kwargs)
    print 'real new2', cls, id(result)
    return result

def __del__(self):
    print 'real del', id(self)
    self._dispatcher.ic_destroy(self)

_ironclad_class_attrs['__new__'] = __new__
_ironclad_class_attrs['__del__'] = __del__

_ironclad_class = _ironclad_metaclass('{0}', _ironclad_bases, 
_ironclad_class_attrs)
_ironclad_class.__doc__ = '''{2}'''
_ironclad_class.__module__ = '{1}'
";
------------------------------------------------------------

...while the code that actually constructs ipy objects around cpy 
objects looks like this:

------------------------------------------------------------
       
        private void
        ActualiseArbitraryObject(IntPtr ptr)
        {
            IntPtr typePtr = CPyMarshal.ReadPtrField(ptr, 
typeof(PyObject), "ob_type");
            PythonType type_ = (PythonType)this.Retrieve(typePtr);

            object[] args = new object[]{};
            if (Builtin.issubclass(type_, TypeCache.Int32))
            {
                args = new object[] { CPyMarshal.ReadIntField(ptr, 
typeof(PyIntObject), "ob_ival") };
            }
            if (Builtin.issubclass(type_, TypeCache.Double))
            {
                args = new object[] { CPyMarshal.ReadDoubleField(ptr, 
typeof(PyFloatObject), "ob_fval") };
            }
            // ...
           
            object obj = PythonCalls.Call(this.classStubs[typePtr], args);
            Builtin.setattr(this.scratchContext, obj, "__class__", type_);
            // ...
        }
------------------------------------------------------------

BTW: As it happens, the stub class is now a sibling of the real class 
rather than a subclass, because it feels cleaner. Regardless, the 
behaviour is identical in each case.

Cheers
william

Dino Viehland wrote:
> Could this be issue 2?
>
> class Real(object):
>     def __new__(cls, *args, **kwargs):
>         print 'real new'
>         return object.__new__(Stub)
>     #def __del__(self): pass     # uncomment me and this works as expected
>
> class Stub(Real):
>     def __del__(self):
>         print "I've been finalized"
>
> f = Real(1.0)
> del f
>
> import sys
> if sys.platform == 'clr':
>     import clr
>     from System import GC
>     for _ in range(4):
>         GC.Collect()
>         GC.WaitForPendingFinalizers()
>
>
>   
>> -----Original Message-----
>> From: users-bounces at lists.ironpython.com [mailto:users-
>> bounces at lists.ironpython.com] On Behalf Of William Reade
>> Sent: Monday, July 20, 2009 9:38 AM
>> To: Discussion of IronPython
>> Subject: [IronPython] object lifecycle issues
>>
>> Hi all
>>
>> I have two problems that are at least somewhat related:
>>
>> +++ Issue 1 (probably your bug):
>>
>> ---------------------------------------------------------------
>> C:\dev\ironclad - Copy>ipy y.py
>> real new
>> stub new
>> real init
>> real del
>>
>> C:\dev\ironclad - Copy>python y.py
>> real new
>> stub new
>> stub init
>> real del
>> ---------------------------------------------------------------
>>
>> I freely admit that the attached code is pretty weird, but I really do
>> need to do stuff like this in Ironclad. The difference in behaviour may
>> or may not be responsible for certain failing numpy/scipy tests -- I'm
>> not sure how to tell -- but I'd enormously appreciate a fix.
>>
>> I'd report the issue on Codeplex, but trying to visit the issue tracker
>> just leaves my browser spinning forever. Speaking of which: is there
>> any alternative way of reporting bugs that doesn't make me feel as if
>> I'm spamming the list with out-of-band noise? I'm pretty sure that a
>> few bugs have just dropped off my stack in the last few months, just
>> because I got tired of waiting for Codeplex to start working.
>>
>> +++ Issue 2 (almost certainly my bug):
>>
>> In a nearly identical* situation -- close enough that I can't say how
>> it's actually different -- f will never get its __del__ method called
>> (the object is destroyed -- a WeakReference to it knows it's dead --
>> but the __del__ call never happens).
>>
>> For context: I have *very* similar classes, whose instances are
>> constructed in exactly the weird way demonstrated in the attached file,
>> and which work fine. The only difference between the cases that work
>> and the cases that don't is that the broken cases multiply inherit from
>> ipy types which wrap CLR types (int and float (and maybe str, although
>> I haven't tested that one)), while the working cases have simple chains
>> of single inheritance from user-defined types all the way up to object.
>> However, the attached repro doesn't show my problem, so it's clearly
>> not
>> *just* to do with multiply inheriting from CLR types.
>>
>> Does anyone have any idea what I might be doing wrong? I know this is a
>> vague request, but I'm running out of ideas, and I'd really appreciate
>> some input from someone who understands precisely what all those ipy
>> MetaFoo classes are doing.
>>
>> Cheers
>> william
>>
>>
>> * the attached file started life as an attempt to repro the __del__
>> issue, and I incidentally noticed the __init__ issue.
>>     
> _______________________________________________
> Users mailing list
> Users at lists.ironpython.com
> http://lists.ironpython.com/listinfo.cgi/users-ironpython.com
>
>   

-------------- next part --------------
An embedded and charset-unspecified text was scrubbed...
Name: x.py
URL: <http://mail.python.org/pipermail/ironpython-users/attachments/20090721/2ebc1cb5/attachment.ksh>


More information about the Ironpython-users mailing list