| Am I right in thinking that you are proposing that we revert to | writing Haskell finalizers but that Hugs and NHC programmers would | have to avoid writing finalizers which manipulate Haskell state while | GHC would use MVars to protect that state? Yes, that's right. It is often the case that there *is* no shared state so a Haskell finalizer is fine. But if there is, then there has to be some mechanism for atomic operations. C is one such mechanism. But there's something I'm puzzled about. Hugs does support non-pre-emptive concurrency, right? (Where can I find a description of it.) So would it not be easy to implement (non-pre-emptive) MVars? And if they existed, everything would be fine, right? We could just use Haskell finalizers as we all want. Or am I missing something. (I'm assuming that the starting point for the entire discussion is whether finalizers are written in Haskell or C. Please let me know if I missed something.) Simon
Yes, that's right. It is often the case that there *is* no shared state so a Haskell finalizer is fine. But if there is, then there has to be some mechanism for atomic operations. C is one such mechanism.
I claim that the major thing that finalizers do is manipulate shared state. Their task is to clean up when an object dies and the reason we care whether or not that cleanup happens is that the thing being cleaned is shared with live objects/threads. If I want to manipulate shared C state, then finalizers written in C do rather well. I see no compelling need to write them in Haskell. (And I see significant problems in encouraging people to write them in Haskell.) To get any benefit from writing finalizers in Haskell, I have to have MVars which protect against finalizers. Malcolm: are you ready to add MVars to NHC?
But there's something I'm puzzled about. Hugs does support non-pre-emptive concurrency, right? (Where can I find a description of it.)
I don't think I ever described it in print. Mark Jones implemented the IO monad using two continuations: an error continuation: IOError -> () and a success continuation: a -> () On this base, I added MVars (whose state is either a value or a list of success continuations) and a queue of running tasks. Context switches happen when you manipulate MVars and when a thread terminates.
So would it not be easy to implement (non-pre-emptive) MVars?
Yes, we have those. Of course, NHC will have to add them too. There's no reasonable way to add Haskell finalizers without providing locking as well.
And if they existed, everything would be fine, right? We could just use Haskell finalizers as we all want. Or am I missing something.
Finalizers would have to be scheduled by the same scheduler as normal threads. (This is the key change. SimonM's patch effectively set up the garbage collector as a separate scheduler much as 'scheduling' of CPU interrupts isn't handled by the pthread scheduler.) The scheduler would need to prioritize finalizers over normal threads. That is finalizers should be run whenever they are runnable. This is essential in cooperative schedulers because you never know when or if a thread will give up control. Most of the cooperative concurrency implementation is written in Haskell. That would have to be rewritten in C to make the operations atomic wrt garbage collection. I think there's a bunch of other changes needed but it's way past my bedtime - I'll need to think more about this.
(I'm assuming that the starting point for the entire discussion is whether finalizers are written in Haskell or C. Please let me know if I missed something.)
That's pretty much it. Of course, to get any benefit from restricting them to being written in C, C finalizers have to be restricted to not call Haskell code. (Calling the Haskell runtime isn't ruled out though some calls like performGC may be off limits.) And to get any benefit from allowing finalizers to be written in Haskell, you have to have MVars. -- Alastair
Alastair Reid <alastair@reid-consulting-uk.ltd.uk> writes:
Malcolm: are you ready to add MVars to NHC?
I have just done a little investigation to try to discover exactly what MVars are semantically, since it is far from obvious from the name alone. The GHC user's guide (7.2.15) has the following: type MVar# s elt -- primitive newMVar# :: State# s -> (# State# s, MVar# s elt #) takeMVar# :: SynchVar# s elt -> State# s -> (# State# s, elt #) putMVar# :: SynchVar# s elt -> State# s -> State# s where the inconsistent naming suggests that MVars were once named SynchVars, which would make much more sense. (Why do implementers always go for shorter-to-type rather than easier-to-understand?) As another aside, in the hierarchical libraries docs (package base, Control.Concurrent.MVar) there appears to be an ambiguity: readMVar :: MVar a -> IO a This is a combination of takeMVar and putMVar; ie. it takes the value from the MVar, puts it back, and also returns it. It specifically avoids describing the combination as atomic, so I am uncertain whether the thread can be pre-empted between the 'take' and the 'put'? Normally the answer would be yes, but then there is no benefit from having a separate 'readMVar' to combine the operations, so I am guessing that it /should/ be atomic. Now to the question of whether we can implement something like this in nhc98. Tricky one. What does it mean for the only thread in a single-thread system to block waiting on another thread? Deadlock of course, as you already pointed out. It seems a bit pointless to implement that! I think the key point here is that for MVars to make sense, you need concurrency. And if you really want to manipulate shared state safely in Haskell, it implies MVars. Thus, the need for concurrency has always been your objection to writing finalisers in Haskell. However, I now do /not/ think it is finalisers in Haskell per se that are the problem. The problem is the desire to manipulate shared state in a concurrent manner. The FFI specification is a stand-alone specification extending Haskell'98 only, and should neither rely on concurrency nor outlaw it. Thus it seems sensible for the FFI to say: (1) do not manipulate shared state in a finaliser unless your host system implements concurrency; (2) if you do manipulate shared state in a finaliser, then you must use the model for mutual exclusion provided by the host system (e.g. MVars, or storing the shared state in the foreign language only). Regards, Malcolm
Thus it seems sensible for the FFI to say:
(1) do not manipulate shared state in a finaliser unless your host system implements concurrency; (2) if you do manipulate shared state in a finaliser, then you must use the model for mutual exclusion provided by the host system (e.g. MVars, or storing the shared state in the foreign language only).
I think this kind of specification spells disaster for portability. We'll see lots of code that works for GHC, crashes occasionally on Hugs and doesn't compile or crashes occasionally with NHC. The crashes will be hard to repeat and a nightmare to debug. The 'crashes occasionally with NHC' is because I suspect that some of NHC's libraries contain some state and that NHC doesn't document that these libraries contain race conditions which show up in the presence of concurrency. -- Alastair
participants (3)
-
Alastair Reid -
Malcolm Wallace -
Simon Peyton-Jones