RE: Minor tweaks to ffi addendum
It's going to be really hard to implement ForeignPtr as specified. The problem is that invocation of the cleanup function is triggered by the garbage collector.
Malcolm can comment on exactly how nhc98 handles the finalizers, but I believe that there's a list of pending finalizers maintained by the RTS which are run on re-entry to Haskell land.
That's the theory, yes. In practice, I've never been able to get it to work correctly, so the only finalisers that are guaranteed to run in nhc98's runtime system are ones written in C rather than Haskell. The underlying problem is when do you run a Haskell finaliser in a non-concurrent system? You can't run it when you discover it (in the middle of a GC). When the GC is called, the mutator is probably right in the middle of some reduction step. If I run the finalisers immediately the GC finishes, then I'll probably corrupt the reduction that was already in progress. So the pending finalisers need to be held off until a "safe" moment, but I haven't yet been able to find a way to determine when that is.
In any case the most likely definition of a Haskell finaliser is just a call into C-land anyway, so cut out the middle-man is what I say!
Well, looking at the big picture I suppose having Haskell finalizers does imply a kind of pre-emptive concurrency, which might not otherwise be present in the system. Just by adding Haskell finalizers, assuming they can be implemented without concurrency, it is possible that IO code can be arbitrarily interleaved. This is a pretty big step to take for a single-threaded system, since it will likely impact other parts of the system (libraries have to be thread-safe, etc.). Hmm. Maybe we should have the pure C finalizer version of addForeignPtrFinalizer, with the version with a Haskell finalizer optional, available iff pre-emptive Concurrent Haskell is supported. We have to specify the constraint that the C finalizer can't directly or indirectly invoke any Haskell code (is this checkable at run time?). Alternatively, we could have an explicit operation runFinalizers :: IO () which causes finalizers to run in a non-pre-emptive or non-concurrent system. Cheers, Simon
Alternatively, we could have an explicit operation
runFinalizers :: IO ()
which causes finalizers to run in a non-pre-emptive or non-concurrent system.
Of course, this solution is no use at all if your use of ffi is to pull a big mostly pure C library into Haskell. On two separate occasions, I've added interfaces to image processing libraries where pretty much every function was pure (it either generated a fresh image or returned an Int, Float or whatever). Images tend to be big (640x400x24bits is 3/4 Mbyte) so a few dozen image processing steps might quickly use up a lot of memory - and in all that time you don't go anywhere near the IO monad. -- Alastair Reid reid@cs.utah.edu http://www.cs.utah.edu/~reid/
We have to specify the constraint that the C finalizer can't directly or indirectly invoke any Haskell code (is this checkable at run time?).
It's the same constraint we place on 'unsafe' foreign imports. I picture a typical usage being something like this: foreign import ccall unsafe "static & free" free :: Ptr a -> IO () alloc x = do object <- malloc x newForeignPtr' object free It's easy to check correct use of the unsafe flag at runtime if we want: set a flag when you make an unsafe call and test the flag in the foreign export wrapper code. We could imagine getting the Haskell typesystem to check that newForeignPtr' is only used with unsafe free functions. It'd be a bit verbose but we could probably do it. There's not much point though since C can't do a similar job on its side. -- Alastair Reid reid@cs.utah.edu http://www.cs.utah.edu/~reid/
participants (2)
-
Alastair Reid -
Simon Marlow