Signals + minimal proposal (was Re: asynchronous exceptions)

Simon Marlow simonmar at microsoft.com
Mon Apr 10 09:58:20 EDT 2006


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


More information about the Haskell-prime mailing list