can we rely on when garbage collection fires?

Posted by jankeir on 15-Nov-2018 16:06

I would like to know if there is any documentation on when garbage collection fires. We have a quite a bit of code that still relies heavily on persistent procedures. 

Years ago I wrote a class to start the persistent procedure passed in as a character in the constructor and has a destructor to stop them, the advantage of this is that if you forget to delete it it does not stay around consuming memory but instead is cleaned up by garbage collection. You can use it like this:

myProc = new safelib("someproc.p") . 

run someinternalprocedure in myProc:getHandle(). 

As soon as myProc goes out of scope the safelib destructor fires and cleans up the procedure. 

Now this is all nice but you have to define myProc and if you need it for only one internal proc this also works:

run someinternalprocedure in new safelib("someproc.p"):getHandle().

This works right now because the destructor for the object created by safelib only fires after the internal procedure has been called. Strictly speaking though I think it would be possible for progress to GC the object immediately after calling getHandle() before the internal procedure is called in the future. 

So I'm looking for some documentation to see if what I suggest here can be relied upon or if this behavior could change in the future. 

Posted by Laura Stern on 15-Nov-2018 16:42

Ah.  No, I wouldn't count on that at all.  Much better to new the object, store the reference in a variable and then set it to ? after you get back from the RUN statement.

All Replies

Posted by Laura Stern on 15-Nov-2018 16:15

I'm not sure I really understand your concern.  Your safelib class will stay around as long as you have a reference to it.  Once you don't, you cannot count on it being there anymore.  You are in control of this.

Posted by jankeir on 15-Nov-2018 16:35

I mean in this case: run someinternalprocedure in new safelib("someproc.p"):getHandle().

Can I rely on the safelib that is being instantiated here to stay around until after the run someinternalprocedure? The handle that is obtained from the object is the persistent procedure handle, however if the object would ever be garbage collected immediately after 'getHandle' but before run someInternalprocedure it would delete that persistent procedure handle.

Posted by Laura Stern on 15-Nov-2018 16:42

Ah.  No, I wouldn't count on that at all.  Much better to new the object, store the reference in a variable and then set it to ? after you get back from the RUN statement.

Posted by jankeir on 15-Nov-2018 16:49

Ok, makes sense, thanks for the feedback.  

Posted by jankeir on 15-Nov-2018 16:49

Ok, makes sense, thanks for the feedback.  

Posted by dbeavon on 27-Nov-2018 18:35

@jankeir, would you be able to post your implementation of the safe persistent procedure holder?

I have lot of leaked handles in PASOE as well (MS agents in PASOE live quite a long time).

I noticed there is an interface that might do something similar: OpenEdge.Ccs.Common.Support.IHandleHolder.

implemented by OpenEdge.Core.WidgetHandle

See :

documentation.progress.com/.../index.html

But it would be nice to compare that to your own solution to this problem.

Posted by Peter Judge on 27-Nov-2018 18:46

The WidgetHandle is a general wrapper with an AutoDestroy property that will delete the wrapped handle on object destruction.  It exposes no extra attributes other than a Value property which contains the handle.
 
A ProcedureHandle extension to the WidgetHandle (or just another implementation of the IHandleHolder) is a good idea.
 
 
 

Posted by dbeavon on 27-Nov-2018 19:40

It sounds like you are saying that WidgetHandle's AutoDestroy isn't the same as deleting the procedure with "DELETE PROCEDURE".  Is that right?  I read the docs and it says DELETE OBJECT is a synonym for DELETE PROCEDURE, when the handle points to a persistent procedure.  Here is the destructor code of the WidgetHandle.

    destructor public WidgetHandle():
        if     AutoDestroy 
           and valid-object(this-object:Value ) then
            delete object this-object:Value no-error.
    end destructor.

Personally I'm a big fan of trimming inactive ABL sessions in PASOE every hour.  It seems like there are better things to do in life than play the role of a garbage collector.  At least with our regular OO classes we don't have to worry about these things.

Posted by Peter Judge on 27-Nov-2018 19:46

The behaviour is the same as if you’d done the DELETE PROCEDURE yourself; the difference is that you don’t have to remember to do it  since when the object is GC’ed, the handle will be deleted along with it.
 
Trimming the sessions will have the same gross effect – the memory will be cleared – but doing it as-you-go means that the memory shouldn’t grow unchecked during the life of a session.
 
Trimming sessions also means that you may incur a cost on session instantiation (if there’s work done in the session startup event procedure). Obviously YMMV.
 
 
 

Posted by jankeir on 29-Nov-2018 09:01

Only use with versions that have garbage collection ;-)

/* ============================================================================

  FILE:    utils/safelib.cls

-------------------------------------------------------------------------------

PURPOSE: Create a safe way to start libraries that are automatically stopped

        if the calling procedure ends.

Why this works:

        The registered/started procedure is automatically deleted because

        the destructor fires automatically when the OBJECT goes out of scope.

Example Usage:

        In the definitions section

        DEFINE VARIABLE mySafeLib AS utils.safelib NO-UNDO.

        DEFINE VARIABLE hSomeProcedure       AS HANDLE        NO-UNDO.

        Where you want to start a persistent procedure:

        IF NOT VALID-HANDLE(hSomeProcedure) THEN DO:

         RUN some/proc.p PERSISTENT SET hSomeProcedure.

         ASSIGN mySafeLib = NEW utils.safelib(hSomeProcedure).  

        END.

Example Usage 2: Advantage of this method: Only one variable needed in calling procedure.

                Disadvantage: less flexibility: Can't start on appserver,

                can't start procedures with parameters.

        In the definitions section

        DEFINE VARIABLE mySafeLib AS utils.safelib NO-UNDO.

        Where you want to start a persistent procedure:

        IF NOT VALID-OBJECT(mySafeLib) THEN DO:

          ASSIGN mySafeLib = NEW utils.safelib("some/proc.p").  

        END.

        To use it:                                                

        RUN some-internal-proc IN mySafeLib:getHandle().

============================================================================ */

CLASS utils.safelib:

 DEFINE VARIABLE hProcedure AS HANDLE      NO-UNDO.

 /* start the procedure from the safelib */

 CONSTRUCTOR safelib(INPUT icProcedure AS CHARACTER):

   RUN VALUE(icProcedure) PERSISTENT SET hProcedure.

 END CONSTRUCTOR.

 /* register the procedure here that was started elsewhere, usefull for appserver handles, webservices,

  * procdures with input Parameters,... */

 CONSTRUCTOR safelib(INPUT ihProcedure AS HANDLE):

   ASSIGN hProcedure = ihProcedure.

 END CONSTRUCTOR.

 /* obtain the handle if we didn't start it */

 METHOD PUBLIC HANDLE getHandle   ():

   RETURN hProcedure.

 END METHOD. /* getHandle */

 /* clean up */

 DESTRUCTOR safelib () :

   IF VALID-HANDLE(hProcedure) THEN

   DO:

     DELETE PROCEDURE hProcedure.

   END. /* valid-handle */

 END DESTRUCTOR.

END CLASS.

Posted by dbeavon on 29-Nov-2018 14:24

Thanks @jankeir.  It is helpful.  The WidgetHandle does a similar thing.  So it seems that there is some general consensus about the need to allow the GC to manage runaway handles.

This thread is closed