RE: Signals + minimal proposal (was Re: asynchronous exceptions)

On 07 April 2006 13:58, John Meacham wrote:
all threads keep running while the exit handers are running, all blockExit would do is grab and release an MVar. exit itself takes that MVar on starting to get rid of races to exit as well as protect itsesf from 'blockExit' (but won't ever put the MVar back).
That sounds hard to program with - surely you want to stop the program in order to clean up? Otherwise the program is going to continue working, generating more exit handlers, and we might never get to exit. 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?
Exceptions are the right way to handle releasing resources, and they are the right way to register cleanup actions. Therefore I believe exceptions are the right way to handle cleaning up on exit, too.
Well, We really need 'onExit' for other reasons as well, I think it should make it into the standard independently.
Sure, I'm happy with onExit.
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.
Since async exceptions are no problem to implement in a coop system, shouldn't we use them for exit too?
but then only one thread gets to clean up after itself,
I think all threads should get the exit exception (I know GHC doesn't currently do this).
and you have the issue that you can't interrupt a foreign function by throwing to it.
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. Cheers, Simon

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⑈

"Simon Marlow"
That sounds hard to program with - surely you want to stop the program in order to clean up? Otherwise the program is going to continue working, generating more exit handlers, and we might never get to exit.
Here is how I've done it in Kogut: An equivalent of Haskell's exitWith simply throws a predefined exception. When an unhandled exception reaches the toplevel, this exception is treated specially and is not printed with a stack trace. Exceptions caused by system signals are special too. There is a central list of registered exit handlers. On program exit each handler is run once. Handlers registered during this cleanup are run too. Any exceptions thrown from handlers are caught and ignored. This happens after printing a stack trace from an unhandled exception, just before shutting down the runtime and exiting. One of exit handlers cancels all other threads (except those which has been garbage collected) and waits until they finish. New threads started during this cleanup are canceled too. -- __("< Marcin Kowalczyk \__/ qrczak@knm.org.pl ^^ http://qrnik.knm.org.pl/~qrczak/
participants (3)
-
John Meacham
-
Marcin 'Qrczak' Kowalczyk
-
Simon Marlow