Malcolm.Wallace at cs.york.ac.uk
Mon Oct 7 06:02:24 EDT 2002
Some comments on the question of finalisers.
Firstly, a retraction. In nhc98, it *is* possible to implement Haskell
finalisers, by creating a `pending' list of finalisers during the GC,
and then running them immediately the GC finishes. I see several
messages in the archive where I claimed the latter was impossible.
I was wrong. (The key issue is deciding when GC has really finished -
not as obvious as it looks, but I got there in the end.)
What is more, I am now pretty convinced that it is relatively trivial
to permit those finalisers to call foreign code that calls back into
Haskell, possibly triggering a further GC, and for everything still
to play nicely. (A mutex lock is required to ensure that the pending
queue is not traversed more than once, but that is all I think.)
At the moment nhc98 still has the ability, as per recent changes in the
FFI spec, to call a foreign language finaliser (FunPtr (Ptr a -> IO ()))
rather than a Haskell one. However, our GC currently calls such a
finaliser immediately it finds it, i.e. in the middle of the GC cycle.
I am now certain that this is wrong, because the foreign code could
call back into Haskell, which of course would be disastrous in the
middle of a GC. So just like with a Haskell finaliser, a foreign code
finaliser must be placed on a `pending' list and run only after GC
Hence, I think the really key issue for the safety of finalisers is
not what language the finaliser is written in, but when it is run.
Now, how about concurrency?
nhc98 has no concurrency model, so all its interactions with foreign
code must be within a single thread. This is true even if the foreign
code has concurrent threads internally. Because nhc98 produces only
sequential code, all interaction with the Haskell program must take
place within that thread alone. The sequential Haskell RTS simply
has to ensure is that when a Haskell GC is in progress, no foreign
code can run in the same thread. (This is the reason for delaying any
finalisers until the GC is complete.)
For systems that do have concurrency, George points out that a
concurrent foreign thread might call into Haskell at any moment,
even when the Haskell RTS is in the middle of a GC. True enough.
But this has nothing to do with finalisation per se, it is just a
general feature of any concurrent system where there is a combination
of languages. Just apply the usual mechanism in the Haskell RTS to
exclude other activity during a GC.
I understand that there are more tricky issues in concurrency, to do
with shared access to resources, and scheduling policy. However I am
still not sure of all the details, so Alastair's concerns are probably
well-founded. I *think* the problem in Hugs is that the scheduler is
co-operative, so the GC cannot simply place a triggered finaliser into
a new thread and ignore it. (The co-operative scheduler might delay
the running of the finaliser thread for an arbitrarily long time.)
The GC also cannot make the triggered finaliser into a new thread and
start running it immediately GC is finished. (This would amount to
overriding the scheduler - pre-empting it in fact.)
Is this a fair characterisation Alastair? If so, then I have two
(1) Why is it a disaster for a finaliser to be delayed for
an arbitrary amount of time? As things stand, there is no
guarantee on when a finaliser will run anyway, just that it
will do so before the end of the computation.
(2) Why is it a disaster to have an occasional change of scheduling
policy (from co-operative multi-tasking to pre-emption)?
Does it change the semantics of the concurrency model?
Does it lead to a possible loss of termination?
Does it disrupt some in-built assumptions in the runtime system?
More information about the FFI