RE: asynchronous exceptions (was: RE: Concurrency)

On 05 April 2006 13:38, John Meacham wrote:
On Wed, Apr 05, 2006 at 07:47:08AM -0400, David Roundy wrote:
For me, asynchronous exceptions are the primary reason to use concurrent Haskell. They're the only way I'm aware of to write a program that handles signals in Haskell, and it's be a real shame to leave Haskell' programs unable to handle signals--it means that any real-world programs that deal with locking or the like will need to use non-standard extensions. Unless you can come up with some other way to deal with signals. Having no chance to clean up when control-C is hit isn't an acceptable alternative, and neither is simply ignoring control-C and forcing users to run kill (and then once again get no chance to clean up!).
I have been giving signals some thought, and resarching what other languages do, and have a semi-proposal-maybe.
We should be careful here: the Haskell standard has so far remained platform-independent, and I think it would be nice to keep it that way. I'm not proposing that we ignore signals, just that we should clearly delimit the platform-specific bits, perhaps by putting signal support into an addendum.
signals tend to be used for one of a couple purposes (some can fall into multiple categories):
signal a synchronous exceptional event - SIGFPE, SIGPIPE, SIGILL, SIGSEGV signal an asynchronous exceptional event - SIGINT, SIGHUP (interactive) inform the app of an event it might want to take note of - SIGALRM, SIGCHLD, SIGWINCH, SIGHUP (daemon)
I think it would make sense to have 3 mechanisms to cover these cases.
signal a synchronous exceptional event - raise a (possibly imprecise) exception on the thread that produced the signal.
GHC has no support for these right now. They're pretty tricky to handle, because the OS thread that caused the signal to be raised is stopped at some arbitrary instruction, and it would require some serious acrobatics to munge that OS thread into a state where it is possible to raise the (Haskell) exception. I do vaguely recall that people have achieved this in the past, in order to use page faults for write barriers, that sort of thing. SIGPIPE is possibly easier than the others. SIGFPE you can usually turn off in favour of "exceptional values" instead.
signal an asynchronous exceptional event - the user should be able to choose the threads on which they wish to catch these, those that need to clean up after themselves.
inform the app of an event it might want to take note of - these should run on their own thread, concurrently to all other threads
GHC directly support the latter version, and you can implement the former with a little extra code and a global variable. I think it would be nice to do as you suggest and provide a way to have the async signals turn directly into exceptions. One problem, though, is that because we can't interrupt a foreign call with an async exception, ^C can be rather unresponsive. Perhaps I should look into this and see whether it would be possible in GHC for a concurrent foreign call to be interruptible; it would involve terminating the foreign call somehow (pthread_cancel?) before raising the exception. We can't do this in a bound thread, however. Cheers, Simon

On Wed, Apr 05, 2006 at 03:41:55PM +0100, Simon Marlow wrote:
I have been giving signals some thought, and resarching what other languages do, and have a semi-proposal-maybe.
We should be careful here: the Haskell standard has so far remained platform-independent, and I think it would be nice to keep it that way.
Signals arn't as bad as some things, as a platform that doesn't support signals would just be visibly the same as one where the signals never happen to be generated. in any case, a signal-safe haskell program will be portable. assuming we don't make the ability to _send_ a signal part of the standard.
I'm not proposing that we ignore signals, just that we should clearly delimit the platform-specific bits, perhaps by putting signal support into an addendum.
yeah, I was thinking a separate environment addendum should be in the report, which takes behavior that is undefined in the language standard, and defines it for various platforms. it wouldn't extend the functionality or scope of the standard, just define what couldn't be defined in the standard. like the standard might say "the set of signals is undefined" while the UNIX addendum will say "the set of signals will include at least SIGINT,SIGHUP,etc..."
GHC has no support for these right now. They're pretty tricky to handle, because the OS thread that caused the signal to be raised is stopped at some arbitrary instruction, and it would require some serious acrobatics to munge that OS thread into a state where it is possible to raise the (Haskell) exception. I do vaguely recall that people have achieved this in the past, in order to use page faults for write barriers, that sort of thing.
how does ghc handle things like divide by zero then?
SIGPIPE is possibly easier than the others. SIGFPE you can usually turn off in favour of "exceptional values" instead.
yeah, I was thinking we should make these the default in the unix addendum.
signal an asynchronous exceptional event - the user should be able to choose the threads on which they wish to catch these, those that need to clean up after themselves.
inform the app of an event it might want to take note of - these should run on their own thread, concurrently to all other threads
GHC directly support the latter version, and you can implement the former with a little extra code and a global variable. I think it would be nice to do as you suggest and provide a way to have the async signals turn directly into exceptions.
One problem, though, is that because we can't interrupt a foreign call with an async exception, ^C can be rather unresponsive. Perhaps I should look into this and see whether it would be possible in GHC for a concurrent foreign call to be interruptible; it would involve terminating the foreign call somehow (pthread_cancel?) before raising the exception. We can't do this in a bound thread, however.
You should be able to handle the SIGINT imediatly no matter whether foregin code is running if your handler is in its own thread right? just have the C signal handler write a byte to a pipe, your haskell signal handler thread is in a repeatM $ do readExactlyOneByte signalHandler loop. so will run immediatly no matter what thread the async signal was delivered to. the same solution will work in cooperative implementations, but are subject to normal scheduling latency issues. = minimal proposal = I think a good minimal solution will be the following, it neatly avoids turning signals into exceptions, which may be problematic, but provides for the common cases of signal usages while being compatible with both cooperative and SMP systems. == catching signals == implementations provide a way of catching signals such that the handler runs as if in its own thread. something like the following data SigInfo = ... data HandlerType = SigOneShot | SigReset | SigIdempotent data SigAction = SigAction { signalType :: HandlerType, signalAction :: SigInfo -> IO () } | SigDefault | SigIgnore | SigExit (SigInfo -> ExitStatus) installHandler :: Signal -> SigAction -> IO SigAction installHandler = ... the action runs in its own thread. SigExit is special in that it is equivalent to a 'signalAction' that just calls exit, but since you know the program is going to exit, the implementation can jump to the exit handler immediatly aborting the current computation in whatever state it is in since it knows it will never return to it. SigIdempotent is the same as SigReset except multiple signals are condensed into one with the SigInfo being chosen non-deterministically from all those available. == on exit == implementations also provide an onExit functionality, for registering handlers that can be run when the program exits, as this is the most common use of signals as exceptions, to clean up after oneself. -- | temporarily register an exit handler for the duration of the action argument withExitHandler :: IO () -> IO a -> IO a withExitHandler = .... -- | register a handler to be run on exiting the program onExit :: IO () -> IO () onExit = .... -- | block exiting during this call for critical sections. blockExit :: IO a -> IO a blockExit = ... although thees are less powerful than exceptions in that you can only catch a single event, "exit" they are more powerful in the sense that the exception handlers are global, so when you register an exit handler it happens no matter what thread is in scope. in addition, an exit_ routine should be added that bypasses the exit handlers, quiting the program immediatly. John -- John Meacham - ⑆repetae.net⑆john⑈

On Thu, Apr 06, 2006 at 03:19:30PM -0700, John Meacham wrote:
= minimal proposal =
I think a good minimal solution will be the following, it neatly avoids turning signals into exceptions, which may be problematic, but provides for the common cases of signal usages while being compatible with both cooperative and SMP systems.
I must admit that I don't understand the problem with turning signals into exceptions, but I really prefer the picture of signals as exceptions, as it seems to me to be much simpler. Both simpler to use, and simpler to describe in the standard. Rather than having to enumerate (or leave vague) a list of signals, and describe an API for handling these things that may or may not exist on a given platform, the standard could just state that any "OS events that would normally lead to the application quitting" generate asynchronous exceptions. This is suitably vague, and has the result that when coding you can simply assume that bracket will catch everything that could be caught and lead to nicely cleaning up. The catch to this [no pun intended] is that when the main thread exits all other threads are silently terminated, without the chance to clean up... I'd prefer they get a special exception first, to give them a chance to clean up. But at least with a single-threaded app, you could then work under the assumption that catching exceptions catches is "complete" (or at least as complete as the compiler was able to implement--I don't think you can catch sigKILL, but that's an OS limitation).
== on exit ==
implementations also provide an onExit functionality, for registering handlers that can be run when the program exits, as this is the most common use of signals as exceptions, to clean up after oneself.
-- | temporarily register an exit handler for the duration of the action argument withExitHandler :: IO () -> IO a -> IO a withExitHandler = ....
This would work, but seems overly-complex, and leads to interesting questions. What happens if the action argument throws an exception? How do I implement a "really_bracket" using this? really_bracket init clean job = bracket init clean job' where job' a = withExitHandler (do {clean a;return ()}) job Is this as clean a solution as bracket? It seems to me that in either case there is a window in which clean won't get called either in the case of a signal or an exception, although I'm not sure. I'm also not sure whether you'd run into danger of clean being called twice when an exit is never caught, once by the bracket and once by the exit handler. If signals don't generate exceptions, I'd at least like some sort of really_bracket to make it into the standard, as this is probably the most troublesome aspect of existing Haskell. -- David Roundy http://www.darcs.net
participants (3)
-
David Roundy
-
John Meacham
-
Simon Marlow