Re: Treating POSIX signals as exceptions?

[moving to libraries] I've made a few modifications to your SigException modle... On Tue, Nov 11, 2003 at 09:39:05AM -0000, Simon Marlow wrote:
Hmm, there is clearly a choice between having a signal spawn a new thread or raise an asynchronous exception, and it's not at all clear which is best.
But, since we can implement either using the other one, it doesn't really matter too much which one is primitive.
While either could be implemented in terms of the other, the choice of which to use for the default handlers will change the behavior of the program dramatically, simply because it determines whether bracket cleans up in the presense of signals. In darcs, I really have no interest in dealing with signals, I just want to make sure my temporary files are cleaned up even if the user hits ^C. If the default signal handler uses exceptions, this will happen for free. Currently, my only choice for the former purpose is to use something like withSignal. I guess the problem is that as far as I can tell, there is no way to implement a "correct" bracket along with the existing POSIX signal implementation, since there's no way to find out what the current handler is, which means bracket can't install its own handler without messing up the current handler. Having block work for protecting areas of the code where unexpected termination could cause data corruption would also be handy, although this could already be done with blockSignals and unblockSignals.
I've attached a more robust implementation of your withSig. This one:
- doesn't suffer from race conditions around the point at which the signal handler is installed
- it uses a datatype for the signal exception (Typeable can be derived these days)
- it allows several threads to request notification of the same signal
- it doesn't support nesting use of withSig with the same signal. (that could be added, but I'm not sure it's worth it).
I've added (but not tested) support for nesting of withSig. It seems like it's worthwhile just so you can't get weird errors caused by nesting withSig.
Is there any reason all of System.Posix.Signals couldn't be done in this manner? Have the RTS always add a handler for each signal, which just throws an exception. Off the top of my head, all we'd need is one function
acceptSignals :: IO () -- tells RTS to send signal exceptions to this
You can implement this too using the module I attached.
Well, this would only make sense if the default behavior was to treat signals as exceptions. Otherwise the system wouldn't know *which* signals to send as exceptions to this thread, and any that aren't caught wouldn't be handled by the default handler.
I guess we should provide this in the POSIX library. Would you like to play around with the API and submit it for inclusion?
I've modified it a bit. As I said above, I added support for nested withSignals. I also added a catchSignal function. It was either that or export a type and let the user use catchDyn, and it seemed cleaner to simply export a function. It still doesn't do what I'd really like, which is add default handlers for signals. I'd really like a withSignalsAsExceptions :: IO a -> IO a which would add an exception-throwing handler for *every* signal type (perhaps every asynchronous signal type...), and then run the default handler for any uncaught signals. Even better if main was automatically run in a withSignalsAsExceptions environment. If we had this function, we wouldn't need withSig (since every signal would generate exceptions). We would need additional functions to indicate which threads should get the signal exceptions, although by default they could just go to the main thread. The catch is that this withSignalsAsExceptions would need to have a handler for every signal type, which is more work than I'm willing to put in at the moment. I'm attaching my modified SigException.hs. -- David Roundy http://www.abridgegame.org

On Tue, Nov 11, 2003 at 09:39:05AM -0000, Simon Marlow wrote:
Hmm, there is clearly a choice between having a signal spawn a new thread or raise an asynchronous exception, and it's not at all clear which is best.
But, since we can implement either using the other one, it doesn't really matter too much which one is primitive.
How can the spawn-a-thread semantics be implemented using asynchronous exceptions? Doesn't the thread that the exception is thrown to unwind to the topmost handler? On 14.11.2003, at 16:21, David Roundy wrote:
In darcs, I really have no interest in dealing with signals, I just want to make sure my temporary files are cleaned up even if the user hits ^C. If the default signal handler uses exceptions, this will happen for free. Currently, my only choice for the former purpose is to use something like withSignal.
But the ^C signal itself is not, in general, sent to a specific thread; rather, it's sent to the whole process. So while it's reasonable to convert a SIGINT to an asynchronous exception thrown to a specific thread (as GHCi does), I don't see how that can be done generally. If you want bracket to clean up if the user hits ^C, you'd still have to hava a way of specifying which thread(s) get aborted using an exception on SIGINT. The default behaviour should lead to termination of the entire program; but in other situations (like GHCi for example), I'd probably want to catch the exception in one thread and have all other threads continue.
I guess the problem is that as far as I can tell, there is no way to implement a "correct" bracket along with the existing POSIX signal implementation, since there's no way to find out what the current handler is, which means bracket can't install its own handler without messing up the current handler.
A way to find out the current handler could probably be added, but it'd still be impossible to implement a SIGINT-aware bracket because signal handlers are per-process, not per-thread.
It still doesn't do what I'd really like, which is add default handlers for signals. I'd really like a
withSignalsAsExceptions :: IO a -> IO a
which would add an exception-throwing handler for *every* signal type (perhaps every asynchronous signal type...), and then run the default handler for any uncaught signals. Even better if main was automatically run in a withSignalsAsExceptions environment.
Wouldn't that render every signal that isn't intended to cause clean program shutdown next to useless? It seems perfect for SIGINT and SIGTERM, but I don't think that I'd want to handle SIGCONT or SIGCHLD as exceptions. Cheers, Wolfgang

On Fri, Nov 14, 2003 at 10:51:59PM +0100, Wolfgang Thaller wrote:
On Tue, Nov 11, 2003 at 09:39:05AM -0000, Simon Marlow wrote:
Hmm, there is clearly a choice between having a signal spawn a new thread or raise an asynchronous exception, and it's not at all clear which is best.
But, since we can implement either using the other one, it doesn't really matter too much which one is primitive.
How can the spawn-a-thread semantics be implemented using asynchronous exceptions? Doesn't the thread that the exception is thrown to unwind to the topmost handler?
You'd create a thread specifically for handling exceptions when you add your handler.
On 14.11.2003, at 16:21, David Roundy wrote:
In darcs, I really have no interest in dealing with signals, I just want to make sure my temporary files are cleaned up even if the user hits ^C. If the default signal handler uses exceptions, this will happen for free. Currently, my only choice for the former purpose is to use something like withSignal.
But the ^C signal itself is not, in general, sent to a specific thread; rather, it's sent to the whole process. So while it's reasonable to convert a SIGINT to an asynchronous exception thrown to a specific thread (as GHCi does), I don't see how that can be done generally. If you want bracket to clean up if the user hits ^C, you'd still have to hava a way of specifying which thread(s) get aborted using an exception on SIGINT. The default behaviour should lead to termination of the entire program; but in other situations (like GHCi for example), I'd probably want to catch the exception in one thread and have all other threads continue.
This doesn't seem like a problem. By default the exception goes to main (which aborts the whole program), and if you want to change which thread gets the exception that's fine.
I guess the problem is that as far as I can tell, there is no way to implement a "correct" bracket along with the existing POSIX signal implementation, since there's no way to find out what the current handler is, which means bracket can't install its own handler without messing up the current handler.
A way to find out the current handler could probably be added, but it'd still be impossible to implement a SIGINT-aware bracket because signal handlers are per-process, not per-thread.
Well, bracket doesn't work with concurrent programs even in the absense of signals (except in the main thread) so you don't lose anything there. If you write a concurrent program and want its threads to clean up, you always have to code that yourself, for example using a bracket in the main thread.
It still doesn't do what I'd really like, which is add default handlers for signals. I'd really like a
withSignalsAsExceptions :: IO a -> IO a
which would add an exception-throwing handler for *every* signal type (perhaps every asynchronous signal type...), and then run the default handler for any uncaught signals. Even better if main was automatically run in a withSignalsAsExceptions environment.
Wouldn't that render every signal that isn't intended to cause clean program shutdown next to useless? It seems perfect for SIGINT and SIGTERM, but I don't think that I'd want to handle SIGCONT or SIGCHLD as exceptions.
You're right. I was only thinking of exceptions that terminate, so you'd only want to throw exceptions for sigHUP, sigINT, sigALRM, sigTERM and possibly sigUSR1 and sigUSR2. I'm attaching the signal handling code I now actually use. It makes no provision for concurrency because I don't use any concurrency (although I do use pthreads). Actually, although I export catchSignal and throwSignal on matter of principle, I don't use them either. I just use main = withSignalsHandled $ do ... which does all I want done. -- David Roundy http://www.abridgegame.org
participants (2)
-
David Roundy
-
Wolfgang Thaller