Finalizers etcetera

Alastair Reid alastair at reid-consulting-uk.ltd.uk
Wed Oct 9 09:02:35 EDT 2002


> So are you saying that if a GC were to occur in the middle of a
> C-implemented Hugs primitive, it could be bad news? 

It's possible.  It's something we never had to consider when writing
the Hugs primitives so we never took care to protect against it.

> Surely there must be some mechanism for ensuring that allocation
> during a primitive call is safe?  For instance, any primitives in
> nhc98's RTS that allocate on the heap exclude the possibility of GC
> by checking there is enough free space before doing anything.

Using a conservative GC saves us from most of that though we need to
take some care with some of the more complex primops like array
allocation.

> (By contrast, FFI calls (a) cannot heap-allocate, and (b) do not
> copy any heap pointers, so a GC would be harmless.)

Of course, safe FFI calls can heap allocate...

>> 2) That there is no blocking mechanism handy with which to
>> implement the mutexes needed to make data structure modifications
>> atomic.

>> I find it a bit misleading because it seems to suggest that NHC
>> will not suffer from the same problem because NHC doesn't have a
>> cooperative scheduler.

>> That is, you have to provide mutexes (because the only useful
>> finalizers are those that access shared mutable data structures).

> I'm afraid I don't agree that the /only/ useful finalisers
> manipulate global variables.  I can think of plenty of more
> tractable situations, e.g. releasing the various pieces of storage
> for a foreign value built of several components, where a finaliser
> written in Haskell would be preferable to one in C.

Do those really occur?  We're talking about finalizers for C objects
(i.e., things of type '[Foreign]Ptr a') here.

If it was a finalizer for a Haskell object, I could easily imagine 
a finalizer like this:

  finalizeFoo (Foo x y) = finalize x >> finalize y

but it's a C object.  I guess it might contain a pointer to a Haskell
object.  That would be a StablePtr, of course, so I'd call 

  void hs_freeStablePtr( HsStablePtr a );

from C which would tell the GC that we're done with the object.

[I think I've previously mentioned the need to add hs_freeStablePtr
before and the need to be specify that unsafe ffi calls and finalizers
can safely call it.]

> However, given that you do want to manipulate shared global
> variables, I still have a question.  Why not build a queue of
> pending finalisers, make each one into a co-operative thread, and
> add those threads to the pool of threads being managed by Hugs'
> normal scheduler?  It seems to me that this guarantees the atomicity
> you need.  The only downside is:

>> (because waiting until the main thread terminated or took another
>> lock would be unacceptable).

> So why is this unacceptable?  You need to delay the finaliser for
> correctness.  By the assumptions of the co-operative scheduling
> model, the only safe moment to run the finaliser is when the current
> thread releases control.  But now you seem to say that this is bad,
> because you want efficiency/timeliness, and efficiency means you
> need pre-emption.  Make your mind up!  Either you accept the
> co-operative scheduling model and put up with lack of timeliness, or
> you decide that timeliness is more important and move to
> pre-emption.

This would delay execution of the finalizer far longer than is necessary.

With your proposal, finalizers which might possibly conflict would be
delayed until control returned to the main thread and executed one of
those IO actions which can cause preemption.  This could take a very
long time (it might not even happen until the program terminates) and
you will hold onto space during all that time.

In GHC, using a mutex to protect a data structure from finalizers only
delays execution of the finalizers if the data structure is currently
being accessed and only delays them until the thread leaves the
critical section.  [Actually, this isn't quite true: there's a delay
caused by the length of time slices and by GHC not having a priority
scheduler.  This could be significant in some programs.]

With my proposal, Hugs and NHC can call the finalizer during GC and
that finalizer can release any stable pointers it may hold during that
GC.  Space is released very quickly and, in particular, the time taken
to release that garbage is independent of how much time you spend in the
IO monad.
[It'd be nicer still if releasing those StablePtrs instantly triggered
release of more ForeignPtrs which released more StablePtrs, etc. so that
a single GC run released all unnecessary heap space but I think this
would require substantial reengineering.]


--
Alastair



More information about the FFI mailing list