84d6a4b328d027f6945f98d77ce583004e0724b4
[unicorn.git] / test / unit / test_signals.rb
blob84d6a4b328d027f6945f98d77ce583004e0724b4
1 # -*- encoding: binary -*-
3 # Copyright (c) 2009 Eric Wong
4 # You can redistribute it and/or modify it under the same terms as Ruby 1.8 or
5 # the GPLv3
7 # Ensure we stay sane in the face of signals being sent to us
9 require 'test/test_helper'
11 include Unicorn
13 class Dd
14   def initialize(bs, count)
15     @count = count
16     @buf = ' ' * bs
17   end
19   def each(&block)
20     @count.times { yield @buf }
21   end
22 end
24 class SignalsTest < Test::Unit::TestCase
26   def setup
27     @bs = 1 * 1024 * 1024
28     @count = 100
29     @port = unused_port
30     @sock = Tempfile.new('unicorn.sock')
31     @tmp = Tempfile.new('unicorn.write')
32     @tmp.sync = true
33     File.unlink(@sock.path)
34     File.unlink(@tmp.path)
35     @server_opts = {
36       :listeners => [ "127.0.0.1:#@port", @sock.path ],
37       :after_fork => lambda { |server,worker|
38         trap(:HUP) { @tmp.syswrite('.') }
39       },
40     }
41     @server = nil
42   end
44   def teardown
45     reset_sig_handlers
46   end
48   def test_worker_dies_on_dead_master
49     pid = fork {
50       app = lambda { |env| [ 200, {'X-Pid' => "#$$" }, [] ] }
51       opts = @server_opts.merge(:timeout => 3)
52       redirect_test_io { HttpServer.new(app, opts).start.join }
53     }
54     wait_workers_ready("test_stderr.#{pid}.log", 1)
55     sock = TCPSocket.new('127.0.0.1', @port)
56     sock.syswrite("GET / HTTP/1.0\r\n\r\n")
57     buf = sock.readpartial(4096)
58     assert_nil sock.close
59     buf =~ /\bX-Pid: (\d+)\b/ or raise Exception
60     child = $1.to_i
61     wait_master_ready("test_stderr.#{pid}.log")
62     wait_workers_ready("test_stderr.#{pid}.log", 1)
63     Process.kill(:KILL, pid)
64     Process.waitpid(pid)
65     File.unlink("test_stderr.#{pid}.log", "test_stdout.#{pid}.log")
66     t0 = Time.now
67     assert child
68     assert t0
69     assert_raises(Errno::ESRCH) { loop { Process.kill(0, child); sleep 0.2 } }
70     assert((Time.now - t0) < 60)
71   end
73   def test_sleepy_kill
74     rd, wr = IO.pipe
75     pid = fork {
76       rd.close
77       app = lambda { |env| wr.syswrite('.'); sleep; [ 200, {}, [] ] }
78       redirect_test_io { HttpServer.new(app, @server_opts).start.join }
79     }
80     wr.close
81     wait_workers_ready("test_stderr.#{pid}.log", 1)
82     sock = TCPSocket.new('127.0.0.1', @port)
83     sock.syswrite("GET / HTTP/1.0\r\n\r\n")
84     buf = rd.readpartial(1)
85     wait_master_ready("test_stderr.#{pid}.log")
86     Process.kill(:INT, pid)
87     Process.waitpid(pid)
88     assert_equal '.', buf
89     buf = nil
90     assert_raises(EOFError,Errno::ECONNRESET,Errno::EPIPE,Errno::EINVAL,
91                   Errno::EBADF) do
92       buf = sock.sysread(4096)
93     end
94     assert_nil buf
95   end
97   def test_timeout_slow_response
98     pid = fork {
99       app = lambda { |env| sleep }
100       opts = @server_opts.merge(:timeout => 3)
101       redirect_test_io { HttpServer.new(app, opts).start.join }
102     }
103     t0 = Time.now
104     wait_workers_ready("test_stderr.#{pid}.log", 1)
105     sock = TCPSocket.new('127.0.0.1', @port)
106     sock.syswrite("GET / HTTP/1.0\r\n\r\n")
108     buf = nil
109     assert_raises(EOFError,Errno::ECONNRESET,Errno::EPIPE,Errno::EINVAL,
110                   Errno::EBADF) do
111       buf = sock.sysread(4096)
112     end
113     diff = Time.now - t0
114     assert_nil buf
115     assert diff > 1.0, "diff was #{diff.inspect}"
116     assert diff < 60.0
117     ensure
118       Process.kill(:TERM, pid) rescue nil
119   end
121   def test_response_write
122     app = lambda { |env|
123       [ 200, { 'Content-Type' => 'text/plain', 'X-Pid' => Process.pid.to_s },
124         Dd.new(@bs, @count) ]
125     }
126     redirect_test_io { @server = HttpServer.new(app, @server_opts).start }
127     wait_workers_ready("test_stderr.#{$$}.log", 1)
128     sock = TCPSocket.new('127.0.0.1', @port)
129     sock.syswrite("GET / HTTP/1.0\r\n\r\n")
130     buf = ''
131     header_len = pid = nil
132     buf = sock.sysread(16384, buf)
133     pid = buf[/\r\nX-Pid: (\d+)\r\n/, 1].to_i
134     header_len = buf[/\A(.+?\r\n\r\n)/m, 1].size
135     assert pid > 0, "pid not positive: #{pid.inspect}"
136     read = buf.size
137     size_before = @tmp.stat.size
138     assert_raises(EOFError,Errno::ECONNRESET,Errno::EPIPE,Errno::EINVAL,
139                   Errno::EBADF) do
140       loop do
141         3.times { Process.kill(:HUP, pid) }
142         sock.sysread(16384, buf)
143         read += buf.size
144         3.times { Process.kill(:HUP, pid) }
145       end
146     end
148     redirect_test_io { @server.stop(true) }
149     # can't check for == since pending signals get merged
150     assert size_before < @tmp.stat.size
151     got = read - header_len
152     expect = @bs * @count
153     assert_equal(expect, got, "expect=#{expect} got=#{got}")
154     assert_nil sock.close
155   end
157   def test_request_read
158     app = lambda { |env|
159       while env['rack.input'].read(4096)
160       end
161       [ 200, {'Content-Type'=>'text/plain', 'X-Pid'=>Process.pid.to_s}, [] ]
162     }
163     redirect_test_io { @server = HttpServer.new(app, @server_opts).start }
165     wait_workers_ready("test_stderr.#{$$}.log", 1)
166     sock = TCPSocket.new('127.0.0.1', @port)
167     sock.syswrite("GET / HTTP/1.0\r\n\r\n")
168     pid = sock.sysread(4096)[/\r\nX-Pid: (\d+)\r\n/, 1].to_i
169     assert_nil sock.close
171     assert pid > 0, "pid not positive: #{pid.inspect}"
172     sock = TCPSocket.new('127.0.0.1', @port)
173     sock.syswrite("PUT / HTTP/1.0\r\n")
174     sock.syswrite("Content-Length: #{@bs * @count}\r\n\r\n")
175     1000.times { Process.kill(:HUP, pid) }
176     size_before = @tmp.stat.size
177     killer = fork { loop { Process.kill(:HUP, pid); sleep(0.0001) } }
178     buf = ' ' * @bs
179     @count.times { sock.syswrite(buf) }
180     Process.kill(:KILL, killer)
181     Process.waitpid2(killer)
182     redirect_test_io { @server.stop(true) }
183     # can't check for == since pending signals get merged
184     assert size_before < @tmp.stat.size
185     assert_equal pid, sock.sysread(4096)[/\r\nX-Pid: (\d+)\r\n/, 1].to_i
186     assert_nil sock.close
187   end