summary refs log tree commit homepage
diff options
context:
space:
mode:
authorEric Wong <normalperson@yhbt.net>2009-07-16 01:13:33 -0700
committerEric Wong <normalperson@yhbt.net>2009-07-16 01:13:33 -0700
commit2e913054b36848a05f7ba06c3accbe164c666708 (patch)
tree3dbac8d2be3f7ba7a00cd23fff146c40e0a3d747
parenta0f2c4514e969d0a127227201cbdb8e57f71df63 (diff)
This simplifies chunked_reader substantially with a slight
increase in tee_input complexity.  This is beneficial because
chunked_reader is more complex to begin with and more likely
to experience correctness issues.
-rw-r--r--lib/unicorn/chunked_reader.rb17
-rw-r--r--lib/unicorn/tee_input.rb31
-rw-r--r--test/unit/test_chunked_reader.rb59
-rw-r--r--test/unit/test_tee_input.rb66
4 files changed, 84 insertions, 89 deletions
diff --git a/lib/unicorn/chunked_reader.rb b/lib/unicorn/chunked_reader.rb
index 539b350..b813da6 100644
--- a/lib/unicorn/chunked_reader.rb
+++ b/lib/unicorn/chunked_reader.rb
@@ -32,23 +32,6 @@ module Unicorn
       buf
     end
 
-    def gets
-      line = nil
-      begin
-        line = readpartial(Const::CHUNK_SIZE)
-        begin
-          if line.sub!(%r{\A(.*?#{$/})}, Z)
-            @chunk_left += line.size
-            @buf = @buf ? (line << @buf) : line
-            return $1.dup
-          end
-          line << readpartial(Const::CHUNK_SIZE)
-        end while true
-      rescue EOFError
-        return line
-      end
-    end
-
   private
 
     def last_block(max = nil)
diff --git a/lib/unicorn/tee_input.rb b/lib/unicorn/tee_input.rb
index 06028a6..1bcbf1d 100644
--- a/lib/unicorn/tee_input.rb
+++ b/lib/unicorn/tee_input.rb
@@ -73,22 +73,25 @@ module Unicorn
       @input or return @tmp.gets
       nil == $/ and return read
 
-      line = nil
-      if @tmp.pos < @tmp.stat.size
-        line = @tmp.gets # cannot be nil here
-        $/ == line[-$/.size, $/.size] and return line
-
-        # half the line was already read, and the rest of has not been read
-        if buf = @input.gets
-          @tmp.write(buf)
-          line << buf
-        else
-          @input = nil
-        end
-      elsif line = @input.gets
-        @tmp.write(line)
+      orig_size = @tmp.stat.size
+      if @tmp.pos == orig_size
+        tee(Const::CHUNK_SIZE, Z.dup) or return nil
+        @tmp.seek(orig_size)
       end
 
+      line = @tmp.gets # cannot be nil here since size > pos
+      $/ == line[-$/.size, $/.size] and return line
+
+      # unlikely, if we got here, then @tmp is at EOF
+      begin
+        orig_size = @tmp.stat.size
+        tee(Const::CHUNK_SIZE, Z.dup) or break
+        @tmp.seek(orig_size)
+        line << @tmp.gets
+        $/ == line[-$/.size, $/.size] and return line
+        # @tmp is at EOF again here, retry the loop
+      end while true
+
       line
     end
 
diff --git a/test/unit/test_chunked_reader.rb b/test/unit/test_chunked_reader.rb
index 6aaa72a..a1323e9 100644
--- a/test/unit/test_chunked_reader.rb
+++ b/test/unit/test_chunked_reader.rb
@@ -53,63 +53,6 @@ class TestChunkedReader < Test::Unit::TestCase
     assert_raises(EOFError) { cr.readpartial(8192) }
   end
 
-  def test_gets1
-    cr = bin_reader("4\r\nasdf\r\n0\r\n")
-    STDOUT.sync = true
-    assert_equal 'asdf', cr.gets
-    assert_raises(EOFError) { cr.readpartial(8192) }
-  end
-
-  def test_gets2
-    cr = bin_reader("4\r\nasd\n\r\n0\r\n\r\n")
-    assert_equal "asd\n", cr.gets
-    assert_nil cr.gets
-  end
-
-  def test_gets3
-    max = Unicorn::Const::CHUNK_SIZE * 2
-    str = ('a' * max).freeze
-    first = 5
-    last = str.size - first
-    cr = bin_reader(
-      "#{'%x' % first}\r\n#{str[0, first]}\r\n" \
-      "#{'%x' % last}\r\n#{str[-last, last]}\r\n" \
-      "0\r\n")
-    assert_equal str, cr.gets
-    assert_nil cr.gets
-  end
-
-  def test_readpartial_gets_mixed1
-    max = Unicorn::Const::CHUNK_SIZE * 2
-    str = ('a' * max).freeze
-    first = 5
-    last = str.size - first
-    cr = bin_reader(
-      "#{'%x' % first}\r\n#{str[0, first]}\r\n" \
-      "#{'%x' % last}\r\n#{str[-last, last]}\r\n" \
-      "0\r\n")
-    partial = cr.readpartial(16384)
-    assert String === partial
-
-    len = max - partial.size
-    assert_equal(str[-len, len], cr.gets)
-    assert_raises(EOFError) { cr.readpartial(1) }
-    assert_nil cr.gets
-  end
-
-  def test_gets_mixed_readpartial
-    max = 10
-    str = ("z\n" * max).freeze
-    first = 5
-    last = str.size - first
-    cr = bin_reader(
-      "#{'%x' % first}\r\n#{str[0, first]}\r\n" \
-      "#{'%x' % last}\r\n#{str[-last, last]}\r\n" \
-      "0\r\n")
-    assert_equal("z\n", cr.gets)
-    assert_equal("z\n", cr.gets)
-  end
-
   def test_dd
     cr = bin_reader("6\r\nhello\n\r\n")
     tmp = Tempfile.new('test_dd')
@@ -138,7 +81,7 @@ class TestChunkedReader < Test::Unit::TestCase
         exit 0
       end while true
     }
-    assert_equal "hello\n", cr.gets
+    assert_equal "hello\n", cr.readpartial(6)
     sha1 = Digest::SHA1.new
     buf = Unicorn::Z.dup
     begin
diff --git a/test/unit/test_tee_input.rb b/test/unit/test_tee_input.rb
new file mode 100644
index 0000000..a6c61e6
--- /dev/null
+++ b/test/unit/test_tee_input.rb
@@ -0,0 +1,66 @@
+# encoding: binary
+require 'test/unit'
+require 'digest/sha1'
+require 'unicorn'
+
+class TestTeeInput < Test::Unit::TestCase
+
+  def setup
+    @rs = $/
+    @env = {}
+    @rd, @wr = IO.pipe
+    @rd.sync = @wr.sync = true
+    @start_pid = $$
+  end
+
+  def teardown
+    return if $$ != @start_pid
+    $/ = @rs
+    @rd.close rescue nil
+    @wr.close rescue nil
+    begin
+      Process.wait
+    rescue Errno::ECHILD
+      break
+    end while true
+  end
+
+  def test_gets_long
+    ti = Unicorn::TeeInput.new(@rd, nil, "hello")
+    status = line = nil
+    pid = fork {
+      @rd.close
+      3.times { @wr.write("ffff" * 4096) }
+      @wr.write "#$/foo#$/"
+      @wr.close
+    }
+    @wr.close
+    assert_nothing_raised { line = ti.gets }
+    assert_equal(4096 * 4 * 3 + 5 + $/.size, line.size)
+    assert_equal("hello" << ("ffff" * 4096 * 3) << "#$/", line)
+    assert_nothing_raised { line = ti.gets }
+    assert_equal "foo#$/", line
+    assert_nil ti.gets
+    assert_nothing_raised { pid, status = Process.waitpid2(pid) }
+    assert status.success?
+  end
+
+  def test_gets_short
+    ti = Unicorn::TeeInput.new(@rd, nil, "hello")
+    status = line = nil
+    pid = fork {
+      @rd.close
+      @wr.write "#$/foo"
+      @wr.close
+    }
+    @wr.close
+    assert_nothing_raised { line = ti.gets }
+    assert_equal("hello#$/", line)
+    assert_nothing_raised { line = ti.gets }
+    assert_equal "foo", line
+    assert_nil ti.gets
+    assert_nothing_raised { pid, status = Process.waitpid2(pid) }
+    assert status.success?
+  end
+
+end