SIGPIPE in the GHC runtime

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

Quoth Brian Bloniarz
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.
I don't know enough to make a case for or against this, but the side effects are different: whether the handler does anything or not, it will interrupt some system calls. Since there are already may be a regular barrage of SIGALRMs from the GHC runtime, maybe one more such interruption can't hurt anything ... but note that we recently heard from someone in Australia with a problem on OpenSolaris where evidently these SIGALRMs were in fact interrupting hGetContents from a pipe, and no one seemed to have any notion why. At any rate, the affected system calls would set EAGAIN, possibly including the write(2) that's supposed to set EPIPE (though I think that would be an unusual implementation.) Donn Cave, donn@avvanta.com

On 08/18/2010 04:55 PM, Donn Cave wrote:
Quoth Brian Bloniarz
, 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.
I don't know enough to make a case for or against this, but the side effects are different: whether the handler does anything or not, it will interrupt some system calls. Since there are already may be a regular barrage of SIGALRMs from the GHC runtime, maybe one more such interruption can't hurt anything ... but note that we recently heard from someone in Australia with a problem on OpenSolaris where evidently these SIGALRMs were in fact interrupting hGetContents from a pipe, and no one seemed to have any notion why.
Shouldn't enabling SA_RESTART be enough to restart the syscall? I don't know all the Unices enough to guarantee that it'd be transparent, but I hope it should be. I see the SIGALRM code uses SA_RESTART too, though judging by the #ifdefs in Itimer.c, SA_RESTART must not be available everywhere. Note that unlike SIGALRM, the SIGPIPE will be sent to the thread which caused it (i.e. the caller of write()), not any other thread. So any possible damage would be limited to the calling thread.
At any rate, the affected system calls would set EAGAIN, possibly including the write(2) that's supposed to set EPIPE (though I think that would be an unusual implementation.)
I just tested linux in this scenario, it gives EPIPE as I'd expect.
Linux's SA_RESTART has been reliable in my limited experience. Do
you have an OpenSolaris install to test by any chance? The code is
below.
#include

Quoth Brian Bloniarz
I just tested linux in this scenario, it gives EPIPE as I'd expect. Linux's SA_RESTART has been reliable in my limited experience. Do you have an OpenSolaris install to test by any chance? The code is below.
No, sorry! But I don't doubt that with the right compile options and so forth, that test would work fine on OpenSolaris. Without knowing what really caused the problem I mentioned, I can't guess how to duplicate the problem or test for it. (Cf. "hGetContents: resource exhausted", Haskell Cafe, lally.singh@gmail.com.) I know that's rather unhelpful. I hate unnecessary signals. On the bright side, your approach sure has some appealing points, compared to tracking down every place an execve(2) might happen. The only solution of greater elegance would be for GHC to simply leave SIGPIPE the way it was. A less elegant variaton on that would be to block SIGPIPE, instead of ignoring it. I don't have a great deal of practical experience with that, nor have I looked at how it would fit with GHC runtime code, but execve(2) does reset the signal mask, so it would have the inheritance properties you want. I tried sigprocmask() and pthread_sigmask(); either worked the same for me, but something like this needs more testing. Donn Cave, donn@avvanta.com

On 08/18/2010 07:06 PM, Donn Cave wrote:
Quoth Brian Bloniarz
, ... I just tested linux in this scenario, it gives EPIPE as I'd expect. Linux's SA_RESTART has been reliable in my limited experience. Do you have an OpenSolaris install to test by any chance? The code is below.
No, sorry! But I don't doubt that with the right compile options and so forth, that test would work fine on OpenSolaris. Without knowing what really caused the problem I mentioned, I can't guess how to duplicate the problem or test for it. (Cf. "hGetContents: resource exhausted", Haskell Cafe, lally.singh@gmail.com.) I know that's rather unhelpful. I hate unnecessary signals.
Thanks for the pointer! I can try to dig around, but yeah I'd be tempted to just hope for the best given that the patch I posted won't make matters worse.
A less elegant variaton on that would be to block SIGPIPE, instead of ignoring it. I don't have a great deal of practical experience with that, nor have I looked at how it would fit with GHC runtime code, but execve(2) does reset the signal mask, so it would have the inheritance properties you want.
I think the signal mask is retained across exec (!). http://www.opengroup.org/onlinepubs/009695399/functions/execl.html Search for "the new process will inherit"; experimentally, linux retains it. -Brian

Could someone summarise this thread in a ticket in the GHC trac? http://hackage.haskell.org/trac/ghc/ Thanks Ian

On 08/23/2010 07:15 AM, Ian Lynagh wrote:
Could someone summarise this thread in a ticket in the GHC trac? http://hackage.haskell.org/trac/ghc/
I'd be happy to, I'll follow up as soon as I can find time. Thanks, -Brian

On Mon, Aug 23, 2010 at 10:44:10PM -0400, Brian Bloniarz wrote:
On 08/23/2010 07:15 AM, Ian Lynagh wrote:
Could someone summarise this thread in a ticket in the GHC trac? http://hackage.haskell.org/trac/ghc/
I'd be happy to, I'll follow up as soon as I can find time.
Great, thanks! Thanks Ian

On 08/23/2010 07:15 AM, Ian Lynagh wrote:
Could someone summarise this thread in a ticket in the GHC trac?
participants (3)
-
Brian Bloniarz
-
Donn Cave
-
Ian Lynagh