Signals + minimal proposal (was Re: asynchronous exceptions)

John Meacham john at repetae.net
Mon Apr 10 17:19:23 EDT 2006


On Mon, Apr 10, 2006 at 02:58:20PM +0100, Simon Marlow wrote:
> 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?

you ask the system to send you an exception on exit.

> 
> 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.

hrm? nothing goes wrong. it is the same as calling 'throw' in the
current thread.

I don't see how it is unsafe, it is always unsafe to call a routine that
needs an MVar you already have held open. you don't call exitWith by
accident. There is always 'forkIO exitFailure' in any case. 

> You didn't show WithTemporaryExitHandler, which complicates things quite
> a bit.

it is uneeded, only a utility routine, I just didn't want to show the
bookkeeping in the handler list to allow deletion of elements as it
wasn't important to the scheme. any thread that wants to do bracket
style cleanup just asks to be thrown an Exit exception and uses the
standard 'bracket' etc.. routines.

> Also, your implementation has a race condition - a thread might add
> another exit handler after the swapMVar.

that is why it is in a swapMVar loop, processing batch's of handlers. at
some point, you just gotta accept that another thread didn't get its
handler in on time, after all, if things were scheduled differently it
might not have gotten there. mainly I wanted to make sure no handlers
registered from within other handlers got lost, as those should run to
completion being synchronously regiseterd from the handlers point of
view.

> I think we can probably agree on one thing: exitWith should raise an
> exception:
> 
>   exitWith e = throw (ExitException e)

I disagree :)

throwTo and throw should raise exceptions, exit should quit the program.
though, perhaps we just need another function in the middle.

AFAICT, what you are proposing is the same as mine but with

forkIO being implemented as 

forkIO action = forkIO' action' where 
        action' = do
                myThreadId >>= onExit . throwTo PleaseExit
                action

I just want to have control as to whether that throw me an exception
exit handler gets added and not have the implementation wait on my
thread to clean up before it can exit if it has nothing special to clean
up and might be deep in foreign calls.

perhaps if there were just a flag on each thread saying whether they
wanted to recieve exit message? though. I still don't like the idea of
exitWith throwing anything, just feels really dirty. though, if there
were a 'runExitHandlers' routine, what I want can be simulated by
'runExitHandlers >> exitWith_ foo'


> 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.

but that means you have to wait until the thread with that top level
exception handler becomes runnable. which could take arbitrary time if
it is in a foreign call. I'd rather stuff be taken care of on the
current thread (since we know we are runnable since we just ran
exitWith), or on some new exit only thread. as if exitwith behaved as if
it were called (forkIO $ exitWith)

it just seems odd for your global system exit code to be hidden deep at
the base of a certain threads stack somewhere. I don't mind so much
exceptions being thrown everywhere to give things a chance to clean up,
so much as the requirement we wait for it to fall off the distinguished
'main thread' before the program can actually quit.

by default fork 
> > 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.

when the signal occurs ghc sends a byte down a pipe, listening thread
reads that and calls exitWith, exitWith calss exit_ which kills whole
program immediatly, no need to wait for any other thread to do anything
or even the foregin call running while the signal occured to complete.
(if you don't want to)

        John

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


More information about the Haskell-prime mailing list