ignores SIGPIPE, why ruby

Z

Zsban Ambrus

Ruby ignores SIGPIPE. In this mail, I'll argue against the usefulness of
this decision.

When you type something like

ruby -we 'n= 1; puts n+=1 while 1;' | head

then after the 10th line the puts function raises Errno::EPIPE (cause that's
what syscalls return when they raise an EPIPE signal) and ruby dies with an
ugly annoying message like

-e:1:in `write': Broken pipe (Errno::EPIPE)
from -e:1:in `puts'
from -e:1

I think that ruby should either silently die from the EPIPE signal (or else
raise an Errno::EPIPE exception that you can catch but if you don't catch it
ruby should just exit silently just like when you call Kernel.exit())

I belive that one of the biggest advantage of the UN*X-like systems is the
well-thought job-control scheme. It works quite automatically: you can
redirect, suspend, resume, background etc a procaess, and everything works
fine even if the processes itself do not have any special support for it.
This is especially true for simple processes that don't do anything special
with stdin and stdout just use them in cooked mode.

It would take nothing to implement this behaviour, as the OS kills ruby, so
ruby has to do nothing. In the above example, you don't need an error
message, as you asked for "|head", so the os killing the process would be right
(and the shell not printing a message about what signal killed the process
on the left side on the pipeline is also right, note that when a process
dies from a signal, new shells usually report this with a short line).

What makes the situation even worse is that when I try to change tshi
behaiviour like this:

ruby -we 'trap "PIPE" do exit end; n= 1; puts n+=1 while 1;' | head

the trap function does not do anything, it does not catch the SIGPIPE. The
trap function should at least raise an error because it does not catch the
signal. The only way to silence the error message is to wrap the whole
script in a begin .... rescue Errno::EPIPE; end;

If ruby would just leave the signal as is, you could probabyl still have the
old behaiviour by adding a trap for SIGPIPE that raises an Errno::EPIPE
exception, or just by ignoring the signal and supposing that the syscall
that raised SIGPIPE will get an EPIPE error anyway thus raise the exception.

What do you think of this idea?

ambrus
 
Z

Zsban Ambrus

Zsban Ambrus ([email protected]) wrote:

...
No, Ruby does not ignore SIGPIPE. It traps it just fine.

It traps SIGPIE, but unless you define your own signal handler, it does
nothing with it. See the sigpipe function in signal.c.

After what you've said I've investigated further what happened.
Ruby did not recive a SIGPIPE, puts raised an Errno::EPIPE. See
errno(2) vs signal(3).

Ruby did get a SIGPIPE. I thought it should get one as the os always sends a
SIGPIPE when any operation (either on a socket or a pipe or a named fifo or
whatever) fails with EPIPE.

So it got SIGPIPE, but as I sad the handler function does nothing, so puts
raises the EPIPE exception it gets from the os.

Read further why I'm so sure

This last statement of mine was incorrect. Ruby indeed catches the SIGPIPE,
but it has "safe signals", so it does not immediately start my trap block,
but schedules it later. But then puts raises an exception, and as the
exception is not caught, ruby exits and it does not get around to run the
trap-block.

To prove this, look:

am ~/a> ruby -we 'trap "PIPE" do $stderr.puts "trapped SIGPIPE"; end; x=0;
begin loop do puts(x+=1); end; rescue Errno::EPIPE; $stderr.puts "rescued
EPIPE"; end;' | head
1
2
3
4
5
6
7
8
9
10
trapped SIGPIPE
rescued EPIPE
am ~/a> ruby -v
ruby 1.8.1 (2003-12-25) [i686-linux]

What I think happens here starts the same as I've described above.
First, ruby gets the SIGPIPE from the os when the head has exited,
but it cannot execute my block now, so it just puts it aside.
Then, the write operation returns with EPIPE, so puts raises an Errno::EPIPE
execption. The begin block captures this exception, but then, before ruby
would start evaling the commands in the rescue clause, it runs my handler
for SIGPIPE. The SIGPIPE handler returns, and then the rescue clause runs
too. Then the program ends normally.

I still be wrong here, so correct me if I'm wrong.
That code never recieves a SIGPIPE signal, False, as I've said above
it raises an Errno::EPIPE.
trap "PIPE" works just fine:

$ cat x.rb
pid = fork do
trap 'PIPE' do puts "caught SIGPIPE"; exit end
File.open '/dev/null', 'a' do |fp|
loop do fp.puts end
end
end

sleep 1
Process.kill 'PIPE', pid
$ ruby x.rb
caught SIGPIPE
Thanks for this example, this is what made me think about why trap does not
seem to work.
def main
# ...
rescue Errno::EPIPE
exit
end

main if $0 == __FILE__
That's almost the same isn't it?
This doesn't hold because of the above.

I am now not quite about what would be the correct behaviour.
Maybe ruby should try to check for pending signals before dieing of an
exception?
 

Ask a Question

Want to reply to this thread or ask your own question?

You'll need to choose a username for the site, which only take a couple of moments. After that, you can post your question and our members will help you out.

Ask a Question

Members online

No members online now.

Forum statistics

Threads
473,755
Messages
2,569,537
Members
45,023
Latest member
websitedesig25

Latest Threads

Top