<?xml version="1.0" encoding="UTF-8"?>
<commit>
  <added type="array"/>
  <modified type="array">
    <modified>
      <diff>@@ -200,16 +200,37 @@ protected
 		end
 	end
 	
+	class PseudoIO
+		def initialize(sink)
+			@sink = sink || File.open(&quot;/dev/null&quot;, &quot;w&quot;)
+			@buffer = StringIO.new
+		end
+		
+		def done!
+			result = @buffer.string
+			@buffer = nil
+			return result
+		end
+		
+		def method_missing(*args, &amp;block)
+			@buffer.send(*args, &amp;block) if @buffer &amp;&amp; args.first != :reopen
+			return @sink.send(*args, &amp;block)
+		end
+		
+		def respond_to?(symbol, include_private = false)
+			return @sink.respond_to?(symbol, include_private)
+		end
+	end
+	
 	# Run the given block. A message will be sent through +channel+ (a
 	# MessageChannel object), telling the remote side whether the block
 	# raised an exception, called exit(), or succeeded.
 	#
-	# Anything written to $stderr and STDERR during execution of the block
-	# will be buffered. If &lt;tt&gt;write_stderr_contents_to&lt;/tt&gt; is non-nil,
-	# then the buffered stderr data will be written to this object. In this
-	# case, &lt;tt&gt;write_stderr_contents_to&lt;/tt&gt; must be an IO-like object.
-	# If &lt;tt&gt;write_stderr_contents_to&lt;/tt&gt; is nil, then the stder data will
-	# be discarded.
+	# If _sink_ is non-nil, then every operation on $stderr/STDERR inside
+	# the block will be performed on _sink_ as well. If _sink_ is nil
+	# then all operations on $stderr/STDERR inside the block will be
+	# silently discarded, i.e. if one writes to $stderr/STDERR then nothing
+	# will be actually written to the console.
 	# 
 	# Returns whether the block succeeded, i.e. whether it didn't raise an
 	# exception.
@@ -217,30 +238,26 @@ protected
 	# Exceptions are not propagated, except SystemExit and a few
 	# non-StandardExeption classes such as SignalException. Of the
 	# exceptions that are propagated, only SystemExit will be reported.
-	def report_app_init_status(channel, write_stderr_contents_to = STDERR)
+	def report_app_init_status(channel, sink = STDERR)
 		begin
 			old_global_stderr = $stderr
 			old_stderr = STDERR
 			stderr_output = &quot;&quot;
-			tempfile = Tempfile.new('passenger-stderr')
-			tempfile.unlink
+			
+			pseudo_stderr = PseudoIO.new(sink)
 			Object.send(:remove_const, 'STDERR') rescue nil
-			Object.const_set('STDERR', tempfile)
+			Object.const_set('STDERR', pseudo_stderr)
+			$stderr = pseudo_stderr
+			
 			begin
 				yield
 			ensure
 				Object.send(:remove_const, 'STDERR') rescue nil
 				Object.const_set('STDERR', old_stderr)
 				$stderr = old_global_stderr
-				tempfile.rewind
-				stderr_output = tempfile.read
-				tempfile.close rescue nil
-				
-				if write_stderr_contents_to
-					write_stderr_contents_to.write(stderr_output)
-					write_stderr_contents_to.flush
-				end
+				stderr_output = pseudo_stderr.done!
 			end
+			
 			channel.write('success')
 			return true
 		rescue StandardError, ScriptError, NoMemoryError =&gt; e</diff>
      <filename>lib/phusion_passenger/utils.rb</filename>
    </modified>
    <modified>
      <diff>@@ -8,6 +8,59 @@ require 'phusion_passenger/utils'
 
 include PhusionPassenger
 
+shared_examples_for &quot;a pseudo stderr created by #report_app_init_status&quot; do
+	before :each do
+		@sink = StringIO.new
+		@temp_channel = MessageChannel.new(StringIO.new)
+	end
+	
+	after :each do
+		File.unlink(&quot;output.tmp&quot;) rescue nil
+	end
+	
+	it &quot;redirects everything written to the pseudo STDERR/$stderr to the sink&quot; do
+		report_app_init_status(@temp_channel, @sink) do
+			STDERR.puts &quot;Something went wrong!&quot;
+			$stderr.puts &quot;Something went wrong again!&quot;
+			raise StandardError, &quot;:-(&quot; if @raise_error
+		end
+		@sink.string.should =~ /Something went wrong!/
+		@sink.string.should =~ /Something went wrong again!/
+	end
+	
+	it &quot;redirects reopen operations on the pseudo stderr to the sink&quot; do
+		@sink.should_receive(:reopen).with(&quot;output.tmp&quot;, &quot;w&quot;)
+		report_app_init_status(@temp_channel, @sink) do
+			STDERR.reopen(&quot;output.tmp&quot;, &quot;w&quot;)
+			raise StandardError, &quot;:-(&quot; if @raise_error
+		end
+	end
+	
+	specify &quot;after the function has finished, every operation on the old pseudo stderr object will still be redirected to the sink&quot; do
+		pseudo_stderr = nil
+		report_app_init_status(@temp_channel, @sink) do
+			pseudo_stderr = STDERR
+			raise StandardError, &quot;:-(&quot; if @raise_error
+		end
+		
+		pseudo_stderr.puts &quot;hello world&quot;
+		@sink.string.should =~ /hello world/
+		
+		@sink.should_receive(:reopen).with(&quot;output.tmp&quot;, &quot;w&quot;)
+		pseudo_stderr.reopen(&quot;output.tmp&quot;, &quot;w&quot;)
+	end
+	
+	specify &quot;after the function has finished, every output operation on the old pseudo stderr object will not be buffered&quot; do
+		pseudo_stderr = nil
+		report_app_init_status(@temp_channel, @sink) do
+			pseudo_stderr = STDERR
+			pseudo_stderr.instance_variable_get(:@buffer).should_not be_nil
+			raise StandardError, &quot;:-(&quot; if @raise_error
+		end
+		pseudo_stderr.instance_variable_get(:@buffer).should be_nil
+	end
+end
+
 describe Utils do
 	include Utils
 	
@@ -76,13 +129,14 @@ describe Utils do
 			success.should be_false
 		end
 		
-		it &quot;reports all data written to stderr&quot; do
+		it &quot;reports all data written to STDERR and $stderr&quot; do
 			a, b = IO.pipe
 			begin
 				pid = safe_fork('utils_spec') do
 					a.close
-					report_app_init_status(MessageChannel.new(b)) do
+					report_app_init_status(MessageChannel.new(b), nil) do
 						STDERR.puts &quot;Something went wrong!&quot;
+						$stderr.puts &quot;Something went wrong again!&quot;
 						exit
 					end
 				end
@@ -93,6 +147,7 @@ describe Utils do
 					violated &quot;No exception raised&quot;
 				rescue AppInitError =&gt; e
 					e.stderr.should =~ /Something went wrong!/
+					e.stderr.should =~ /Something went wrong again!/
 				end
 			ensure
 				a.close rescue nil
@@ -100,23 +155,52 @@ describe Utils do
 			end
 		end
 		
-		it &quot;writes all buffered stderr data to the 'write_stderr_contents_to' argument if the block failed&quot; do
-			stderr_buffer = StringIO.new
-			report_app_init_status(MessageChannel.new(StringIO.new), stderr_buffer) do
-				STDERR.puts &quot;Something went wrong!&quot;
-				raise StandardError, &quot;:-(&quot;
+		it &quot;reports all data written to STDERR and $stderr even if it was reopened&quot; do
+			a, b = IO.pipe
+			begin
+				pid = safe_fork('utils_spec') do
+					a.close
+					report_app_init_status(MessageChannel.new(b), nil) do
+						STDERR.puts &quot;Something went wrong!&quot;
+						STDERR.reopen(&quot;output.tmp&quot;, &quot;w&quot;)
+						STDERR.puts &quot;Something went wrong again!&quot;
+						STDERR.flush
+						$stderr.puts &quot;Something went wrong yet again!&quot;
+						$stderr.flush
+						exit
+					end
+				end
+				b.close
+				
+				begin
+					unmarshal_and_raise_errors(MessageChannel.new(a))
+					violated &quot;No exception raised&quot;
+				rescue AppInitError =&gt; e
+					e.stderr.should =~ /Something went wrong!/
+					e.stderr.should =~ /Something went wrong again!/
+					e.stderr.should =~ /Something went wrong yet again!/
+				end
+				
+				file_contents = File.read(&quot;output.tmp&quot;)
+				file_contents.should =~ /Something went wrong again!/
+				file_contents.should =~ /Something went wrong yet again!/
+			ensure
+				a.close rescue nil
+				b.close rescue nil
+				File.unlink(&quot;output.tmp&quot;) rescue nil
 			end
-			
-			stderr_buffer.string.should =~ /Something went wrong!/
 		end
 		
-		it &quot;writes all buffered stderr data to the 'write_stderr_contents_to' argument if the block succeeded&quot; do
-			stderr_buffer = StringIO.new
-			report_app_init_status(MessageChannel.new(StringIO.new), stderr_buffer) do
-				STDERR.puts &quot;Something went wrong!&quot;
+		describe &quot;if the block failed&quot; do
+			before :each do
+				@raise_error = true
 			end
 			
-			stderr_buffer.string.should =~ /Something went wrong!/
+			it_should_behave_like &quot;a pseudo stderr created by #report_app_init_status&quot;
+		end
+		
+		describe &quot;if the block succeeded&quot; do
+			it_should_behave_like &quot;a pseudo stderr created by #report_app_init_status&quot;
 		end
 	end
 	</diff>
      <filename>test/ruby/utils_spec.rb</filename>
    </modified>
  </modified>
  <removed type="array"/>
  <parents type="array">
    <parent>
      <id>90a77a946051c185fc6e1b177fb27b5a14959d63</id>
    </parent>
  </parents>
  <author>
    <name>Hongli Lai (Phusion)</name>
    <email>hongli@phusion.nl</email>
  </author>
  <url>http://github.com/FooBarWidget/passenger/commit/72416a55bff4c0dfe0aa7513e6963b3f9b4cdd7f</url>
  <id>72416a55bff4c0dfe0aa7513e6963b3f9b4cdd7f</id>
  <committed-date>2009-07-05T03:36:53-07:00</committed-date>
  <authored-date>2009-07-05T03:36:53-07:00</authored-date>
  <message>Improve the STDERR capturing code. Fixes issue #332.

The old code temporarily points STDERR to a temp file, but doesn't take
into account that the app might create a reference to the current STDERR,
i.e. to that temp file object. It also doesn't take into account that apps
might try to reopen STDERR, or that they might access $stderr instead of
STDERR. The new code fixes all of this.</message>
  <tree>7ce49ce85c3c04c9c1ff51119db5dcf4b3bad0a2</tree>
  <committer>
    <name>Hongli Lai (Phusion)</name>
    <email>hongli@phusion.nl</email>
  </committer>
</commit>
