
On 07 April 2006 23:37, John Meacham wrote:
I think we might be thinking of different things. here is a complete implementation of exit.
exitMVar :: MVar () -- starts full exitMVar = ..
handlerMVar :: MVar [IO ()] -- starts with [] handlerMVar = ...
onExit :: IO () -> IO () onExit action = modifyMVar handlerMVar (action:)
exitWith status = do takeMVar exitMVar -- winner takes all let handleLoop = do hs <- swapMVar handlerMVar [] sequence_ hs if null hs then return () else handleLoop handleLoop exitWith_ status
exitWith_ calls the underlying 'exit' routine of the operating system immediatly. no waiting.
Suppose I want to do some action with a temporary file: bracket newTempFile (\f -> removeTempFile f) (\f -> doSomethingWith f) Under your scheme, this code doesn't get to remove its temporary file on exit, unless I explicitly add an exit handler that throws an exception to the current thread. I think code like the above should just work. Furthermore, I think it should be an invariant that a thread is never discarded or killed, only sent an exception. Otherwise, how else can I acquire a resource and guarantee to release it when either an exception is raised, the program exits, or the computation completes? According to your definition of exitWith above, I can't both raise an exception *and* exit in the same thread. If I register an onExit handler that throws an exception to the current thread, things go wrong if the current thread also calls exitWith. Also, you couldn't call exitWith while holding an MVar, if the handlers need access to the same MVar. You didn't show WithTemporaryExitHandler, which complicates things quite a bit. Also, your implementation has a race condition - a thread might add another exit handler after the swapMVar. I think we can probably agree on one thing: exitWith should raise an exception: exitWith e = throw (ExitException e) This isn't inconsistent with your proposal, and I think it's unambiguously better. The top-level exception handler catches ExitException and performs the required steps (running handlers, calling exit_). As you said, you need a top-level exception handler anyway, this is just a small change to your proposal, moving the exit actions to the top-level exception handler. Now additionally I believe that, if the system is about to simply stop, every thread should be sent an exception and be given a chance to clean up before the system stops. If this is the case, then: - withTemporaryExitHandler is unnecessary (catch suffices) - bracket and finally "just work" - it is safe to call exitWith while holding resources such as MVars - the programmer doesn't have to distinguish cleanup actions that should happen on exit from others - library programmers can rely on exceptions being delivered and don't have to additionally install exit handlers It's the right default: if a programmer decides not to handle the exception, then nothing goes wrong.
advantages of this set up.
1. base case requires no concurrency or exceptions
Haskell already has exceptions (in the IO monad), and people are using them to manage resources. We shouldn't introduce another way to clean up. Also, in the single-threaded case my proposal makes sense too.
2. abstract threads possible, if you don't let your ThreadId escape, there is no way to get an exception you don't bring upon yourself.
Well, there's StackOVerflow and HeapOverflow, and imprecise exceptions are hard to plan for too. All IO monad code should be prepared for exceptions, IMO.
3. simple rules. expressable in pure haskell. 4. can quit immediatly on a SIGINT since the exitWith routine runs on whatever thread called exit, rather than throwing responsibility back to the other threads which might be stuck in a foreign call. (unless you explicitly ask it to)
Don't understand this one - it certainly doesn't help with SIGINT in GHC.
5. you don't have to worry about 'PleaseExit' if you don't want to.
See (2). Also, the exit exception can be treated in the same way as all other unexpected exceptions (just clean up and re-throw).
6. modularity modularity. now that concurrency is part of the standard, we will likely see a lot of libraries using concurrency internally for little things that it wants to keep abstract, or concurrent programs composed with each other. having a global 'throw something to all threads on the system' doesn't feel right.
It's having a global exit that doesn't feel right. But since exit is something that is happening to all the threads in the system, they should all be told about it.
7. subsumes the exitWith throws exceptions everywhere policy.
John
Cheers, Simon