Opened 7 years ago

Closed 6 years ago

#1619 closed bug (fixed)

The RTS chokes on SIGPIPE (happens with runInteractiveCommand)

Reported by: int-e Owned by:
Priority: normal Milestone: 6.10 branch
Component: Runtime System Version: 6.7
Keywords: Cc: id@…
Operating System: Linux Architecture: x86
Type of failure: Difficulty: Unknown
Test Case: Blocked By:
Blocking: Related Tickets:

Description

Consider the following code.

-- code based on example by steven_ashley on #haskell
import System.Process
import System.IO
import Control.Exception

main = do
    data <- readFile "core"
    (inp,out,err,pid) <- runInteractiveCommand "cat"
    print "before"
    finally (hPutStr inp data)
            (print "after")

This program, when run with a large enough core file in the current directory, will print "before" and then exit.

What happens (according to strace) is that the RTS closes the input end of the output pipe of the cat process (the one associated with the out variable). Then, cat tries to write some of its output and gets killed by SIGPIPE. This in turn closes the input end of its input pipe. When the Haskell program writes its next chunk of data, it is killed by SIGPIPE itself.

I believe the right fix for this is for the RTS to ignore SIGPIPE. The write syscall would then return EPIPE in the above scenario, which can be handled by the normal exception mechanism.

Attachments (1)

bug.hs (478 bytes) - added by meteficha 6 years ago.
A test case

Download all attachments as: .zip

Change History (11)

comment:1 Changed 7 years ago by int-e

Any big file will do. I just happened to have a nice large core file lying around.

I assume that the out file handle gets closed as a result of garbage collection. That's a bit inconvenient, but not a bug, in my opionion. The bug is that the Haskell program doesn't survive the SIGPIPE, preventing the print "after" from ever happening.

comment:2 Changed 7 years ago by simonmar

We intentionally don't do anything about SIGPIPE, in order to follow the POSIX semantics where SIGPIPE terminates the program by default. It's definitely true that this behaviour can be surprising, and I have a lot of sympathy with that. However, to ignore SIGPIPE by default would be a deviation from POSIX, and might therefore surprise some users - if we "fixed" this, I can imagine someone complaining that we were taking liberties with the spec.

My inclination is to leave things as they are, but I'm prepared to be persuaded otherwise.

comment:3 Changed 7 years ago by Isaac Dupree

what I think the more general issue is --> Should terminating signals all go through the exception mechanism, so Haskell programs can do cleanup in their "usual" way?

comment:4 Changed 7 years ago by int-e

I had hoped for a little more shielding by the RTS, especially since System.Posix.Signals isn't universally available. And I'd really like the above program to print "after" as well. A descriptive exception would be a bonus.

But I realize now that it's a design decision. If you want to provide POSIX semantics by default then you can't ignore the signal. I'm not sure what the least surprising behaviour is -- although obviously the current behaviour surprised me enough to declare it a bug :)

Turning fatal signals into exceptions sounds neat, but is tricky to get right. You have to assure that the exceptions are delivered promptly. That requires forcibly interrupting a running thread, which the RTS currently can't do, as far as I know (it waits until the next allocation or for certain primops). Oh and FFI will cause trouble as well.

comment:5 Changed 7 years ago by Isaac Dupree

  • Cc id@… added

comment:6 Changed 6 years ago by igloo

  • Milestone set to 6.10 branch

comment:7 Changed 6 years ago by meteficha

If this bug/feature is going to be left on the RTS, it should be at least documented properly. I've just spent some time tracking why a lot of brackets weren't doing anything.

Anyway, I've come with a test case that represents my problem. If this really is the same bug, at least this test case is simpler.

Changed 6 years ago by meteficha

A test case

comment:8 follow-up: Changed 6 years ago by duncan

See also ticket #2301. Presumably SIGPIPE should be handled in a similar way to SIGINT and raise an exception.

comment:9 in reply to: ↑ 8 Changed 6 years ago by simonmar

Replying to duncan:

See also ticket #2301. Presumably SIGPIPE should be handled in a similar way to SIGINT and raise an exception.

Yes, I believe so.

comment:10 Changed 6 years ago by simonmar

  • Resolution set to fixed
  • Status changed from new to closed

SIGPIPE already causes an exception to be raised, because it occurs as a side-effect of a write() which returns EPIPE. So I've come around on this one and decided we should be just ignoring SIGPIPE.

Wed Jul  9 09:49:16 BST 2008  Simon Marlow <marlowsd@gmail.com>
  * FIX part of #2301, and #1619
  
  2301: Control-C now causes the new exception (AsyncException
  UserInterrupt) to be raised in the main thread.  The signal handler
  is set up by GHC.TopHandler.runMainIO, and can be overriden in the
  usual way by installing a new signal handler.  The advantage is that
  now all programs will get a chance to clean up on ^C.
  
  When UserInterrupt is caught by the topmost handler, we now exit the
  program via kill(getpid(),SIGINT), which tells the parent process that
  we exited as a result of ^C, so the parent can take appropriate action
  (it might want to exit too, for example).
  
  One subtlety is that we have to use a weak reference to the ThreadId
  for the main thread, so that the signal handler doesn't prevent the
  main thread from being subject to deadlock detection.
  
  1619: we now ignore SIGPIPE by default.  Although POSIX says that a
  SIGPIPE should terminate the process by default, I wonder if this
  decision was made because many C applications failed to check the exit
  code from write().  In Haskell a failed write due to a closed pipe
  will generate an exception anyway, so the main difference is that we
  now get a useful error message instead of silent program termination.
  See #1619 for more discussion.
Note: See TracTickets for help on using tickets.