This patch incorporates Ricky Zhou's idea for establishing a default SIGPIPE
trap of IGNORE in child, which protects really dumb daemons from the effects of
writing to a closed stdout.  As even moderately smart daemons ought not do
this, these may be a reasonable fraction of the cases.

It also includes a sketch of the test for the patch, which is marked pending
as there is no good way to implement it as a portable automated test; you
either need a small standalone C program, a hacked ruby interpreter, or other
shenanigans to run it.

Also, tightened tests (~40 seconds instead of ~200).
---
 lib/puppet/util.rb         |    7 ++++
 spec/unit/exec_test_helper |    6 ++-
 spec/unit/util.rb          |   86 ++++++++++++++++++++-----------------------
 3 files changed, 51 insertions(+), 48 deletions(-)

diff --git a/lib/puppet/util.rb b/lib/puppet/util.rb
index a37a917..158a507 100644
--- a/lib/puppet/util.rb
+++ b/lib/puppet/util.rb
@@ -322,6 +322,13 @@ module Util
                     $stdout.reopen('/dev/null', 'w')
                     $stderr.reopen('/dev/null', 'w')
                 else
+                    # Ignore SIGPIPE.  This allows daemons which do not
+                    # properly close stdout/stderr to receive EPIPE on
+                    # attempting to write to the pipe instead of being
+                    # terminated with a SIGPIPE.  See ticket #1563,
+                    # comment 7 and ticket #3013 for details.
+                    trap(:PIPE, 'SIG_IGN')
+
                     $stdout.reopen(output_write)
                     if arguments[:combine]
                         $stderr.reopen(output_write)
diff --git a/spec/unit/exec_test_helper b/spec/unit/exec_test_helper
index ac536fe..f00814d 100755
--- a/spec/unit/exec_test_helper
+++ b/spec/unit/exec_test_helper
@@ -1,6 +1,7 @@
 #!/usr/bin/env ruby

 require 'optparse'
+$trap_and_relay = []
 options = OptionParser.new
 options.on("--kill-me-withi SIGNAL",/USR[12]/) { |signal| $kill_me = signal }
 options.on("--called-by PID",Integer)          { |parent| $pid = parent }
@@ -13,6 +14,7 @@ options.on("--flush")                          {
     $flush = true }
 options.on("--fork-badly")                     {          $fork_badly = true }
 options.on("--write-in-fork")                  {
$write_in_fork = true }
 options.on("--signal-exit")                    {          $signal_exit = true }
+options.on("--trap-and-relay SIGNAL",/PIPE/)   { |val|
$trap_and_relay |= [val] }
 rest = options.parse(ARGV)

 unless rest.empty?
@@ -34,8 +36,8 @@ end

 unless $fork_badly and fork
     $exit_signal = :USR2 # Something unexpected happened
-    trap(:PIPE) { $exit_signal = :PIPE }
-    at_exit { Process.kill $exit_signal,$pid} if $signal_exit
+    $trap_and_relay.each { |signal| trap(signal) { $exit_signal = signal } }
+    at_exit { Process.kill $exit_signal,$pid } if $signal_exit
     sleep $delay_close || 0
     puts 'Thanks for waiting' if $write_in_fork
     $stdout.close
diff --git a/spec/unit/util.rb b/spec/unit/util.rb
index b178e19..79cc9d8 100755
--- a/spec/unit/util.rb
+++ b/spec/unit/util.rb
@@ -116,60 +116,54 @@ describe Puppet::Util do
                 end
                 it "should not mind (hang, crash, lose output) if the
other end closes the pipe before completing" do
                     n = 5
-                    task =
[File.dirname(__FILE__)+'/exec_test_helper','--called-by',$PID,'--repeat',n,'--delay-exit',5]
-                    timeout(10) { Puppet::Util.execute(task).should
== "#{$PID}\n"*n }
+                    task =
[File.dirname(__FILE__)+'/exec_test_helper','--called-by',$PID,'--repeat',n,'--delay-exit',2]
+                    timeout(5) { Puppet::Util.execute(task).should ==
"#{$PID}\n"*n }
                 end
                 it "should not hang if the child process terminates
but does not close the pipe" do
                     # Ticket #1563 comment #7
                     n = 5
-                    task =
[File.dirname(__FILE__)+'/exec_test_helper','--called-by',$PID,'--fork-badly','--repeat',n,'--delay-close',50]
+                    task =
[File.dirname(__FILE__)+'/exec_test_helper','--called-by',$PID,'--fork-badly','--repeat',n,'--delay-close',100]
                     timeout(20) {  Puppet::Util.execute(task).should
== "#{$PID}\n"*n }
                 end
                 describe "when the child process passes the pipe to a
grandchild and terminates without closing the pipe or waiting for the
grandchild" do
-                    it "should not cause the grandchild child process
to receive a SIGPIPE if the grandchild subsequently ignores the pipe"
do
-                        # Ticket #1563 comment #7, null hypothesis
for Ticket #3013 comment #18
-                        n = 5
-                        task =
[File.dirname(__FILE__)+'/exec_test_helper','--called-by',$PID,'--fork-badly','--repeat',n,'--delay-close',30,'--signal-exit']
-                        # Note that the trapping setup/teardown
appaently can NOT be done in a before block
-                        signal = nil
-                        old_usr1_trap = trap(:USR1) { signal = :USR1
} # They exited normally
-                        old_usr2_trap = trap(:USR2) { signal = :USR2
} # They died
-                        old_pipe_trap = trap(:PIPE) { signal = :PIPE
} # They got a SIGPIPE
-                        timeout(50) {
-                            Puppet::Util.execute(task).should == "#{$PID}\n"*n
-                            `sleep 0` until signal # Process sleep,
not thread sleep
-                        }
-                        trap(:USR1,old_usr1_trap)
-                        trap(:USR2,old_usr2_trap)
-                        trap(:PIPE,old_pipe_trap)
-                        signal.should == :USR1
+                    [
+                        [
+                            "the grandchild subsequently ignores the
pipe", # Ticket #1563 comment #7, null hypothesis for Ticket #3013
comment #18
+                            ['--signal-exit'],
+                            :USR1
+                        ],[
+                            "the grandchild subsequently writes to
the pipe without establishing a SIGPIPE trap", # Ticket #3013 comment
#18 & #22ff
+                            ['--write-in-fork','--signal-exit'],
+                            :USR1,
+                            "This can be demonstrated (see the
ticket) but not in a portable way because ruby _always_ traps SIGPIPE
even if the user program dosn't."
+                        ],[
+                            "the grandchild subsequently writes to
the pipe after establishing a SIGPIPE trap", # Ticket #3013 comment
#28 & #29
+
['--write-in-fork','--signal-exit','--trap-and-relay',:PIPE],
+                            :PIPE
+                        ]
+                    ].each { |desc,flags,expected,pending_reason|
+                        it "should #{(expected == :USR1) ? 'not ' :
''}cause the grandchild child process to receive a SIGPIPE if #{desc}"
do
+                            pending pending_reason if pending_reason
+                            n = 5
+                            task =
[File.dirname(__FILE__)+'/exec_test_helper','--called-by',$PID,'--fork-badly','--repeat',n,'--delay-close',2,*flags]
+                            # Note that the trapping setup/teardown
appaently can NOT be done in a before block
+                            signal = nil
+                            old_usr1_trap = trap(:USR1) { signal =
:USR1 } # They exited normally
+                            old_usr2_trap = trap(:USR2) { signal =
:USR2 } # They died
+                            old_pipe_trap = trap(:PIPE) { signal =
:PIPE } # They got a SIGPIPE
+                            timeout(50) {
+                                Puppet::Util.execute(task).should ==
"#{$PID}\n"*n
+                                `sleep 0` until signal # Process
sleep, not thread sleep
+                            }
+                            trap(:USR1,old_usr1_trap)
+                            trap(:USR2,old_usr2_trap)
+                            trap(:PIPE,old_pipe_trap)
+                            signal.should == expected
+                        end
+                    }
+                    it "should be able to deal with the command-line
'pseudo-daemon&' idiom (but not retain the output)" do
+                        Puppet::Util.execute(['/bin/sh','-c','(sleep
5; echo foo)&']).should == ''
                     end
-                    it "should not cause the grandchild child process
to receive a SIGPIPE if the grandchild subsequently writes to the
pipe" do
-                        # Ticket #3013 comment #18
-                        pending %q{
-                            This situation is presently only a
speculation and may in any case exceed
-                            what we should expect the code to do; see
note in the implementation for a
-                            suggested way to handle it if needed.
-                        }
-                        n = 5
-                        task =
[File.dirname(__FILE__)+'/exec_test_helper','--called-by',$PID,'--fork-badly','--repeat',n,'--delay-close',30,'--write-in-fork','--signal-exit']
-                        # Note that the trapping setup/teardown
apparently can NOT be done in a before block
-                        signal = nil
-                        old_usr1_trap = trap(:USR1) { signal = :USR1
} # They exited normally
-                        old_usr2_trap = trap(:USR2) { signal = :USR2
} # They died
-                        old_pipe_trap = trap(:PIPE) { signal = :PIPE
} # They got a SIGPIPE
-                        timeout(50) {
-                            Puppet::Util.execute(task).should == "#{$PID}\n"*n
-                            `sleep 0` until signal # Process sleep,
not thread sleep
-                        }
-                        trap(:USR1,old_usr1_trap)
-                        trap(:USR2,old_usr2_trap)
-                        trap(:PIPE,old_pipe_trap)
-                        signal.should == :USR1
-                   end
-                   it "should be able to deal with the command-line
'pseudo-daemon&' idiom (but not retain the output)" do
-                       Puppet::Util.execute(['/bin/sh','-c','(sleep
5; echo foo)&']).should == ''
-                   end
                 end
             end
         }
-- 
1.6.4
-- 
You received this message because you are subscribed to the Google Groups 
"Puppet Developers" group.
To post to this group, send email to [email protected].
To unsubscribe from this group, send email to 
[email protected].
For more options, visit this group at 
http://groups.google.com/group/puppet-dev?hl=en.


Reply via email to