[MLton] adding synchronous finalizers

Stephen Weeks MLton@mlton.org
Fri, 1 Oct 2004 22:40:47 -0700


> Ok, I'm definitely confused now.  Suppose a finalizer runs when we
> are not synchronous, but sets the synchronous flag to true?

Setting the synchronous flag to true does nothing other than set the
bool ref.  So, any subsequent enabled finalizers will be placed on the
poll list.

> If it really is true that there are two finalizer lists, with ones
> from the first being added to the second when the objects are ready
> to finalize and when synchronous is false (or polling is being
> done), then I guess that this isn't an issue,

In basis-library/mlton/finalizable.sml, there is the following code

      val r: {clean: unit -> unit,
	      isAlive: unit -> bool} list ref = ref []
      fun clean l =
	 List.foldl (fn (z as {clean, isAlive}, (gotOne, zs)) =>
		     if isAlive ()
			then (gotOne, z :: zs)
		     else (clean (); (true, zs)))
	 (false, []) l
      val _ = MLtonSignal.handleGC (fn () => r := #2 (clean (!r)))

This code defines r as a list of potentially finalizable objects, and
adds a handler that runs after every gc and scans the list and runs
any enabled finalizers (by calling clean ()).  In the code I sent, we
have 

      val finalizers: (unit -> unit) list ref = ref []
      fun addFinalizer (v, f) =
	 F.addFinalizer
	 (v, fn a =>
	  if !synchronously
	     then finalizers := (fn () => f a) :: !finalizers
	  else f a)

In this code, it is the "fn a => ..." that will be applied to the
appropriate value when the GC signal handler is invoked.  If we are in
synchronous mode, this will simply add the finalizer to the poll list
("finalizers").

> As to the second point, my claim is that running finalizers might be
> very expensive, but I don't mind because they are only run on
> explicit polls or when a GC happens (in the non-synchronous case).
> Your code always runs finalizers when I switch from non-synchronous
> to synchronous mode. 

It runs the finalizers that would have been run at the next poll had
we remained synchronous.  These finalizers are enabled, so the should
be run.  Entering asynchronous mode means that the program is ready to
handle enabled finalizers running at any time.  So, I chose to run
them immediately.  Another reasonable choice would be to delay the
enabled finalizers until the next GC, when they would normally happen,
but I don't see much difference.  And all other things being equal, it
seems preferable to me to run finalizers as soon as possible after
they are enabled.

> Consider the case of code that jumps between modes very often.  I
> don't want that to cause finalizers to run more frequently.

I don't understand what it means to cause finalizers to run more
frequently.  A finalizer for an object will run exactly once during
the execution of the program.  Synchronous/Asynchronous mode hasn't
changed that.