According to profiling messages that I'm generating, when a stand-alone executable terminates, any remaining finalizers (added with CInterface.setFinal) are not run. Consequently, resources that should be freed may not be. I think I would probably expect the semantics of finalizers to be such that they are run at some point before the program terminates. Does that sound sensible?
This can be worked around easily, for example by creating a variant of PolyML.export that adds a final PolyML.fullGC to the exported function (for both normal and exception paths). However, there is the question of whether, on termination, a full GC is needed or does a call to each finalizer suffice. If it is the latter, the work-around with an explicit GC does more work than required.
By comparison, for stand-alone executables created with MLton, any remaining finalizers (added with MLton.Finalizable.addFinalizer) are called prior to termination - possibly due to a final GC, I don't know.
Phil
On Mon, Aug 13, 2012 at 5:47 PM, Phil Clayton phil.clayton@lineone.net wrote:
This can be worked around easily, for example by creating a variant of PolyML.export that adds a final PolyML.fullGC to the exported function (for both normal and exception paths). However, there is the question of whether, on termination, a full GC is needed or does a call to each finalizer suffice. If it is the latter, the work-around with an explicit GC does more work than required.
I would think that you would want a full GC. I wouldn't expect a finalizer to run unless the value it is finalizing has been garbage collected, and it is possible that some code might depend upon that invariant. If there is code that should run at the end of the program, whether or not a value has been garbage collected, then OS.Process.atExit seems to be the better choice.
By comparison, for stand-alone executables created with MLton, any remaining finalizers (added with MLton.Finalizable.addFinalizer) are called prior to termination - possibly due to a final GC, I don't know.
Actually, it is due to multiple final GCs. The implementation is at: http://mlton.svn.sourceforge.net/viewvc/mlton/mlton/trunk/basis-library/mlto... The loop is to handle the situation where a full GC reveals that some finalizers should be run and, after running the finalizers and discarding those closures, another full GC reveals that yet more finalizers should be run.
-Matthew
On 14/08/12 15:47, Matthew Fluet wrote:
On Mon, Aug 13, 2012 at 5:47 PM, Phil Clayton phil.clayton@lineone.net wrote:
This can be worked around easily, for example by creating a variant of PolyML.export that adds a final PolyML.fullGC to the exported function (for both normal and exception paths). However, there is the question of whether, on termination, a full GC is needed or does a call to each finalizer suffice. If it is the latter, the work-around with an explicit GC does more work than required.
I would think that you would want a full GC. I wouldn't expect a finalizer to run unless the value it is finalizing has been garbage collected, and it is possible that some code might depend upon that invariant. If there is code that should run at the end of the program, whether or not a value has been garbage collected, then OS.Process.atExit seems to be the better choice.
With Poly/ML, a finalizer is a C function attached to a vol that, when called, is passed (the pointer at offset 0 in) the C data that the vol wraps. So, I suppose a vol's finalizer could callback to SML and cause another vol to become unreachable, invoking another finalizer and so on. I was trying to contrive such an example where the finalizers needed to occur in such an order but was having difficulty. I'm starting to form the opinion that this finalization mechanism, that works on vols rather than ML objects generally like in MLton, has very limited support for expressing order relationships between finalizers. After all, there is no way for the garbage collector to track dependencies via C. May be it is simplest to make no claims about the order of finalizers? (I've not found the current support to be an issue.)
I'm glad you mentioned OS.Process.exit - the work-around would also need to cover that exit path by (OS.Process.atExit PolyML.fullGC).
By comparison, for stand-alone executables created with MLton, any remaining finalizers (added with MLton.Finalizable.addFinalizer) are called prior to termination - possibly due to a final GC, I don't know.
Actually, it is due to multiple final GCs. The implementation is at: http://mlton.svn.sourceforge.net/viewvc/mlton/mlton/trunk/basis-library/mlto... The loop is to handle the situation where a full GC reveals that some finalizers should be run and, after running the finalizers and discarding those closures, another full GC reveals that yet more finalizers should be run.
Thanks - I hadn't appreciated that one call to MLton.GC.collect doesn't attempt a further GC after any finalizers that it triggers are run. It would be useful to know whether PolyML.fullGC is the same. (If so, my work-around above won't work generally.)
Phil
On 13/08/2012 22:47, Phil Clayton wrote:
According to profiling messages that I'm generating, when a stand-alone executable terminates, any remaining finalizers (added with CInterface.setFinal) are not run. Consequently, resources that should be freed may not be. I think I would probably expect the semantics of finalizers to be such that they are run at some point before the program terminates. Does that sound sensible?
This can be worked around easily, for example by creating a variant of PolyML.export that adds a final PolyML.fullGC to the exported function (for both normal and exception paths). However, there is the question of whether, on termination, a full GC is needed or does a call to each finalizer suffice. If it is the latter, the work-around with an explicit GC does more work than required.
I've been wondering about this. It would be fairly simple to have the close-down routine go over the list of "vols" and call the finalisation on them. Since finalisation on vols is always done by calling a C function it shouldn't be too much of a problem.
Of course, this leaves open the possibility that there might be a crash or other failure that means that the finalisations still don't get called.
David
On 15/08/12 15:27, David Matthews wrote:
On 13/08/2012 22:47, Phil Clayton wrote:
According to profiling messages that I'm generating, when a stand-alone executable terminates, any remaining finalizers (added with CInterface.setFinal) are not run. Consequently, resources that should be freed may not be. I think I would probably expect the semantics of finalizers to be such that they are run at some point before the program terminates. Does that sound sensible?
This can be worked around easily, for example by creating a variant of PolyML.export that adds a final PolyML.fullGC to the exported function (for both normal and exception paths). However, there is the question of whether, on termination, a full GC is needed or does a call to each finalizer suffice. If it is the latter, the work-around with an explicit GC does more work than required.
I've been wondering about this. It would be fairly simple to have the close-down routine go over the list of "vols" and call the finalisation on them. Since finalisation on vols is always done by calling a C function it shouldn't be too much of a problem.
After some more thought, I'm starting to think that this may be the way to go, with no guarantee that finalizers occur in a particular order. That is because I'm not sure that the alternative, with a full final GC, helps ordered finalizers. At the moment, I can't see how finalizers can be ordered other than by using callbacks from finalizers. That requires callback objects to exist at termination, but then there needs to be some automatic GC of callback objects as other finalizers could be blocked otherwise (because their vol is reference by the callback function). Automatic GC of callback objects is generally difficult because there is no way to know whether they will be called from C at a future point.
At present, using a final full GC isn't going to work because callbacks are never garbage collected, as mentioned in another message.
Of course, this leaves open the possibility that there might be a crash or other failure that means that the finalisations still don't get called.
That sounds fair enough. I don't think it is worse than the current situation when calling C functions.
Phil