# Treating POSIX signals as exceptions?

David Roundy droundy at abridgegame.org
Sat Nov 15 10:15:38 EST 2003

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?

> 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 -------------- next part -------------- % Copyright (C) 2003 David Roundy % % This program is free software; you can redistribute it and/or modify % it under the terms of the GNU General Public License as published by % the Free Software Foundation; either version 2, or (at your option) % any later version. % % This program is distributed in the hope that it will be useful, % but WITHOUT ANY WARRANTY; without even the implied warranty of % MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the % GNU General Public License for more details. % % You should have received a copy of the GNU General Public License % along with this program; if not, write to the Free Software Foundation, % Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. \begin{code} module SignalHandler ( withSignalsHandled, catchSignal, throwSignal, ) where import System.Exit ( exitWith, ExitCode ( ExitFailure ) ) import Control.Concurrent ( myThreadId ) import Control.Exception ( catchDyn, throwDyn, throwDynTo ) import Data.Dynamic ( Typeable ) import Posix ( installHandler, Handler(..), Signal, sigINT, sigHUP, sigABRT, sigALRM, sigTERM, ) \end{code} \begin{code} newtype SignalException = SignalException Signal deriving (Typeable) withSignalsHandled :: IO a -> IO a withSignalsHandled job = do id <- myThreadId sequence_$ map (ih id) [sigINT, sigHUP, sigABRT, sigALRM, sigTERM]
job catchSignal defaults
where defaults s | s == sigINT = ew "Interrupted!"
| s ==  sigHUP = ew "HUP"
| s ==  sigABRT = ew "ABRT"
| s ==  sigALRM = ew "ALRM"
| s ==  sigTERM = ew "TERM"
| otherwise = ew "Unhandled signal!"
ew s = do putStrLn s
exitWith $ExitFailure$ -1

ih id s = installHandler s (Catch $throwDynTo id$ SignalException s) Nothing

catchSignal :: IO a -> (Signal -> IO a) -> IO a
catchSignal job handler =
job Control.Exception.catchDyn (\(SignalException sig) -> handler sig)

throwSignal :: Signal -> IO a
throwSignal s = Control.Exception.throwDyn (SignalException s)
\end{code}