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.
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.
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.
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.
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.
Ok, makes sense, thanks for the feedback.
Ok, makes sense, thanks for the feedback.
@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.
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.
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.
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.