Treating POSIX signals as exceptions?

David Roundy droundy at abridgegame.org
Fri Nov 14 10:21:43 EST 2003


[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
-------------- next part --------------
-- | This module allows you to receive POSIX signals as asynchronous
-- exceptions.  This is handy, as it allows you to respond to exceptions
-- using all the tools already available for dealing with asynchronous
-- exceptions.  One result of this is that the 'bracket' function can work
-- properly together with POSIX signals.  Another benefit is that it lets
-- you use 'block' and 'unblock' from "Control.Exeception" to avoid
-- receiveing exceptions at inconvenient times.

module SignalException ( withSignal, catchSignal ) where

import Control.Concurrent
import Control.Exception
import Data.FiniteMap
import System.Posix
import System.IO.Unsafe	( unsafePerformIO )
import Data.Dynamic
import System.IO
import Data.List ( nub, delete )

{-# NOINLINE sigmap #-}
sigmap :: MVar (FiniteMap Signal [ThreadId])
sigmap = unsafePerformIO $ newMVar emptyFM

registerSignal :: Signal -> ThreadId -> IO ()
registerSignal s t = do
  modifyMVar_ sigmap $ \fm -> 
	case lookupFM fm s of
		Nothing -> do 
		   putStr "hello1\n"; hFlush stdout; 
		   installHandler s (Catch (handleSignal s)) Nothing
		   return (addToFM fm s [t])
		Just ts ->
		   return (addToFM fm s (t:ts))

unregisterSignal :: Signal -> ThreadId -> IO ()
unregisterSignal s t = 
  modifyMVar_ sigmap $ \fm ->
	case lookupFM fm s of
		Nothing -> return fm
		Just [t] -> do installHandler s Default Nothing
		               return (delFromFM fm s)
		Just ts -> return (addToFM fm s (delete t ts))

handleSignal :: Signal -> IO ()
handleSignal s = withMVar sigmap $ \fm ->
  case lookupFM fm s of
	Nothing -> return ()
	Just ts -> sequence_ [ throwDynTo t (SignalExcpetion s) | t <- nub ts ]

newtype SignalExcpetion = SignalExcpetion Signal deriving (Typeable)

-- | withSignal runs a computation while responding to a given signal by
-- throwing an exception.  If the exception is not caught, it is responded
-- to by a default handler, given as the second argument to withSignal.

withSignal :: Signal -> IO a -> IO a -> IO a
withSignal s handler job = do
  id <- myThreadId
  -- We can almost use bracket, but we want a specialised exception
  -- handler, so we have to inline bracket and modify it.
  block $ do
    registerSignal s id
    r <- Control.Exception.catch
	   (unblock job)
	   (\e -> do unregisterSignal s id
		     case e of
			DynException d
			    | Just (SignalExcpetion sig) <- fromDynamic d,
			      s == sig -> handler
			_other ->
				throw e
	  )
    unregisterSignal s id
    return r

-- | 'catchSignal' allows you to catch a signal exception within a
-- withSignal call.

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


More information about the Libraries mailing list