
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
#include #include // 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
#include #include #include // 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