yahns 0.0.0TP1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
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/