Indeed, I very nearly implemented such a thing as part of the patch I sent out. However, it didn't look trivial enough to implement so I backed off. The "blocked" state needs to be saved & restored at various points: when starting a finalizer, when invoking a foreign-exported function, and when invoking an exception handler.
I think it's fairly easy. A sketch is something like:
// primitive called from Haskell void blockFinalizer(Cell m, State world) { blocked += 1; eval(ap(m,world)); blocked -= 1; runFinalizers(); }
Does this run into problems with exceptions? Also, this is a nested call to eval(), in a primitive, which can invoke an IO action and therefore re-enter Haskell without going through unsafePerformIO. Is that safe? Cheers, Simon
Does this run into problems with exceptions?
Good question. I think the answer depends on what happens if a Haskell finalizer raises an IOError, calls System.exit or (Hugs/GHC extension) raises an Exception. I don't think the ffi spec comments on these. I'd guess the answer is: 1) Returning an IOError aborts the program. (In Hugs, we'd probably want the other finalizers to be run first, the same may be true of GHC.) 2) Calling exit aborts the program. (In Hugs, we'd probably want the other finalizers to be run first, but I don't know what GHC would want.) 3) Not specified to avoid unwanted dependency (but if specified it would be the same as for IOErrors).
Also, this is a nested call to eval(), in a primitive, which can invoke an IO action and therefore re-enter Haskell without going through unsafePerformIO. Is that safe?
Yes, I think so. Most calls to IO actions from primitives are safe and I believe these ones are too. (In some ways, its the rarity of race conditions, etc. biting you that disturbs me most about Haskell finalizers: it's hard to remember to do the right thing when the wrong thing happens so rarely.) -- Alastair
participants (2)
-
Alastair Reid -
Simon Marlow