add GPLv3 option to the license
[unicorn.git] / test / unit / test_signals.rb
blob0adf153b74719f1322ddb79b4c3afd77274f6203
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     child = sock = buf = t0 = nil
55     assert_nothing_raised do
56       wait_workers_ready("test_stderr.#{pid}.log", 1)
57       sock = TCPSocket.new('127.0.0.1', @port)
58       sock.syswrite("GET / HTTP/1.0\r\n\r\n")
59       buf = sock.readpartial(4096)
60       sock.close
61       buf =~ /\bX-Pid: (\d+)\b/ or raise Exception
62       child = $1.to_i
63       wait_master_ready("test_stderr.#{pid}.log")
64       wait_workers_ready("test_stderr.#{pid}.log", 1)
65       Process.kill(:KILL, pid)
66       Process.waitpid(pid)
67       File.unlink("test_stderr.#{pid}.log", "test_stdout.#{pid}.log")
68       t0 = Time.now
69     end
70     assert child
71     assert t0
72     assert_raises(Errno::ESRCH) { loop { Process.kill(0, child); sleep 0.2 } }
73     assert((Time.now - t0) < 60)
74   end
76   def test_sleepy_kill
77     rd, wr = IO.pipe
78     pid = fork {
79       rd.close
80       app = lambda { |env| wr.syswrite('.'); sleep; [ 200, {}, [] ] }
81       redirect_test_io { HttpServer.new(app, @server_opts).start.join }
82     }
83     sock = buf = nil
84     wr.close
85     assert_nothing_raised do
86       wait_workers_ready("test_stderr.#{pid}.log", 1)
87       sock = TCPSocket.new('127.0.0.1', @port)
88       sock.syswrite("GET / HTTP/1.0\r\n\r\n")
89       buf = rd.readpartial(1)
90       wait_master_ready("test_stderr.#{pid}.log")
91       Process.kill(:INT, pid)
92       Process.waitpid(pid)
93     end
94     assert_equal '.', buf
95     buf = nil
96     assert_raises(EOFError,Errno::ECONNRESET,Errno::EPIPE,Errno::EINVAL,
97                   Errno::EBADF) do
98       buf = sock.sysread(4096)
99     end
100     assert_nil buf
101     ensure
102   end
104   def test_timeout_slow_response
105     pid = fork {
106       app = lambda { |env| sleep }
107       opts = @server_opts.merge(:timeout => 3)
108       redirect_test_io { HttpServer.new(app, opts).start.join }
109     }
110     t0 = Time.now
111     sock = nil
112     assert_nothing_raised do
113       wait_workers_ready("test_stderr.#{pid}.log", 1)
114       sock = TCPSocket.new('127.0.0.1', @port)
115       sock.syswrite("GET / HTTP/1.0\r\n\r\n")
116     end
118     buf = nil
119     assert_raises(EOFError,Errno::ECONNRESET,Errno::EPIPE,Errno::EINVAL,
120                   Errno::EBADF) do
121       buf = sock.sysread(4096)
122     end
123     diff = Time.now - t0
124     assert_nil buf
125     assert diff > 1.0, "diff was #{diff.inspect}"
126     assert diff < 60.0
127     ensure
128       Process.kill(:TERM, pid) rescue nil
129   end
131   def test_response_write
132     app = lambda { |env|
133       [ 200, { 'Content-Type' => 'text/plain', 'X-Pid' => Process.pid.to_s },
134         Dd.new(@bs, @count) ]
135     }
136     redirect_test_io { @server = HttpServer.new(app, @server_opts).start }
137     sock = nil
138     assert_nothing_raised do
139       wait_workers_ready("test_stderr.#{$$}.log", 1)
140       sock = TCPSocket.new('127.0.0.1', @port)
141       sock.syswrite("GET / HTTP/1.0\r\n\r\n")
142     end
143     buf = ''
144     header_len = pid = nil
145     assert_nothing_raised do
146       buf = sock.sysread(16384, buf)
147       pid = buf[/\r\nX-Pid: (\d+)\r\n/, 1].to_i
148       header_len = buf[/\A(.+?\r\n\r\n)/m, 1].size
149     end
150     assert pid > 0, "pid not positive: #{pid.inspect}"
151     read = buf.size
152     size_before = @tmp.stat.size
153     assert_raises(EOFError,Errno::ECONNRESET,Errno::EPIPE,Errno::EINVAL,
154                   Errno::EBADF) do
155       loop do
156         3.times { Process.kill(:HUP, pid) }
157         sock.sysread(16384, buf)
158         read += buf.size
159         3.times { Process.kill(:HUP, pid) }
160       end
161     end
163     redirect_test_io { @server.stop(true) }
164     # can't check for == since pending signals get merged
165     assert size_before < @tmp.stat.size
166     got = read - header_len
167     expect = @bs * @count
168     assert_equal(expect, got, "expect=#{expect} got=#{got}")
169     assert_nothing_raised { sock.close }
170   end
172   def test_request_read
173     app = lambda { |env|
174       while env['rack.input'].read(4096)
175       end
176       [ 200, {'Content-Type'=>'text/plain', 'X-Pid'=>Process.pid.to_s}, [] ]
177     }
178     redirect_test_io { @server = HttpServer.new(app, @server_opts).start }
179     pid = nil
181     assert_nothing_raised do
182       wait_workers_ready("test_stderr.#{$$}.log", 1)
183       sock = TCPSocket.new('127.0.0.1', @port)
184       sock.syswrite("GET / HTTP/1.0\r\n\r\n")
185       pid = sock.sysread(4096)[/\r\nX-Pid: (\d+)\r\n/, 1].to_i
186       sock.close
187     end
189     assert pid > 0, "pid not positive: #{pid.inspect}"
190     sock = TCPSocket.new('127.0.0.1', @port)
191     sock.syswrite("PUT / HTTP/1.0\r\n")
192     sock.syswrite("Content-Length: #{@bs * @count}\r\n\r\n")
193     1000.times { Process.kill(:HUP, pid) }
194     size_before = @tmp.stat.size
195     killer = fork { loop { Process.kill(:HUP, pid); sleep(0.0001) } }
196     buf = ' ' * @bs
197     @count.times { sock.syswrite(buf) }
198     Process.kill(:KILL, killer)
199     Process.waitpid2(killer)
200     redirect_test_io { @server.stop(true) }
201     # can't check for == since pending signals get merged
202     assert size_before < @tmp.stat.size
203     assert_equal pid, sock.sysread(4096)[/\r\nX-Pid: (\d+)\r\n/, 1].to_i
204     sock.close
205   end