about summary refs log tree commit homepage
path: root/test/unit/test_signals.rb
diff options
context:
space:
mode:
Diffstat (limited to 'test/unit/test_signals.rb')
-rw-r--r--test/unit/test_signals.rb191
1 files changed, 191 insertions, 0 deletions
diff --git a/test/unit/test_signals.rb b/test/unit/test_signals.rb
new file mode 100644
index 0000000..ef66ed6
--- /dev/null
+++ b/test/unit/test_signals.rb
@@ -0,0 +1,191 @@
+# Copyright (c) 2009 Eric Wong
+# You can redistribute it and/or modify it under the same terms as Ruby.
+#
+# Ensure we stay sane in the face of signals being sent to us
+
+require 'test/test_helper'
+
+include Unicorn
+
+class Dd
+  def initialize(bs, count)
+    @count = count
+    @buf = ' ' * bs
+  end
+
+  def each(&block)
+    @count.times { yield @buf }
+  end
+end
+
+class SignalsTest < Test::Unit::TestCase
+
+  def setup
+    @bs = 1 * 1024 * 1024
+    @count = 100
+    @port = unused_port
+    tmp = @tmp = Tempfile.new('unicorn.sock')
+    File.unlink(@tmp.path)
+    n = 0
+    tmp.chmod(0)
+    @server_opts = {
+      :listeners => [ "127.0.0.1:#@port", @tmp.path ],
+      :after_fork => lambda { |server,worker|
+        trap(:HUP) { tmp.chmod(n += 1) }
+      },
+    }
+    @server = nil
+  end
+
+  def test_worker_dies_on_dead_master
+    pid = fork {
+      app = lambda { |env| [ 200, {'X-Pid' => "#$$" }, [] ] }
+      opts = @server_opts.merge(:timeout => 3)
+      redirect_test_io { HttpServer.new(app, opts).start.join }
+    }
+    child = sock = buf = t0 = nil
+    assert_nothing_raised do
+      wait_workers_ready("test_stderr.#{pid}.log", 1)
+      sock = TCPSocket.new('127.0.0.1', @port)
+      sock.syswrite("GET / HTTP/1.0\r\n\r\n")
+      buf = sock.readpartial(4096)
+      sock.close
+      buf =~ /\bX-Pid: (\d+)\b/ or raise Exception
+      child = $1.to_i
+      wait_master_ready("test_stderr.#{pid}.log")
+      Process.kill(:KILL, pid)
+      Process.waitpid(pid)
+      t0 = Time.now
+    end
+    assert child
+    assert t0
+    assert_raises(Errno::ESRCH) { loop { Process.kill(0, child); sleep 0.2 } }
+    assert((Time.now - t0) < 60)
+  end
+
+  def test_sleepy_kill
+    rd, wr = IO.pipe
+    pid = fork {
+      rd.close
+      app = lambda { |env| wr.syswrite('.'); sleep; [ 200, {}, [] ] }
+      redirect_test_io { HttpServer.new(app, @server_opts).start.join }
+    }
+    sock = buf = nil
+    wr.close
+    assert_nothing_raised do
+      wait_workers_ready("test_stderr.#{pid}.log", 1)
+      sock = TCPSocket.new('127.0.0.1', @port)
+      sock.syswrite("GET / HTTP/1.0\r\n\r\n")
+      buf = rd.readpartial(1)
+      wait_master_ready("test_stderr.#{pid}.log")
+      Process.kill(:INT, pid)
+      Process.waitpid(pid)
+    end
+    assert_equal '.', buf
+    buf = nil
+    assert_raises(EOFError,Errno::ECONNRESET,Errno::EPIPE,Errno::EINVAL,
+                  Errno::EBADF) do
+      buf = sock.sysread(4096)
+    end
+    assert_nil buf
+    ensure
+  end
+
+  def test_timeout_slow_response
+    pid = fork {
+      app = lambda { |env| sleep }
+      opts = @server_opts.merge(:timeout => 3)
+      redirect_test_io { HttpServer.new(app, opts).start.join }
+    }
+    t0 = Time.now
+    sock = nil
+    assert_nothing_raised do
+      wait_workers_ready("test_stderr.#{pid}.log", 1)
+      sock = TCPSocket.new('127.0.0.1', @port)
+      sock.syswrite("GET / HTTP/1.0\r\n\r\n")
+    end
+
+    buf = nil
+    assert_raises(EOFError,Errno::ECONNRESET,Errno::EPIPE,Errno::EINVAL,
+                  Errno::EBADF) do
+      buf = sock.sysread(4096)
+    end
+    diff = Time.now - t0
+    assert_nil buf
+    assert diff > 1.0, "diff was #{diff.inspect}"
+    assert diff < 60.0
+    ensure
+      Process.kill(:QUIT, pid) rescue nil
+  end
+
+  def test_response_write
+    app = lambda { |env|
+      [ 200, { 'Content-Type' => 'text/plain', 'X-Pid' => Process.pid.to_s },
+        Dd.new(@bs, @count) ]
+    }
+    redirect_test_io { @server = HttpServer.new(app, @server_opts).start }
+    sock = nil
+    assert_nothing_raised do
+      wait_workers_ready("test_stderr.#{$$}.log", 1)
+      sock = TCPSocket.new('127.0.0.1', @port)
+      sock.syswrite("GET / HTTP/1.0\r\n\r\n")
+    end
+    buf = ''
+    header_len = pid = nil
+    assert_nothing_raised do
+      buf = sock.sysread(16384, buf)
+      pid = buf[/\r\nX-Pid: (\d+)\r\n/, 1].to_i
+      header_len = buf[/\A(.+?\r\n\r\n)/m, 1].size
+    end
+    read = buf.size
+    mode_before = @tmp.stat.mode
+    assert_raises(EOFError,Errno::ECONNRESET,Errno::EPIPE,Errno::EINVAL,
+                  Errno::EBADF) do
+      loop do
+        3.times { Process.kill(:HUP, pid) }
+        sock.sysread(16384, buf)
+        read += buf.size
+        3.times { Process.kill(:HUP, pid) }
+      end
+    end
+
+    redirect_test_io { @server.stop(true) }
+    # can't check for == since pending signals get merged
+    assert mode_before < @tmp.stat.mode
+    assert_equal(read - header_len, @bs * @count)
+    assert_nothing_raised { sock.close }
+  end
+
+  def test_request_read
+    app = lambda { |env|
+      [ 200, {'Content-Type'=>'text/plain', 'X-Pid'=>Process.pid.to_s}, [] ]
+    }
+    redirect_test_io { @server = HttpServer.new(app, @server_opts).start }
+    pid = nil
+
+    assert_nothing_raised do
+      wait_workers_ready("test_stderr.#{$$}.log", 1)
+      sock = TCPSocket.new('127.0.0.1', @port)
+      sock.syswrite("GET / HTTP/1.0\r\n\r\n")
+      pid = sock.sysread(4096)[/\r\nX-Pid: (\d+)\r\n/, 1].to_i
+      sock.close
+    end
+
+    sock = TCPSocket.new('127.0.0.1', @port)
+    sock.syswrite("PUT / HTTP/1.0\r\n")
+    sock.syswrite("Content-Length: #{@bs * @count}\r\n\r\n")
+    1000.times { Process.kill(:HUP, pid) }
+    mode_before = @tmp.stat.mode
+    killer = fork { loop { Process.kill(:HUP, pid); sleep(0.0001) } }
+    buf = ' ' * @bs
+    @count.times { sock.syswrite(buf) }
+    Process.kill(:TERM, killer)
+    Process.waitpid2(killer)
+    redirect_test_io { @server.stop(true) }
+    # can't check for == since pending signals get merged
+    assert mode_before < @tmp.stat.mode
+    assert_equal pid, sock.sysread(4096)[/\r\nX-Pid: (\d+)\r\n/, 1].to_i
+    sock.close
+  end
+
+end