SIGPIPE in the GHC runtime

Brian Bloniarz brian.bloniarz at gmail.com
Wed Aug 18 00:21:41 EDT 2010


The GHC runtime ignores SIGPIPE by setting the signal
to SIG_IGN. This means that any subprocesses (created via
System.Process or otherwise) will also have their
SIGPIPE handler set to SIG_IGN; I think this might be
a bug. The Python runtime does the same thing,
there's a good explanation of the drawbacks in:
http://bugs.python.org/issue1652

IMHO the simplest fix is the patch below: simply
avoid SIG_IGN, instead install a handler which does nothing.
This way, an exec() restores the handler to SIG_DFL. I've
included a testcase too.

Do people think this is the way to go? I think the alternative
is the approach Python took: patch the various bits of code
that fork&exec and have them restore the signal handler
manually.

---

diff -rN old-ghc/rts/posix/Signals.c new-ghc/rts/posix/Signals.c
472a473,477
> static void
> ignoreSigPipe(int sig)
> {
> }
> 
528,529c533,535
<     // ignore SIGPIPE; see #1619
<     action.sa_handler = SIG_IGN;
---
>     // ignore SIGPIPE; see #1619. Don't use SIG_IGN since that'd
>     // be inherited by any children that get fork&exec'd.
>     action.sa_handler = &ignoreSigPipe;
531c537
<     action.sa_flags = 0;
---
>     action.sa_flags = SA_RESTART;

---
Testcase:

diff -rN old-testsuite/tests/ghc-regress/rts/all.T new-testsuite/tests/ghc-regress/rts/all.T
86a87,88
> 
> test('exec_signals', [cmd_prefix('$MAKE exec_signals-prep && ./exec_signals_prepare')], compile_and_run, [''])
diff -rN old-testsuite/tests/ghc-regress/rts/exec_signals_child.c new-testsuite/tests/ghc-regress/rts/exec_signals_child.c
0a1,47
> #include <signal.h>
> #include <stdio.h>
> #include <errno.h>
> 
> // Prints the state of the signal handlers to stdout
> int main()
> {
>     int open = 0, i;
>     sigset_t blockedsigs;
> 
>     printf("ChildInfo { masked = [");
> 
>     sigprocmask(SIG_BLOCK, NULL, &blockedsigs);
>     for(i = 0; i < NSIG; ++i)
>     {
>         int ret = sigismember(&blockedsigs, i);
>         if(ret >= 0)
>         {
>             if(!open)
>                 open=1;
>             else
>                 printf(",");
>             printf("(%d,%s)", i, ret == 1 ? "True" : "False");
>         }
>     }
>     printf("], handlers = [");
> 
>     open = 0;
>     for(i = 0; i < NSIG; ++i)
>     {
>         struct sigaction old;
>         if(sigaction(i, NULL, &old) >= 0)
>         {
>             if(!open)
>                 open=1;
>             else
>                 printf(",");
> 
>             printf("(%d,%s)", i,
>                     old.sa_handler == SIG_IGN ? "Ignored" :
>                     (old.sa_handler == SIG_DFL ? "Default" : "Handled"));
>         }
>     }
>     printf("]}");
> 
>     return 0;
> }
diff -rN old-testsuite/tests/ghc-regress/rts/exec_signals.hs new-testsuite/tests/ghc-regress/rts/exec_signals.hs
0a1,20
> import System.Process
> import System.Posix.Signals
> import Control.Monad(when)
> 
> data SigState = Ignored | Default | Handled
>     deriving (Eq, Read, Show)
> 
> data ChildInfo = ChildInfo {
>         masked :: [(Int,Bool)],
>         handlers :: [(Int, SigState)] }
>     deriving (Read, Show)
> 
> main = do out <- readProcess "./exec_signals_child" [] ""
>           let ci = read out :: ChildInfo
>               blockedSigs = [x | (x, True) <- masked ci]
>               ignoredSigs = [x | (x, Ignored) <- handlers ci]
>           when (not $ null blockedSigs) $
>             putStrLn ("signals " ++ show blockedSigs ++ " are blocked")
>           when (not $ null ignoredSigs) $
>             putStrLn ("signals " ++ show ignoredSigs ++ " are ignored")
diff -rN old-testsuite/tests/ghc-regress/rts/exec_signals_prepare.c new-testsuite/tests/ghc-regress/rts/exec_signals_prepare.c
0a1,29
> #include <signal.h>
> #include <stdio.h>
> #include <errno.h>
> #include <string.h>
> 
> // Invokes a process, making sure that the state of the signal
> // handlers has all been set back to the unix default.
> int main(int argc, char **argv)
> {
>     int i;
>     sigset_t blockedsigs;
>     struct sigaction action;
> 
>     // unblock all signals
>     sigemptyset(&blockedsigs);
>     sigprocmask(SIG_BLOCK, NULL, NULL);
> 
>     // reset all signals to SIG_DFL
>     memset(&action, 0, sizeof(action));
>     action.sa_handler = SIG_DFL;
>     action.sa_flags = 0;
>     sigemptyset(&action.sa_mask);
>     for(i = 0; i < NSIG; ++i)
>         sigaction(i, &action, NULL);
> 
>     execv(argv[1], argv+1);
>     fprintf(stderr, "failed to execv %s\n", argv[1]);
>     return 0;
> }
diff -rN old-testsuite/tests/ghc-regress/rts/Makefile new-testsuite/tests/ghc-regress/rts/Makefile
29a30,32
> exec_signals-prep::
> 	$(CC) -o exec_signals_child exec_signals_child.c
> 	$(CC) -o exec_signals_prepare exec_signals_prepare.c


More information about the Glasgow-haskell-users mailing list