I don't get this error

Posted by jmls on 26-Jan-2011 15:14

I've tracked an error down to a certain line of code. But I can't reproduce it using a clean set of classes. Anyways. what do you think the result of the following should be ?

DEF VAR a AS ValueObject.bar.

DEF VAR b AS ValueObject.foo.

a = lib.foo:Instance:Get(fooGUID):bar.

b = lib.foo:Instance:Get(fooGUID).

MESSAGE valid-object(a) valid-object(b) valid-object(b:bar) VIEW-AS ALERT-BOX.

*Instance in this case is similar to

  DEF STATIC PROPERTY instance AS CLASS lib.foo NO-UNDO     GET:       IF NOT VALID-OBJECT(instance) THEN instance = NEW lib.foo().       RETURN instance.     END GET . PRIVATE SET .

All Replies

Posted by jmls on 27-Jan-2011 05:58

This is driving me nuts. Anyone got a clue ?

Posted by Admin on 27-Jan-2011 06:06

This is driving me nuts. Anyone got a clue ?

Need to see more code. Share a project with the forum...

Posted by jmls on 27-Jan-2011 06:25

Ha! found it. Synopsis to follow ...

Posted by jmls on 27-Jan-2011 06:37

right. Here we go.

The problem was that foo had a destructor method that cleaned up by deleting the bar class property. Take away the destructor and you get "yes yes yes". With the destructor, you get "no yes yes"

Now that I understand what is causing the problem, I can see why this was happening.

in this line of code

a = lib.foo:Instance:Get(fooGUID):bar

a is now associated with the bar object. The foo object returned by Get(fooGUID) is getting GC'd and therefore being deleted

Because bar is still in use (assigned to a) , it is not GC'd and is therefore a valid-object.

However, with the destructor in place, the bar object is being explicitly deleted when the intermediate foo object was deleted by the GC.

So, the lesson learnt for me is leave it to the ABL to clean up the objects ..

Posted by Thomas Mercer-Hursh on 27-Jan-2011 12:07

That or rationalize the explicit clean up ... leaving it to GC is a good way for bar to hang around for a long time because you never get around to cleaning up A.  It is all a question of scoping lifetimes.  GC will eventually cleanup forgotten things, but that doesn't make it a virtue to forget them.

Posted by jmls on 27-Jan-2011 13:26

how would you rationalise this scenario ? the code is

:bar

so if there is a destructor in that deletes object

bar, you run into the problem I had. So you remove the delete object

from the destructor - which means you have to be responsible then for

deleting bar, whereever you "create" it.

However, you have no control over the lifetime of

take it a level further.

bar has a property of class foobar

now, we have a choice of :

/*#1 == 10 lines of code */

def var a as foo

def var b as bar

def var c as foobar

a = lib:getfoo()

b = a:bar

c = b:foobar

message c:name view-as alert-box.

delete object c

delete object b

delete object a

/end/

or

/* #2 == 1 line of code */

message lib:getfoo():bar:foobar:name view-as alert-box.

/end/

now you have 3 temporary objects (foo) and (bar) and (foobar) GC is

the only way to remove these objects

On 27 January 2011 18:07, Thomas Mercer-Hursh

Posted by Thomas Mercer-Hursh on 27-Jan-2011 13:46

I think the issue here is proper object decomposition.  That is pretty hard to evaluate based on objects called foo and bar since one has no idea of their purpose.  Basically, you are defining a scenario in which there is a problem by definition.  The solution is not some magic trick nor should it be reliance on GC.  Rather, it should be to rationalize the decomposition.  Either bar is a child of foo or not.  If it is a child, then foo can be responsible for deleting it.  If it isn't, then foo shouldn't delete it and there needs to be something enclosing which controls the lifetimes.

Step back from these being object for a moment.  Suppose I showed you a design in which foo.p was run persistently and it then ran bar.p and passed the handle of bar.p back to the original calling program.  You would have the same dilemma.  foo.p will have instantiated bar.p, but it can't delete it because it has passed the handle to a higher context.  This violates the "you create it, you delete it" rule because it is not possible to do so responsibly.  So, either foo.p has to run and delete bar.p, which implies pass through methods for the calling program to access bar.p functionality ... we call this delegation in OO ... or the calling program needs to run both foo.p and bar.p itself because it is the only place that knows the lifetimes.  If they were PPs, you couldn't rely on GC, so you shouldn't design to rely on it now.

Posted by Peter Judge on 27-Jan-2011 20:26

tamhas wrote:

That or rationalize the explicit clean up ... leaving it to GC is a good way for bar to hang around for a long time because you never get around to cleaning up A.  It is all a question of scoping lifetimes.  GC will eventually cleanup forgotten things, but that doesn't make it a virtue to forget them.

When dealing with GC, I would look at it in terms more akin to buffer scoping than explicit the create-and-delete cycles of widgets: with static buffers and buffer scoping, you cannot explicity delete the scope, so you need to be aware of what the buffer scope is, and program accordingly. If you've set things up properly with a DEFINE BUFFER or what have you, once you're done with a buffer, you can rest assured that you're done with it and it will no longer concern you.

Same principles with GC for me. If the variables/members/temp-tables that hold references are correctly scoped, then GC is never an issue since an object remains alive for just long enough. If it lives for longer or shorter than my expectations, I assume a bug (in my code or the ABL, not in my expectations ).

Using -noGC is closer to working with dynamic buffers to me.

-- peter

Posted by Thomas Mercer-Hursh on 28-Jan-2011 13:45

I agree with you Peter!  When I emphasize consciousness about lifetimes, it isn't that I am expecting one to micromanage, but rather that things have natural scope and they should live for their natural scope.  If they are gone too early or stick around longer than needed, it is a sign that one isn't paying attention to the natural lifetime in some way ... and it is likely to bite one just as surely as not paying attention to buffer scope.

This thread is closed