Signals + minimal proposal (was Re: asynchronous exceptions)

John Meacham john at repetae.net
Fri Apr 7 18:37:15 EDT 2006


On Fri, Apr 07, 2006 at 02:58:01PM +0100, Simon Marlow wrote:
> Of course you could implement some global flag to say that an exit is in
> progress, but that implies explicit checking of the flag all over the
> place, which is what asynchronous exceptions are designed to avoid.
> 
> When *do* we exit, in fact?  When all the exit handlers have finished?

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.

I'll get to why you can't have handlers building up indefinitly below.

> > I think you have that backwards, releasing resources is the right
> > thing 
> > to do when you get an exception, but there are lots of other reasons
> > you 
> > want to release resources that have nothing to do with exceptions. you
> > don't use 'throwTo' to close all your files :)
> 
> No, but you do use an exception handler, or something built using
> exception handlers like 'finally'.  I don't want to have to use *both*
> exception handlers and exit handlers.

they serve different purposes. You might use both at different places in
the same program, but never for the same resource. 

> The situation is the same as in your proposal - the foreign call
> continues running.  However, as soon as it returns, the Haskell thread
> will receive an exception.
> 
> I propose this:
> 
>   When System.Exit.exitWith is called, all currently running
>   threads are sent an exit exception as soon as possible.
>   Exit handlers registered with onExit are started immediately.
>   The system exits when (a) the main thread has stopped, and (b)
>   all exit handlers have completed.  Subsequent calls to exitWith
>   simply throw an exit exception in the current thread.

this seems the wrong way round. exitWith is something you call in
_response_ to an exception, telling the program you want to exit. not
something that generates an exception. In particular, you often won't
know what 'status' to exit with until you have had everything clean up
properly (or fail to clean up properly). We have 'throwTo' to throw
exceptions around.

what I would expect from dealing with other languages is:

exitWith does as it does above in my example, nothing more, nothing
less. in particular it is not special in any way when it comes to
exceptions or concurrency other than using standard MVars.


falling off the end of the main thread is equivalent to calling
exitWith Success, an exception falling off the end is equivalent to
exitWith Failure.

the main thread is not special in any way other than being wrappen in
the equivalent of.

-- user written main function
main = do ...

-- what the implementation uses as its main thread
realMain = catch (\_ -> exitFailure) main >> exitSuccess


if you want to die and clean up via exceptions, use 'throwTo' to throw a
'PleaseExit' exception to whatever threads you like.

if you are writing a library that uses threads internally, where you
have a particular thread you want to clean up via exceptions, do an

myThreadId >>= onExit . throwTo PleaseExit

now you will get an exception on exit. if you need the exit to wait
until you complete something, you can have your handler wait on an MVar.

advantages of this set up.

1. base case requires no concurrency or exceptions
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.
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)
5. you don't have to worry about 'PleaseExit' if you don't want to.
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.
7. subsumes the exitWith throws exceptions everywhere policy.

        John







-- 
John Meacham - ⑆repetae.net⑆john⑈


More information about the Haskell-prime mailing list