yahns 0.0.0TP1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (58) hide show
  1. checksums.yaml +7 -0
  2. data/.gitignore +15 -0
  3. data/COPYING +674 -0
  4. data/GIT-VERSION-GEN +41 -0
  5. data/GNUmakefile +90 -0
  6. data/README +127 -0
  7. data/Rakefile +60 -0
  8. data/bin/yahns +32 -0
  9. data/examples/README +3 -0
  10. data/examples/init.sh +76 -0
  11. data/examples/logger_mp_safe.rb +28 -0
  12. data/examples/logrotate.conf +32 -0
  13. data/examples/yahns_multi.conf.rb +89 -0
  14. data/examples/yahns_rack_basic.conf.rb +27 -0
  15. data/lib/yahns.rb +73 -0
  16. data/lib/yahns/acceptor.rb +28 -0
  17. data/lib/yahns/client_expire.rb +40 -0
  18. data/lib/yahns/client_expire_portable.rb +39 -0
  19. data/lib/yahns/config.rb +344 -0
  20. data/lib/yahns/daemon.rb +51 -0
  21. data/lib/yahns/fdmap.rb +90 -0
  22. data/lib/yahns/http_client.rb +198 -0
  23. data/lib/yahns/http_context.rb +65 -0
  24. data/lib/yahns/http_response.rb +184 -0
  25. data/lib/yahns/log.rb +73 -0
  26. data/lib/yahns/queue.rb +7 -0
  27. data/lib/yahns/queue_egg.rb +23 -0
  28. data/lib/yahns/queue_epoll.rb +57 -0
  29. data/lib/yahns/rack.rb +80 -0
  30. data/lib/yahns/server.rb +336 -0
  31. data/lib/yahns/server_mp.rb +181 -0
  32. data/lib/yahns/sigevent.rb +7 -0
  33. data/lib/yahns/sigevent_efd.rb +18 -0
  34. data/lib/yahns/sigevent_pipe.rb +29 -0
  35. data/lib/yahns/socket_helper.rb +117 -0
  36. data/lib/yahns/stream_file.rb +34 -0
  37. data/lib/yahns/stream_input.rb +150 -0
  38. data/lib/yahns/tee_input.rb +114 -0
  39. data/lib/yahns/tmpio.rb +27 -0
  40. data/lib/yahns/wbuf.rb +36 -0
  41. data/lib/yahns/wbuf_common.rb +32 -0
  42. data/lib/yahns/worker.rb +58 -0
  43. data/test/covshow.rb +29 -0
  44. data/test/helper.rb +115 -0
  45. data/test/server_helper.rb +65 -0
  46. data/test/test_bin.rb +97 -0
  47. data/test/test_client_expire.rb +132 -0
  48. data/test/test_config.rb +56 -0
  49. data/test/test_fdmap.rb +19 -0
  50. data/test/test_output_buffering.rb +291 -0
  51. data/test/test_queue.rb +59 -0
  52. data/test/test_rack.rb +28 -0
  53. data/test/test_serve_static.rb +42 -0
  54. data/test/test_server.rb +415 -0
  55. data/test/test_stream_file.rb +30 -0
  56. data/test/test_wbuf.rb +136 -0
  57. data/yahns.gemspec +19 -0
  58. metadata +165 -0
@@ -0,0 +1,97 @@
1
+ # Copyright (C) 2013, Eric Wong <normalperson@yhbt.net> and all contributors
2
+ # License: GPLv3 or later (https://www.gnu.org/licenses/gpl-3.0.txt)
3
+ require_relative 'server_helper'
4
+ class TestBin < Testcase
5
+ parallelize_me!
6
+ include ServerHelper
7
+ alias teardown server_helper_teardown
8
+
9
+ def setup
10
+ server_helper_setup
11
+ @cmd = %W(ruby -I lib bin/yahns)
12
+ end
13
+
14
+ def test_bin_daemon_noworker_inherit
15
+ bin_daemon(false, true)
16
+ end
17
+
18
+ def test_bin_daemon_worker_inherit
19
+ bin_daemon(true, true)
20
+ end
21
+
22
+ def test_bin_daemon_noworker_bind
23
+ bin_daemon(false, false)
24
+ end
25
+
26
+ def test_bin_daemon_worker_bind
27
+ bin_daemon(true, false)
28
+ end
29
+
30
+ def bin_daemon(worker, inherit)
31
+ @srv.close unless inherit
32
+ @pid = tmpfile(%w(test_bin_daemon .pid))
33
+ @ru = tmpfile(%w(test_bin_daemon .ru))
34
+ @ru.write("require 'rack/lobster'; run Rack::Lobster.new\n")
35
+ cfg = tmpfile(%w(test_bin_daemon_conf .rb))
36
+ cfg.puts "pid '#{@pid.path}'"
37
+ cfg.puts "stderr_path '#{@err.path}'"
38
+ cfg.puts "worker_processes 1" if worker
39
+ cfg.puts "app(:rack, '#{@ru.path}', preload: false) do"
40
+ cfg.puts " listen ENV['YAHNS_TEST_LISTEN']"
41
+ cfg.puts "end"
42
+ @cmd.concat(%W(-D -c #{cfg.path}))
43
+ addr = IO.pipe
44
+ pid = fork do
45
+ if inherit
46
+ @cmd << { @srv.fileno => @srv }
47
+ ENV["YAHNS_FD"] = @srv.fileno.to_s
48
+ else
49
+ @srv = TCPServer.new(ENV["TEST_HOST"] || "127.0.0.1", 0)
50
+ end
51
+ host, port = @srv.addr[3], @srv.addr[1]
52
+ listen = ENV["YAHNS_TEST_LISTEN"] = "#{host}:#{port}"
53
+ addr[1].write(listen)
54
+ addr[1].close
55
+ addr[0].close
56
+ exec(*@cmd)
57
+ end
58
+ addr[1].close
59
+ listen = Timeout.timeout(10) { addr[0].read }
60
+ addr[0].close
61
+ host, port = listen.split(/:/, 2)
62
+ port = port.to_i
63
+ assert_operator port, :>, 0
64
+
65
+ unless inherit
66
+ # daemon_pipe guarantees socket will be usable after this:
67
+ Timeout.timeout(10) do # Ruby startup is slow!
68
+ _, status = Process.waitpid2(pid)
69
+ assert status.success?, status.inspect
70
+ end
71
+ end
72
+
73
+ Net::HTTP.start(host, port) do |http|
74
+ req = Net::HTTP::Get.new("/")
75
+ res = http.request(req)
76
+ assert_equal 200, res.code.to_i
77
+ assert_equal "keep-alive", res["Connection"]
78
+ end
79
+ rescue => e
80
+ warn "#{e.message} (#{e.class})"
81
+ e.backtrace.each { |l| warn "#{l}" }
82
+ raise
83
+ ensure
84
+ cfg.close! if cfg
85
+ pid = File.read(@pid.path)
86
+ pid = pid.to_i
87
+ assert_operator pid, :>, 0
88
+ Process.kill(:QUIT, pid)
89
+ if inherit
90
+ _, status = Timeout.timeout(10) { Process.waitpid2(pid) }
91
+ assert status.success?, status.inspect
92
+ else
93
+ poke_until_dead pid
94
+ end
95
+ @pid.close! if @pid
96
+ end
97
+ end
@@ -0,0 +1,132 @@
1
+ # Copyright (C) 2013, Eric Wong <normalperson@yhbt.net> and all contributors
2
+ # License: GPLv3 or later (https://www.gnu.org/licenses/gpl-3.0.txt)
3
+ require_relative 'server_helper'
4
+
5
+ class TestClientExpire < Testcase
6
+ parallelize_me!
7
+ include ServerHelper
8
+ alias setup server_helper_setup
9
+ alias teardown server_helper_teardown
10
+
11
+ def test_client_expire_negative
12
+ err = @err
13
+ cfg = Yahns::Config.new
14
+ host, port = @srv.addr[3], @srv.addr[1]
15
+ cfg.instance_eval do
16
+ GTL.synchronize do
17
+ ru = lambda { |e| h = { "Content-Length" => "0" }; [ 200, h, [] ] }
18
+ app(:rack, ru) do
19
+ listen "#{host}:#{port}", sndbuf: 2048, rcvbuf: 2048
20
+ end
21
+ client_expire_threshold(-10)
22
+ end
23
+ logger(Logger.new(err.path))
24
+ end
25
+ srv = Yahns::Server.new(cfg)
26
+ pid = fork do
27
+ ENV["YAHNS_FD"] = @srv.fileno.to_s
28
+ srv.start.join
29
+ end
30
+ Net::HTTP.start(host, port) { |h|
31
+ res = h.get("/")
32
+ assert_empty res.body
33
+ }
34
+ ensure
35
+ quit_wait(pid)
36
+ end
37
+
38
+ def test_client_expire
39
+ nr = 32
40
+ err = @err
41
+ cfg = Yahns::Config.new
42
+ host, port = @srv.addr[3], @srv.addr[1]
43
+ cfg.instance_eval do
44
+ GTL.synchronize do
45
+ h = { "Content-Length" => "0" }
46
+ app(:rack, lambda { |e| [ 200, h, [] ]}) do
47
+ listen "#{host}:#{port}", sndbuf: 2048, rcvbuf: 2048
48
+ client_timeout 1
49
+ end
50
+ client_expire_threshold(32)
51
+ end
52
+ logger(Logger.new(err.path))
53
+ end
54
+ srv = Yahns::Server.new(cfg)
55
+ pid = fork do
56
+ ENV["YAHNS_FD"] = @srv.fileno.to_s
57
+ srv.start.join
58
+ end
59
+ f = TCPSocket.new(host, port)
60
+ s = TCPSocket.new(host, port)
61
+ req = "GET / HTTP/1.1\r\nHost: example.com\r\n\r\n"
62
+ s.write(req)
63
+ str = Timeout.timeout(20) { s.readpartial(666) }
64
+ assert_match(%r{keep-alive}, str)
65
+ sleep 2
66
+ abe = tmpfile(%w(abe .err))
67
+ ab_res = `ab -c #{nr} -n 10000 -k http://#{host}:#{port}/ 2>#{abe.path}`
68
+ assert $?.success?, $?.inspect << abe.read
69
+ abe.close!
70
+ assert_match(/Complete requests:\s+10000\n/, ab_res)
71
+
72
+ [ f, s ].each do |io|
73
+ assert_raises(Errno::EPIPE,Errno::ECONNRESET) do
74
+ req.each_byte { |b| io.write(b.chr) }
75
+ end
76
+ io.close
77
+ end
78
+ rescue => e
79
+ Yahns::Log.exception(Logger.new($stderr), "test", e)
80
+ raise
81
+ ensure
82
+ quit_wait(pid)
83
+ end
84
+
85
+ def test_client_expire_desperate
86
+ skip "disabled since FD counts vary heavily in an MT process"
87
+ err = @err
88
+ cfg = Yahns::Config.new
89
+ host, port = @srv.addr[3], @srv.addr[1]
90
+ cfg.instance_eval do
91
+ GTL.synchronize do
92
+ h = { "Content-Length" => "0" }
93
+ app(:rack, lambda { |e| [ 200, h, [] ]}) do
94
+ listen "#{host}:#{port}", sndbuf: 2048, rcvbuf: 2048
95
+ client_timeout 1
96
+ end
97
+ client_expire_threshold 1.0
98
+ end
99
+ logger(Logger.new(err.path))
100
+ end
101
+ srv = Yahns::Server.new(cfg)
102
+ pid = fork do
103
+ Process.setrlimit :NOFILE, 512, 1024
104
+ ENV["YAHNS_FD"] = @srv.fileno.to_s
105
+ srv.start.join
106
+ end
107
+ f = TCPSocket.new(host, port)
108
+ s = TCPSocket.new(host, port)
109
+ req = "GET / HTTP/1.1\r\nHost: example.com\r\n\r\n"
110
+ s.write(req)
111
+ str = Timeout.timeout(20) { s.readpartial(666) }
112
+ assert_match(%r{keep-alive}, str)
113
+ sleep 3
114
+ system "ab -c 1000 -n 10000 -k http://#{host}:#{port}/ 2>&1"
115
+
116
+ [ f, s ].each do |io|
117
+ assert_raises(Errno::EPIPE,Errno::ECONNRESET) do
118
+ req.each_byte { |b| io.write(b.chr) }
119
+ end
120
+ io.close
121
+ end
122
+ errs = File.readlines(err.path).grep(/ERROR/)
123
+ File.truncate(err.path, 0) # avoid error on teardown
124
+ re = %r{consider raising open file limits} # - accept, consider raising open file limits}
125
+ assert_equal errs.grep(re), errs
126
+ rescue => e
127
+ Yahns::Log.exception(Logger.new($stderr), "test", e)
128
+ raise
129
+ ensure
130
+ quit_wait(pid)
131
+ end
132
+ end
@@ -0,0 +1,56 @@
1
+ # Copyright (C) 2013, Eric Wong <normalperson@yhbt.net> and all contributors
2
+ # License: GPLv3 or later (https://www.gnu.org/licenses/gpl-3.0.txt)
3
+ require_relative 'helper'
4
+ require 'rack/lobster'
5
+
6
+ class TestConfig < Testcase
7
+ parallelize_me!
8
+
9
+ def test_initialize
10
+ cfg = Yahns::Config.new
11
+ assert_instance_of Yahns::Config, cfg
12
+ end
13
+
14
+ def test_multi_conf_example
15
+ tmpdir = Dir.mktmpdir
16
+
17
+ # modify the example config file for testing
18
+ path = "examples/yahns_multi.conf.rb"
19
+ cfgs = File.read(path)
20
+ cfgs.gsub!(%r{/path/to/}, "#{tmpdir}/")
21
+ conf = File.open("#{tmpdir}/yahns_multi.conf.rb", "w")
22
+ conf.sync = true
23
+ conf.write(cfgs)
24
+ File.open("#{tmpdir}/another.ru", "w") do |fp|
25
+ fp.puts("run Rack::Lobster.new\n")
26
+ end
27
+ FileUtils.mkpath("#{tmpdir}/another")
28
+
29
+ cfg = GTL.synchronize { Yahns::Config.new(conf.path) }
30
+ assert_instance_of Yahns::Config, cfg
31
+ ensure
32
+ FileUtils.rm_rf(tmpdir) if tmpdir
33
+ end
34
+
35
+ def test_rack_basic_conf_example
36
+ tmpdir = Dir.mktmpdir
37
+
38
+ # modify the example config file for testing
39
+ path = "examples/yahns_rack_basic.conf.rb"
40
+ cfgs = File.read(path)
41
+ cfgs.gsub!(%r{/path/to/}, "#{tmpdir}/")
42
+ Dir.mkdir("#{tmpdir}/my_app")
43
+ Dir.mkdir("#{tmpdir}/my_logs")
44
+ Dir.mkdir("#{tmpdir}/my_pids")
45
+ conf = File.open("#{tmpdir}/yahns_rack_basic.conf.rb", "w")
46
+ conf.sync = true
47
+ conf.write(cfgs)
48
+ File.open("#{tmpdir}/my_app/config.ru", "w") do |fp|
49
+ fp.puts("run Rack::Lobster.new\n")
50
+ end
51
+ cfg = GTL.synchronize { Yahns::Config.new(conf.path) }
52
+ assert_instance_of Yahns::Config, cfg
53
+ ensure
54
+ FileUtils.rm_rf(tmpdir) if tmpdir
55
+ end
56
+ end
@@ -0,0 +1,19 @@
1
+ # Copyright (C) 2013, Eric Wong <normalperson@yhbt.net> and all contributors
2
+ # License: GPLv3 or later (https://www.gnu.org/licenses/gpl-3.0.txt)
3
+ require_relative 'helper'
4
+
5
+ class TestFdmap < Testcase
6
+ def test_fdmap_negative
7
+ fdmap = Yahns::Fdmap.new(Logger.new($stderr), -5)
8
+ nr = fdmap.instance_variable_get :@client_expire_threshold
9
+ assert_operator nr, :>, 0
10
+ assert_equal nr, Process.getrlimit(:NOFILE)[0] - 5
11
+ end
12
+
13
+ def test_fdmap_float
14
+ fdmap = Yahns::Fdmap.new(Logger.new($stderr), 0.5)
15
+ nr = fdmap.instance_variable_get :@client_expire_threshold
16
+ assert_operator nr, :>, 0
17
+ assert_equal nr, Process.getrlimit(:NOFILE)[0]/2
18
+ end
19
+ end
@@ -0,0 +1,291 @@
1
+ # Copyright (C) 2013, Eric Wong <normalperson@yhbt.net> and all contributors
2
+ # License: GPLv3 or later (https://www.gnu.org/licenses/gpl-3.0.txt)
3
+ require_relative 'server_helper'
4
+ require 'digest/md5'
5
+ require 'rack/file'
6
+
7
+ class TestOutputBuffering < Testcase
8
+ parallelize_me!
9
+ include ServerHelper
10
+ alias setup server_helper_setup
11
+ alias teardown server_helper_teardown
12
+
13
+ GPLv3 = File.read("COPYING")
14
+ RAND = IO.binread("/dev/urandom", 666) * 119
15
+ dig = Digest::MD5.new
16
+ NR = 1337
17
+ MD5 = Thread.new do
18
+ NR.times { dig << RAND }
19
+ dig.hexdigest
20
+ end
21
+
22
+ class BigBody
23
+ def each
24
+ NR.times { yield RAND }
25
+ end
26
+ end
27
+
28
+ def test_output_buffer_false_curl
29
+ output_buffer(false, :curl)
30
+ end
31
+
32
+ def test_output_buffer_false_http09
33
+ output_buffer(false, :http09)
34
+ end
35
+
36
+ def test_output_buffer_true_curl
37
+ output_buffer(true, :curl)
38
+ end
39
+
40
+ def test_output_buffer_true_http09
41
+ output_buffer(true, :http09)
42
+ end
43
+
44
+ def output_buffer(btype, check_type, delay = 4)
45
+ err = @err
46
+ cfg = Yahns::Config.new
47
+ host, port = @srv.addr[3], @srv.addr[1]
48
+ len = (RAND.size * NR).to_s
49
+ cfg.instance_eval do
50
+ ru = lambda do |e|
51
+ [ 200, {'Content-Length'=>len}, BigBody.new ]
52
+ end
53
+ GTL.synchronize do
54
+ app(:rack, ru) do
55
+ listen "#{host}:#{port}"
56
+ output_buffering btype
57
+ end
58
+ end
59
+ logger(Logger.new(err.path))
60
+ end
61
+ srv = Yahns::Server.new(cfg)
62
+ pid = fork do
63
+ ENV["YAHNS_FD"] = @srv.fileno.to_s
64
+ srv.start.join
65
+ end
66
+
67
+ case check_type
68
+ when :curl
69
+ # curl is faster for piping gigantic wads of data than Net::HTTP
70
+ sh_sleep = delay ? "sleep #{delay} && " : ""
71
+ md5 = `curl -sSf http://#{host}:#{port}/ | (#{sh_sleep} md5sum)`
72
+ assert $?.success?, $?.inspect
73
+ (md5 =~ /([a-f0-9]{32})/i) or raise "bad md5: #{md5.inspect}"
74
+ md5 = $1
75
+ assert_equal MD5.value, md5
76
+ when :http09
77
+ # HTTP/0.9
78
+ c = TCPSocket.new(host, port)
79
+ c.write("GET /\r\n\r\n")
80
+ md5in = IO.pipe
81
+ md5out = IO.pipe
82
+ sleep(delay) if delay
83
+ md5pid = Process.spawn("md5sum", :in => md5in[0], :out => md5out[1])
84
+ md5in[0].close
85
+ md5out[1].close
86
+ assert_equal(NR * RAND.size, IO.copy_stream(c, md5in[1]))
87
+ c.shutdown
88
+ c.close
89
+ md5in[1].close
90
+ _, status = Timeout.timeout(10) { Process.waitpid2(md5pid) }
91
+ assert status.success?, status.inspect
92
+ md5 = md5out[0].read
93
+ (md5 =~ /([a-f0-9]{32})/i) or raise "bad md5: #{md5.inspect}"
94
+ md5 = $1
95
+ assert_equal MD5.value, md5
96
+ md5out[0].close
97
+ else
98
+ raise "TESTBUG"
99
+ end
100
+ rescue => e
101
+ Yahns::Log.exception(Logger.new($stderr), "test", e)
102
+ raise
103
+ ensure
104
+ quit_wait(pid)
105
+ end
106
+
107
+ class BigHeader
108
+ A = "A" * 65536
109
+ def initialize(h)
110
+ @h = h
111
+ end
112
+ def each
113
+ NR.times do |n|
114
+ yield("X-#{n}", A)
115
+ end
116
+ @h.each { |k,v| yield(k,v) }
117
+ end
118
+ end
119
+
120
+ def test_big_header
121
+ err = @err
122
+ cfg = Yahns::Config.new
123
+ host, port = @srv.addr[3], @srv.addr[1]
124
+ cfg.instance_eval do
125
+ ru = lambda do |e|
126
+ case e["PATH_INFO"]
127
+ when "/COPYING"
128
+ Rack::File.new(Dir.pwd).call(e)
129
+ gplv3 = File.open("COPYING")
130
+ def gplv3.each
131
+ raise "SHOULD NOT BE CALLED"
132
+ end
133
+ size = gplv3.stat.size
134
+ len = size.to_s
135
+ ranges = Rack::Utils.byte_ranges(e, size)
136
+ status = 200
137
+ h = { "Content-Type" => "text/plain", "Content-Length" => len }
138
+ if ranges && ranges.size == 1
139
+ status = 206
140
+ range = ranges[0]
141
+ h["Content-Range"] = "bytes #{range.begin}-#{range.end}/#{size}"
142
+ size = range.end - range.begin + 1
143
+ len.replace(size.to_s)
144
+ end
145
+ [ status , BigHeader.new(h), gplv3 ]
146
+ when "/"
147
+ h = { "Content-Type" => "text/plain", "Content-Length" => "4" }
148
+ [ 200, BigHeader.new(h), ["BIG\n"] ]
149
+ else
150
+ raise "WTF"
151
+ end
152
+ end
153
+ GTL.synchronize do
154
+ app(:rack, ru) do
155
+ listen "#{host}:#{port}"
156
+ end
157
+ end
158
+ logger(Logger.new(err.path))
159
+ end
160
+ srv = Yahns::Server.new(cfg)
161
+ pid = fork do
162
+ ENV["YAHNS_FD"] = @srv.fileno.to_s
163
+ srv.start.join
164
+ end
165
+ threads = []
166
+
167
+ # start with just a big header
168
+ threads << Thread.new do
169
+ c = TCPSocket.new(host, port)
170
+ c.write "GET / HTTP/1.0\r\n\r\n"
171
+ begin
172
+ sleep 1
173
+ end while c.nread == 0
174
+ nr = 0
175
+ last = nil
176
+ c.each_line do |line|
177
+ case line
178
+ when %r{\AX-} then nr += 1
179
+ else
180
+ last = line
181
+ end
182
+ end
183
+ assert_equal NR, nr
184
+ assert_equal "BIG\n", last
185
+ c.close
186
+ end
187
+
188
+ threads << Thread.new do
189
+ c = TCPSocket.new(host, port)
190
+ c.write "GET /COPYING HTTP/1.0\r\n\r\n"
191
+ begin
192
+ sleep 1
193
+ end while c.nread == 0
194
+ nr = 0
195
+ c.each_line do |line|
196
+ case line
197
+ when %r{\AX-} then nr += 1
198
+ else
199
+ break if line == "\r\n"
200
+ end
201
+ end
202
+ assert_equal NR, nr
203
+ assert_equal GPLv3, c.read
204
+ c.close
205
+ end
206
+
207
+ threads << Thread.new do
208
+ c = TCPSocket.new(host, port)
209
+ c.write "GET /COPYING HTTP/1.0\r\nRange: bytes=5-46\r\n\r\n"
210
+ begin
211
+ sleep 1
212
+ end while c.nread == 0
213
+ nr = 0
214
+ c.each_line do |line|
215
+ case line
216
+ when %r{\AX-} then nr += 1
217
+ else
218
+ break if line == "\r\n"
219
+ end
220
+ end
221
+ assert_equal NR, nr
222
+ assert_equal GPLv3[5..46], c.read
223
+ c.close
224
+ end
225
+ threads.each do |t|
226
+ assert_equal t, t.join(30)
227
+ assert_nil t.value
228
+ end
229
+ ensure
230
+ quit_wait(pid)
231
+ end
232
+
233
+ def test_client_timeout
234
+ err = @err
235
+ apperr = tmpfile(%w(app .err))
236
+ cfg = Yahns::Config.new
237
+ size = RAND.size * NR
238
+ host, port = @srv.addr[3], @srv.addr[1]
239
+ cfg.instance_eval do
240
+ ru = lambda do |e|
241
+ if e["PATH_INFO"] == "/bh"
242
+ h = { "Content-Type" => "text/plain", "Content-Length" => "4" }
243
+ [ 200, BigHeader.new(h), ["BIG\n"] ]
244
+ else
245
+ [ 200, {'Content-Length' => size.to_s }, BigBody.new ]
246
+ end
247
+ end
248
+ GTL.synchronize do
249
+ app(:rack, ru) do
250
+ listen "#{host}:#{port}"
251
+ output_buffering false
252
+ client_timeout 3
253
+ logger(Logger.new(apperr.path))
254
+ end
255
+ end
256
+ logger(Logger.new(err.path))
257
+ end
258
+ srv = Yahns::Server.new(cfg)
259
+ pid = fork do
260
+ ENV["YAHNS_FD"] = @srv.fileno.to_s
261
+ srv.start.join
262
+ end
263
+ threads = []
264
+ threads << Thread.new do
265
+ c = TCPSocket.new(host, port)
266
+ c.write("GET / HTTP/1.1\r\nHost: example.com\r\n\r\n")
267
+ sleep(5) # wait for timeout
268
+ assert_operator c.nread, :>, 0
269
+ c
270
+ end
271
+
272
+ threads << Thread.new do
273
+ c = TCPSocket.new(host, port)
274
+ c.write("GET /bh HTTP/1.1\r\nHost: example.com\r\n\r\n")
275
+ sleep(5) # wait for timeout
276
+ assert_operator c.nread, :>, 0
277
+ c
278
+ end
279
+ threads.each { |t| t.join(10) }
280
+ assert_operator size, :>, threads[0].value.read.size
281
+ assert_operator size, :>, threads[1].value.read.size
282
+ msg = File.readlines(apperr.path)
283
+ msg = msg.grep(/timeout on :wait_writable after 3s$/)
284
+ assert_equal 2, msg.size
285
+ threads.each { |t| t.value.close }
286
+ ensure
287
+ apperr.close! if apperr
288
+ quit_wait(pid)
289
+ end
290
+ end if `which curl 2>/dev/null`.strip =~ /curl/ &&
291
+ `which md5sum 2>/dev/null`.strip =~ /md5sum/