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