procedures , classes, pub/sub and events.

Posted by jmls on 02-Apr-2012 04:52

So, I have a persistent procedure that needs to get some data to a class. And yes, I *have* to use a PP because the ABL does not allow me to set up a read-response method (like, we've had classes for 5+ years now).

In the past, I have passed the class instance as a parameter to the persistent procedure so that the pp can do a

SomeClass:SetData(<<data>>)

However, this is a "Bad Thing" (tm) because the pp has a reference to the instance. A pp is *not* garbage collected, therefore the reference remains until either the pp is deleted or the session ends.

so, if SomeClass creates the pp, and passes itself as a parameter to the pp, then the following statement will create a memory leak:

(new SomeClass()):SomeMethod().

in this case, the destructor will not be called on the "throwaway" class because the pp has a reference to the SomeClass reference. Because the descructor is not called, the pp is never deleted, so the reference is then never released.

the only way round this is to manage the instance lifecycle yourself.

def var Foo as SomeClass.

Foo = new SomeClass().

Foo:SomeMethod().

delete object Foo

which is kinda lame.

so, I need to work out a way of getting the data back to the class.

I can't seem to find a way of using PUB/SUB because classes don't seem to be able to subscribe to a PUB/SUB event in the procedure. WHY?

I can't create a named event in the pp, because it seems that you can only do that in classes. WHY?

The worst workaround right now is to create a static method, get the pp to call that method, which raises an event which the SomeClass subscribes to. This is not pleasant, as there may be more than one instance of SomeClass active at any time.

Urgh. I need a lie down.

All Replies

Posted by jmls on 02-Apr-2012 06:54

I've solved this by splitting the class into two chunks, Foo and bar

Bar is responsible for the persistent procedure and the gathering of data , which it publishes as an event. Any new instance of Foo creates a new bar instance, and subscribes to bar's new data event. In the  Foo destructor , it deletes the bar object.

Because Foo has no reference, it is garbage collected as and when needed. Because the foo destructor is run, and a delete bar instruction executed, the bar object is then also deleted, and in the Bar destructor, the pp is then deleted.

no more leaks (tm)

Posted by Peter Judge on 02-Apr-2012 07:46

jmls wrote:

So, I have a persistent procedure that needs to get some data to a class. And yes, I *have* to use a PP because the ABL does not allow me to set up a read-response method (like, we've had classes for 5+ years now).

In the past, I have passed the class instance as a parameter to the persistent procedure so that the pp can do a

SomeClass:SetData(<>)

However, this is a "Bad Thing" (tm) because the pp has a reference to the instance. A pp is *not* garbage collected, therefore the reference remains until either the pp is deleted or the session ends.

so, if SomeClass creates the pp, and passes itself as a parameter to the pp, then the following statement will create a memory leak:

(new SomeClass()):SomeMethod().

in this case, the destructor will not be called on the "throwaway" class because the pp has a reference to the SomeClass reference. Because the descructor is not called, the pp is never deleted, so the reference is then never released.

the only way round this is to manage the instance lifecycle yourself.

def var Foo as SomeClass.

Foo = new SomeClass().

Foo:SomeMethod().

delete object Foo

which is kinda lame.

Why do you think it lame? GC is not able or meant to intuit the lifecycle of objects from their behaviour. If you want an object to live longer than the GC cycle, then you have to store a reference and manage it yourself.

Does the SomeClass reference need to live for as long as the PP? If not, why not have the PP instantiate it when it receives an event for that object? You could also look at using weak references, but in that case you may have to instantiate a new SomeClass anyway if the original has been GC'ed.

-- peter

Posted by jmls on 02-Apr-2012 07:53

it's lame because

1) I *have* to create a pp to handle a socket response. If I were able to specify a method as a read-response then I would not have to create a pp which has to know the class instance to push the data to, and therefore creates a requirement to have to have a reference to said object, negating the benefits of the GC. Note, the PP only needs to last as long as the class that created it, but that class is not now GC'd because of the refrerence in the pp

2) I cannot publish an ABL event in the pp because the class can't subscribe to them

3) I cannot create a named event in the pp for the class to subscribe to because I am not allowed to create named events in a .p

if any of the above were allowed (especially (1) - why on god's green earth can't we set methods as read-response ?? ) then I would not have to manually manage the life-cycle

Posted by Peter Judge on 02-Apr-2012 09:11

it's lame because

1) I have to create a pp to handle a socket response. If I were able to

specify a method as a read-response then I would not have to create a pp

which has to know the class instance to push the data to, and therefore

creates a requirement to have to have a reference to said object, negating

the benefits of the GC. Note, the PP only needs to last as long as the

class that created it, but that class is not now GC'd because of the

refrerence in the pp

Agree on the need for class-based handlers.

How would you keep the instance alive long enough to process the events, without the GC cleaning it up? You'd have to hold a reference somewhere, and manually delete it, right? Would you expect the existence of the event handler to hold the reference for you(1)?

-- peter

(1) Strongly-typed events do this until explicitly Unsubscribed(), but I'm not sure about a simple PUB/SUB.

Posted by jmls on 02-Apr-2012 09:36

pjudge wrote:

Agree on the need for class-based handlers.

How would you keep the instance alive long enough to process the events, without the GC cleaning it up? You'd have to hold a reference somewhere, and manually delete it, right? Would you expect the existence of the event handler to hold the reference for you(1)?

the instance could be created at the top of a method or procedure that takes some time to run, or has a wait-for. until that method closes , the "anonymous" instance is still active.

method foo:

  (new StompClient()):SendTopic("myapp.monitor.info.process","started")

  /* do stuff */

end method.

instead of

method foo:

  def var x as StompClient no-undo.

  x = new StompClient().

  x:SendTopic("myapp.monitor.info.process","started")

  /* do stuff */

  finally:

   delete object x

  end finally.

end method.

Posted by Peter Judge on 02-Apr-2012 11:09

jmls wrote:

pjudge wrote:

Agree on the need for class-based handlers.

How would you keep the instance alive long enough to process the events, without the GC cleaning it up? You'd have to hold a reference somewhere, and manually delete it, right? Would you expect the existence of the event handler to hold the reference for you(1)?

the instance could be created at the top of a method or procedure that takes some time to run, or has a wait-for. until that method closes , the "anonymous" instance is still active.

method foo:
  (new StompClient()):SendTopic("myapp.monitor.info.process","started")

  /* do stuff */
end method.


instead of



method foo:
  def var x as StompClient no-undo.

  x = new StompClient().
  x:SendTopic("myapp.monitor.info.process","started")

  /* do stuff */

  finally:
   delete object x
  end finally.

end method.


A hack maybe, but could the SendTopic() method not unregister itself from the PP? RUN unsetClassReference in hProcedureEventHandler ?

Posted by jmls on 02-Apr-2012 12:21

Not really, as the socket can receive data as well

Julian

Julian Lyndon-Smith

IT Director

dot.r limited

On Apr 2, 2012 5:10 PM, "Peter Judge"

Posted by gus on 02-Apr-2012 13:47

Sorry, Julian. We are inadequate.

At this time, all of the 4GL constructs that have callbacks (sockets, pub/sub, etc.) do not know how to call /methods/. Someday this will be fixed, but in the current state of the world it is not possible. Mea culpa.

Posted by jmls on 02-Apr-2012 15:10

I think that I can speak on behalf of the community when I say that you're far from inadequate

Knowing why something can't be done more than makes up for not being able to do it. If Gus says it's complicated, then it's complicated for sure.

I've had the problem of classes not being deleted when they should have been, and found a fix that works without actually understanding why it got fixed. I thought that it was a problem with classes and persistent procedures, but could *never* reproduce a simple case so that I could report a bug. However, today I spent hours tracking it down and finally realised what the problem was.

Having done that, it's more than obvious that the issue was being caused by a reference to an instance, and not a bug in Progress at all.

I was just frustrated that my "bug" was (and always has been) created by the workaround that I had to put in because I can't use a method as a callback.

Posted by Thomas Mercer-Hursh on 02-Apr-2012 15:26

And, just because something is complicated, doesn't mean it isn't worth doing!

This kludgy dependence on procedural code is one of the things that stands between where we are and OO being a fully first class solution in ABL.

Posted by jmls on 02-Apr-2012 15:44

the only procedures we write now have now are for starting the progress session, and for callbacks like this.

Posted by Admin on 02-Apr-2012 15:51

the only procedures we write now have now are for starting the progress session, and for callbacks like this.

You are forgetting the entry points into the AppServer.

Posted by jmls on 02-Apr-2012 15:56

yup.

Posted by gus on 02-Apr-2012 16:16

I did not say it was not worth doing, only that we have not.

Posted by Thomas Mercer-Hursh on 02-Apr-2012 16:36

Yes, I know ... I just want to be real sure that the folks who decide such things are *real* clear that those of us out here who use OO *really* want it.

It is not the only thing on the list, but it is one of those things which is painful to explain to someone from another background.

This thread is closed