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.
- checksums.yaml +7 -0
- data/.gitignore +15 -0
- data/COPYING +674 -0
- data/GIT-VERSION-GEN +41 -0
- data/GNUmakefile +90 -0
- data/README +127 -0
- data/Rakefile +60 -0
- data/bin/yahns +32 -0
- data/examples/README +3 -0
- data/examples/init.sh +76 -0
- data/examples/logger_mp_safe.rb +28 -0
- data/examples/logrotate.conf +32 -0
- data/examples/yahns_multi.conf.rb +89 -0
- data/examples/yahns_rack_basic.conf.rb +27 -0
- data/lib/yahns.rb +73 -0
- data/lib/yahns/acceptor.rb +28 -0
- data/lib/yahns/client_expire.rb +40 -0
- data/lib/yahns/client_expire_portable.rb +39 -0
- data/lib/yahns/config.rb +344 -0
- data/lib/yahns/daemon.rb +51 -0
- data/lib/yahns/fdmap.rb +90 -0
- data/lib/yahns/http_client.rb +198 -0
- data/lib/yahns/http_context.rb +65 -0
- data/lib/yahns/http_response.rb +184 -0
- data/lib/yahns/log.rb +73 -0
- data/lib/yahns/queue.rb +7 -0
- data/lib/yahns/queue_egg.rb +23 -0
- data/lib/yahns/queue_epoll.rb +57 -0
- data/lib/yahns/rack.rb +80 -0
- data/lib/yahns/server.rb +336 -0
- data/lib/yahns/server_mp.rb +181 -0
- data/lib/yahns/sigevent.rb +7 -0
- data/lib/yahns/sigevent_efd.rb +18 -0
- data/lib/yahns/sigevent_pipe.rb +29 -0
- data/lib/yahns/socket_helper.rb +117 -0
- data/lib/yahns/stream_file.rb +34 -0
- data/lib/yahns/stream_input.rb +150 -0
- data/lib/yahns/tee_input.rb +114 -0
- data/lib/yahns/tmpio.rb +27 -0
- data/lib/yahns/wbuf.rb +36 -0
- data/lib/yahns/wbuf_common.rb +32 -0
- data/lib/yahns/worker.rb +58 -0
- data/test/covshow.rb +29 -0
- data/test/helper.rb +115 -0
- data/test/server_helper.rb +65 -0
- data/test/test_bin.rb +97 -0
- data/test/test_client_expire.rb +132 -0
- data/test/test_config.rb +56 -0
- data/test/test_fdmap.rb +19 -0
- data/test/test_output_buffering.rb +291 -0
- data/test/test_queue.rb +59 -0
- data/test/test_rack.rb +28 -0
- data/test/test_serve_static.rb +42 -0
- data/test/test_server.rb +415 -0
- data/test/test_stream_file.rb +30 -0
- data/test/test_wbuf.rb +136 -0
- data/yahns.gemspec +19 -0
- metadata +165 -0
data/test/test_queue.rb
ADDED
@@ -0,0 +1,59 @@
|
|
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 'timeout'
|
5
|
+
require 'stringio'
|
6
|
+
|
7
|
+
class TestQueue < Testcase
|
8
|
+
parallelize_me!
|
9
|
+
|
10
|
+
def setup
|
11
|
+
@q = Yahns::Queue.new
|
12
|
+
@err = StringIO.new
|
13
|
+
@logger = Logger.new(@err)
|
14
|
+
@q.fdmap = @fdmap = Yahns::Fdmap.new(@logger, 0.5)
|
15
|
+
assert @q.close_on_exec?
|
16
|
+
end
|
17
|
+
|
18
|
+
def test_queue
|
19
|
+
r, w = IO.pipe
|
20
|
+
assert_equal 0, @fdmap.size
|
21
|
+
@q.queue_add(r, Yahns::Queue::QEV_RD)
|
22
|
+
assert_equal 1, @fdmap.size
|
23
|
+
def r.yahns_step
|
24
|
+
begin
|
25
|
+
case read_nonblock(11)
|
26
|
+
when "delete"
|
27
|
+
return :delete
|
28
|
+
end
|
29
|
+
rescue Errno::EAGAIN
|
30
|
+
return :wait_readable
|
31
|
+
rescue EOFError
|
32
|
+
return nil
|
33
|
+
end while true
|
34
|
+
end
|
35
|
+
w.write('.')
|
36
|
+
Timeout.timeout(10) do
|
37
|
+
Thread.pass until r.nread > 0
|
38
|
+
@q.spawn_worker_threads(@logger, 1, 1)
|
39
|
+
Thread.pass until r.nread == 0
|
40
|
+
|
41
|
+
w.write("delete")
|
42
|
+
Thread.pass until r.nread == 0
|
43
|
+
Thread.pass until @fdmap.size == 0
|
44
|
+
|
45
|
+
# should not raise
|
46
|
+
@q.queue_add(r, Yahns::Queue::QEV_RD)
|
47
|
+
assert_equal 1, @fdmap.size
|
48
|
+
w.close
|
49
|
+
Thread.pass until @fdmap.size == 0
|
50
|
+
end
|
51
|
+
assert r.closed?
|
52
|
+
ensure
|
53
|
+
[ r, w ].each { |io| io.close unless io.closed? }
|
54
|
+
end
|
55
|
+
|
56
|
+
def teardown
|
57
|
+
@q.close
|
58
|
+
end
|
59
|
+
end
|
data/test/test_rack.rb
ADDED
@@ -0,0 +1,28 @@
|
|
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
|
+
require 'yahns/rack'
|
6
|
+
class TestRack < Testcase
|
7
|
+
parallelize_me!
|
8
|
+
|
9
|
+
def test_rack
|
10
|
+
tmp = tmpfile(%W(config .ru))
|
11
|
+
tmp.write "run Rack::Lobster.new\n"
|
12
|
+
rapp = GTL.synchronize { Yahns::Rack.new(tmp.path) }
|
13
|
+
assert_kind_of Rack::Lobster, GTL.synchronize { rapp.app_after_fork }
|
14
|
+
defaults = rapp.app_defaults
|
15
|
+
assert_kind_of Hash, defaults
|
16
|
+
tmp.close!
|
17
|
+
end
|
18
|
+
|
19
|
+
def test_rack_preload
|
20
|
+
tmp = tmpfile(%W(config .ru))
|
21
|
+
tmp.write "run Rack::Lobster.new\n"
|
22
|
+
rapp = GTL.synchronize { Yahns::Rack.new(tmp.path, preload: true) }
|
23
|
+
assert_kind_of Rack::Lobster, rapp.instance_variable_get(:@app)
|
24
|
+
defaults = rapp.app_defaults
|
25
|
+
assert_kind_of Hash, defaults
|
26
|
+
tmp.close!
|
27
|
+
end
|
28
|
+
end
|
@@ -0,0 +1,42 @@
|
|
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 'rack/file'
|
5
|
+
|
6
|
+
class TestServeStatic < Testcase
|
7
|
+
parallelize_me!
|
8
|
+
include ServerHelper
|
9
|
+
alias setup server_helper_setup
|
10
|
+
alias teardown server_helper_teardown
|
11
|
+
|
12
|
+
def test_serve_static
|
13
|
+
err = @err
|
14
|
+
cfg = Yahns::Config.new
|
15
|
+
host, port = @srv.addr[3], @srv.addr[1]
|
16
|
+
cfg.instance_eval do
|
17
|
+
GTL.synchronize do
|
18
|
+
app(:rack, Rack::File.new(Dir.pwd)) { listen "#{host}:#{port}" }
|
19
|
+
end
|
20
|
+
logger(Logger.new(err.path))
|
21
|
+
end
|
22
|
+
srv = Yahns::Server.new(cfg)
|
23
|
+
pid = fork do
|
24
|
+
ENV["YAHNS_FD"] = @srv.fileno.to_s
|
25
|
+
srv.start.join
|
26
|
+
end
|
27
|
+
gplv3 = File.read("COPYING")
|
28
|
+
Net::HTTP.start(host, port) do |http|
|
29
|
+
res = http.request(Net::HTTP::Get.new("/COPYING"))
|
30
|
+
assert_equal gplv3, res.body
|
31
|
+
|
32
|
+
req = Net::HTTP::Get.new("/COPYING", "Range" => "bytes=5-46")
|
33
|
+
res = http.request(req)
|
34
|
+
assert_equal gplv3[5..46], res.body
|
35
|
+
end
|
36
|
+
rescue => e
|
37
|
+
Yahns::Log.exception(Logger.new($stderr), "test", e)
|
38
|
+
raise
|
39
|
+
ensure
|
40
|
+
quit_wait(pid)
|
41
|
+
end
|
42
|
+
end
|
data/test/test_server.rb
ADDED
@@ -0,0 +1,415 @@
|
|
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 TestServer < Testcase
|
6
|
+
parallelize_me!
|
7
|
+
include ServerHelper
|
8
|
+
|
9
|
+
alias setup server_helper_setup
|
10
|
+
alias teardown server_helper_teardown
|
11
|
+
|
12
|
+
def test_single_process
|
13
|
+
err = @err
|
14
|
+
cfg = Yahns::Config.new
|
15
|
+
host, port = @srv.addr[3], @srv.addr[1]
|
16
|
+
cfg.instance_eval do
|
17
|
+
ru = lambda { |_| [ 200, {'Content-Length'=>'2'}, ['HI'] ] }
|
18
|
+
GTL.synchronize { app(:rack, ru) { listen "#{host}:#{port}" } }
|
19
|
+
logger(Logger.new(err.path))
|
20
|
+
end
|
21
|
+
srv = Yahns::Server.new(cfg)
|
22
|
+
pid = fork do
|
23
|
+
ENV["YAHNS_FD"] = @srv.fileno.to_s
|
24
|
+
srv.start.join
|
25
|
+
end
|
26
|
+
run_client(host, port) { |res| assert_equal "HI", res.body }
|
27
|
+
c = TCPSocket.new(host, port)
|
28
|
+
|
29
|
+
# test pipelining
|
30
|
+
r = "GET / HTTP/1.1\r\nHost: example.com\r\n\r\n"
|
31
|
+
c.write(r + r)
|
32
|
+
buf = ""
|
33
|
+
Timeout.timeout(10) do
|
34
|
+
until buf =~ /HI.+HI/m
|
35
|
+
buf << c.readpartial(4096)
|
36
|
+
end
|
37
|
+
end
|
38
|
+
|
39
|
+
# trickle pipelining
|
40
|
+
c.write(r + "GET ")
|
41
|
+
buf = ""
|
42
|
+
Timeout.timeout(10) do
|
43
|
+
until buf =~ /HI\z/
|
44
|
+
buf << c.readpartial(4096)
|
45
|
+
end
|
46
|
+
end
|
47
|
+
c.write("/ HTTP/1.1\r\nHost: example.com\r\n\r\n")
|
48
|
+
Timeout.timeout(10) do
|
49
|
+
until buf =~ /HI.+HI/m
|
50
|
+
buf << c.readpartial(4096)
|
51
|
+
end
|
52
|
+
end
|
53
|
+
Process.kill(:QUIT, pid)
|
54
|
+
"GET / HTTP/1.1\r\n\r\n".each_byte { |x| Thread.pass; c.write(x.chr) }
|
55
|
+
buf = Timeout.timeout(10) { c.read }
|
56
|
+
assert_match(/Connection: close/, buf)
|
57
|
+
_, status = Timeout.timeout(10) { Process.waitpid2(pid) }
|
58
|
+
assert status.success?, status.inspect
|
59
|
+
c.close
|
60
|
+
rescue => e
|
61
|
+
Yahns::Log.exception(Logger.new($stderr), "test", e)
|
62
|
+
raise
|
63
|
+
end
|
64
|
+
|
65
|
+
def test_input_body_true; input_body(true); end
|
66
|
+
def test_input_body_false; input_body(false); end
|
67
|
+
def test_input_body_lazy; input_body(:lazy); end
|
68
|
+
|
69
|
+
def input_body(btype)
|
70
|
+
err = @err
|
71
|
+
cfg = Yahns::Config.new
|
72
|
+
host, port = @srv.addr[3], @srv.addr[1]
|
73
|
+
cfg.instance_eval do
|
74
|
+
ru = lambda {|e|[ 200, {'Content-Length'=>'2'},[e["rack.input"].read]]}
|
75
|
+
GTL.synchronize do
|
76
|
+
app(:rack, ru) do
|
77
|
+
listen "#{host}:#{port}"
|
78
|
+
input_buffering btype
|
79
|
+
end
|
80
|
+
end
|
81
|
+
logger(Logger.new(err.path))
|
82
|
+
end
|
83
|
+
srv = Yahns::Server.new(cfg)
|
84
|
+
pid = fork do
|
85
|
+
ENV["YAHNS_FD"] = @srv.fileno.to_s
|
86
|
+
srv.start.join
|
87
|
+
end
|
88
|
+
c = TCPSocket.new(host, port)
|
89
|
+
buf = "PUT / HTTP/1.0\r\nContent-Length: 2\r\n\r\nHI"
|
90
|
+
c.write(buf)
|
91
|
+
IO.select([c], nil, nil, 5)
|
92
|
+
rv = c.read(666)
|
93
|
+
head, body = rv.split(/\r\n\r\n/)
|
94
|
+
assert_match(%r{^Content-Length: 2\r\n}, head)
|
95
|
+
assert_equal "HI", body, "#{rv.inspect} - #{btype.inspect}"
|
96
|
+
c.close
|
97
|
+
|
98
|
+
# pipelined oneshot
|
99
|
+
buf = "PUT / HTTP/1.1\r\nContent-Length: 2\r\n\r\nHI"
|
100
|
+
c = TCPSocket.new(host, port)
|
101
|
+
c.write(buf + buf)
|
102
|
+
buf = ""
|
103
|
+
Timeout.timeout(10) do
|
104
|
+
until buf =~ /HI.+HI/m
|
105
|
+
buf << c.readpartial(4096)
|
106
|
+
end
|
107
|
+
end
|
108
|
+
assert buf.gsub!(/Date:[^\r\n]+\r\n/, ""), "kill differing Date"
|
109
|
+
rv = buf.sub!(/\A(HTTP.+?\r\n\r\nHI)/m, "")
|
110
|
+
first = $1
|
111
|
+
assert rv
|
112
|
+
assert_equal first, buf
|
113
|
+
|
114
|
+
# pipelined trickle
|
115
|
+
buf = "PUT / HTTP/1.1\r\nContent-Length: 5\r\n\r\nHIBYE"
|
116
|
+
(buf + buf).each_byte do |b|
|
117
|
+
c.write(b.chr)
|
118
|
+
sleep(0.01) if b.chr == ":"
|
119
|
+
Thread.pass
|
120
|
+
end
|
121
|
+
buf = ""
|
122
|
+
Timeout.timeout(10) do
|
123
|
+
until buf =~ /HIBYE.+HIBYE/m
|
124
|
+
buf << c.readpartial(4096)
|
125
|
+
end
|
126
|
+
end
|
127
|
+
assert buf.gsub!(/Date:[^\r\n]+\r\n/, ""), "kill differing Date"
|
128
|
+
rv = buf.sub!(/\A(HTTP.+?\r\n\r\nHIBYE)/m, "")
|
129
|
+
first = $1
|
130
|
+
assert rv
|
131
|
+
assert_equal first, buf
|
132
|
+
rescue => e
|
133
|
+
Yahns::Log.exception(Logger.new($stderr), "test", e)
|
134
|
+
raise
|
135
|
+
ensure
|
136
|
+
c.close if c
|
137
|
+
quit_wait(pid)
|
138
|
+
end
|
139
|
+
|
140
|
+
def test_trailer_true; trailer(true); end
|
141
|
+
def test_trailer_false; trailer(false); end
|
142
|
+
def test_trailer_lazy; trailer(:lazy); end
|
143
|
+
def test_slow_trailer_true; trailer(true, 0.02); end
|
144
|
+
def test_slow_trailer_false; trailer(false, 0.02); end
|
145
|
+
def test_slow_trailer_lazy; trailer(:lazy, 0.02); end
|
146
|
+
|
147
|
+
def trailer(btype, delay = false)
|
148
|
+
err = @err
|
149
|
+
cfg = Yahns::Config.new
|
150
|
+
host, port = @srv.addr[3], @srv.addr[1]
|
151
|
+
cfg.instance_eval do
|
152
|
+
ru = lambda do |e|
|
153
|
+
body = e["rack.input"].read
|
154
|
+
s = e["HTTP_XBT"] + "\n" + body
|
155
|
+
[ 200, {'Content-Length'=>s.size.to_s}, [ s ] ]
|
156
|
+
end
|
157
|
+
GTL.synchronize do
|
158
|
+
app(:rack, ru) do
|
159
|
+
listen "#{host}:#{port}"
|
160
|
+
input_buffering btype
|
161
|
+
end
|
162
|
+
end
|
163
|
+
logger(Logger.new(err.path))
|
164
|
+
end
|
165
|
+
srv = Yahns::Server.new(cfg)
|
166
|
+
pid = fork do
|
167
|
+
ENV["YAHNS_FD"] = @srv.fileno.to_s
|
168
|
+
srv.start.join
|
169
|
+
end
|
170
|
+
c = TCPSocket.new(host, port)
|
171
|
+
buf = "PUT / HTTP/1.0\r\nTrailer:xbt\r\nTransfer-Encoding: chunked\r\n\r\n"
|
172
|
+
c.write(buf)
|
173
|
+
xbt = btype.to_s
|
174
|
+
sleep(delay) if delay
|
175
|
+
c.write(sprintf("%x\r\n", xbt.size))
|
176
|
+
sleep(delay) if delay
|
177
|
+
c.write(xbt)
|
178
|
+
sleep(delay) if delay
|
179
|
+
c.write("\r\n")
|
180
|
+
sleep(delay) if delay
|
181
|
+
c.write("0\r\nXBT: ")
|
182
|
+
sleep(delay) if delay
|
183
|
+
c.write("#{xbt}\r\n\r\n")
|
184
|
+
IO.select([c], nil, nil, 5000) or raise "timed out"
|
185
|
+
rv = c.read(666)
|
186
|
+
_, body = rv.split(/\r\n\r\n/)
|
187
|
+
a, b = body.split(/\n/)
|
188
|
+
assert_equal xbt, a
|
189
|
+
assert_equal xbt, b
|
190
|
+
ensure
|
191
|
+
c.close if c
|
192
|
+
quit_wait(pid)
|
193
|
+
end
|
194
|
+
|
195
|
+
def test_check_client_connection
|
196
|
+
msgs = %w(ZZ zz)
|
197
|
+
err = @err
|
198
|
+
cfg = Yahns::Config.new
|
199
|
+
bpipe = IO.pipe
|
200
|
+
host, port = @srv.addr[3], @srv.addr[1]
|
201
|
+
cfg.instance_eval do
|
202
|
+
ru = lambda { |e|
|
203
|
+
case e['PATH_INFO']
|
204
|
+
when '/sleep'
|
205
|
+
a = Object.new
|
206
|
+
a.instance_variable_set(:@bpipe, bpipe)
|
207
|
+
a.instance_variable_set(:@msgs, msgs)
|
208
|
+
def a.each
|
209
|
+
@msgs.each do |msg|
|
210
|
+
yield @bpipe[0].read(msg.size)
|
211
|
+
end
|
212
|
+
end
|
213
|
+
when '/cccfail'
|
214
|
+
# we should not get here if check_client_connection worked
|
215
|
+
abort "CCCFAIL"
|
216
|
+
else
|
217
|
+
a = %w(HI)
|
218
|
+
end
|
219
|
+
[ 200, {'Content-Length'=>'2'}, a ]
|
220
|
+
}
|
221
|
+
GTL.synchronize {
|
222
|
+
app(:rack, ru) {
|
223
|
+
listen "#{host}:#{port}"
|
224
|
+
check_client_connection true
|
225
|
+
# needed to avoid concurrency with check_client_connection
|
226
|
+
queue { worker_threads 1 }
|
227
|
+
output_buffering false
|
228
|
+
}
|
229
|
+
}
|
230
|
+
logger(Logger.new(err.path))
|
231
|
+
end
|
232
|
+
srv = Yahns::Server.new(cfg)
|
233
|
+
|
234
|
+
# ensure we set worker_threads correctly
|
235
|
+
eggs = srv.instance_variable_get(:@config).qeggs
|
236
|
+
assert_equal 1, eggs.size
|
237
|
+
assert_equal 1, eggs[:default].instance_variable_get(:@worker_threads)
|
238
|
+
|
239
|
+
pid = fork do
|
240
|
+
bpipe[1].close
|
241
|
+
ENV["YAHNS_FD"] = @srv.fileno.to_s
|
242
|
+
srv.start.join
|
243
|
+
end
|
244
|
+
bpipe[0].close
|
245
|
+
a = TCPSocket.new(host, port)
|
246
|
+
b = TCPSocket.new(host, port)
|
247
|
+
a.write("GET /sleep HTTP/1.0\r\n\r\n")
|
248
|
+
r = IO.select([a], nil, nil, 4)
|
249
|
+
assert r, "nothing ready"
|
250
|
+
assert_equal a, r[0][0]
|
251
|
+
buf = a.read(8)
|
252
|
+
assert_equal "HTTP/1.1", buf
|
253
|
+
|
254
|
+
# hope the kernel sees this before it sees the bpipe ping-ponging below
|
255
|
+
b.write("GET /cccfail HTTP/1.0\r\n\r\n")
|
256
|
+
b.shutdown
|
257
|
+
b.close
|
258
|
+
|
259
|
+
# ping-pong a bit to stall the server
|
260
|
+
msgs.each do |msg|
|
261
|
+
bpipe[1].write(msg)
|
262
|
+
Timeout.timeout(10) { buf << a.readpartial(10) until buf =~ /#{msg}/ }
|
263
|
+
end
|
264
|
+
bpipe[1].close
|
265
|
+
assert_equal msgs.join, buf.split(/\r\n\r\n/)[1]
|
266
|
+
|
267
|
+
# do things still work?
|
268
|
+
run_client(host, port) { |res| assert_equal "HI", res.body }
|
269
|
+
a.close
|
270
|
+
ensure
|
271
|
+
quit_wait(pid)
|
272
|
+
end
|
273
|
+
|
274
|
+
def test_mp
|
275
|
+
pid, host, port = new_mp_server
|
276
|
+
wpid = nil
|
277
|
+
run_client(host, port) do |res|
|
278
|
+
wpid ||= res.body.to_i
|
279
|
+
end
|
280
|
+
ensure
|
281
|
+
quit_wait(pid)
|
282
|
+
if wpid
|
283
|
+
assert_raises(Errno::ESRCH) { Process.kill(:KILL, wpid) }
|
284
|
+
assert_raises(Errno::ECHILD) { Process.waitpid2(wpid) }
|
285
|
+
end
|
286
|
+
end
|
287
|
+
|
288
|
+
# Linux blocking accept() has fair behavior between multiple tasks
|
289
|
+
def test_mp_balance
|
290
|
+
skip("linux-only test") unless RUBY_PLATFORM =~ /linux/
|
291
|
+
pid, host, port = new_mp_server(2)
|
292
|
+
seen = {}
|
293
|
+
|
294
|
+
# wait for both processes to spin up
|
295
|
+
Timeout.timeout(10) do
|
296
|
+
run_client(host, port) { |res| seen[res.body] = 1 } until seen.size == 2
|
297
|
+
end
|
298
|
+
|
299
|
+
prev = nil
|
300
|
+
req = Net::HTTP::Get.new("/")
|
301
|
+
# we should bounce new connections between 2 processes
|
302
|
+
4.times do
|
303
|
+
Net::HTTP.start(host, port) do |http|
|
304
|
+
res = http.request(req)
|
305
|
+
assert_equal 200, res.code.to_i
|
306
|
+
assert_equal "keep-alive", res["Connection"]
|
307
|
+
refute_equal prev, res.body, "same PID accepted twice"
|
308
|
+
prev = res.body.dup
|
309
|
+
seen[prev] += 1
|
310
|
+
666.times { Thread.pass } # have the other acceptor to wake up
|
311
|
+
end
|
312
|
+
end
|
313
|
+
assert_equal 2, seen.size
|
314
|
+
ensure
|
315
|
+
quit_wait(pid)
|
316
|
+
end
|
317
|
+
|
318
|
+
def test_mp_worker_die
|
319
|
+
pid, host, port = new_mp_server
|
320
|
+
wpid1 = wpid2 = nil
|
321
|
+
run_client(host, port) do |res|
|
322
|
+
wpid1 ||= res.body.to_i
|
323
|
+
end
|
324
|
+
Process.kill(:QUIT, wpid1)
|
325
|
+
poke_until_dead(wpid1)
|
326
|
+
run_client(host, port) do |res|
|
327
|
+
wpid2 ||= res.body.to_i
|
328
|
+
end
|
329
|
+
refute_equal wpid2, wpid1
|
330
|
+
ensure
|
331
|
+
quit_wait(pid)
|
332
|
+
assert_raises(Errno::ESRCH) { Process.kill(:KILL, wpid2) } if wpid2
|
333
|
+
end
|
334
|
+
|
335
|
+
def test_mp_dead_parent
|
336
|
+
pid, host, port = new_mp_server
|
337
|
+
wpid = nil
|
338
|
+
run_client(host, port) do |res|
|
339
|
+
wpid ||= res.body.to_i
|
340
|
+
end
|
341
|
+
Process.kill(:KILL, pid)
|
342
|
+
_, status = Process.waitpid2(pid)
|
343
|
+
assert status.signaled?, status.inspect
|
344
|
+
poke_until_dead(wpid)
|
345
|
+
end
|
346
|
+
|
347
|
+
def run_client(host, port)
|
348
|
+
c = TCPSocket.new(host, port)
|
349
|
+
Net::HTTP.start(host, port) do |http|
|
350
|
+
res = http.request(Net::HTTP::Get.new("/"))
|
351
|
+
assert_equal 200, res.code.to_i
|
352
|
+
assert_equal "keep-alive", res["Connection"]
|
353
|
+
yield res
|
354
|
+
res = http.request(Net::HTTP::Get.new("/"))
|
355
|
+
assert_equal 200, res.code.to_i
|
356
|
+
assert_equal "keep-alive", res["Connection"]
|
357
|
+
yield res
|
358
|
+
end
|
359
|
+
c.write "GET / HTTP/1.0\r\n\r\n"
|
360
|
+
res = Timeout.timeout(10) { c.read }
|
361
|
+
head, _ = res.split(/\r\n\r\n/)
|
362
|
+
head = head.split(/\r\n/)
|
363
|
+
assert_equal "HTTP/1.1 200 OK", head[0]
|
364
|
+
assert_equal "Connection: close", head[-1]
|
365
|
+
c.close
|
366
|
+
end
|
367
|
+
|
368
|
+
def new_mp_server(nr = 1)
|
369
|
+
ru = @ru = tmpfile(%w(config .ru))
|
370
|
+
@ru.puts('a = $$.to_s')
|
371
|
+
@ru.puts('run lambda { |_| [ 200, {"Content-Length"=>a.size.to_s},[a]]}')
|
372
|
+
err = @err
|
373
|
+
cfg = Yahns::Config.new
|
374
|
+
host, port = @srv.addr[3], @srv.addr[1]
|
375
|
+
cfg.instance_eval do
|
376
|
+
worker_processes 2
|
377
|
+
GTL.synchronize { app(:rack, ru.path) { listen "#{host}:#{port}" } }
|
378
|
+
logger(Logger.new(File.open(err.path, "a")))
|
379
|
+
end
|
380
|
+
srv = Yahns::Server.new(cfg)
|
381
|
+
pid = fork do
|
382
|
+
ENV["YAHNS_FD"] = @srv.fileno.to_s
|
383
|
+
srv.start.join
|
384
|
+
end
|
385
|
+
[ pid, host, port ]
|
386
|
+
end
|
387
|
+
|
388
|
+
def test_nonpersistent
|
389
|
+
err = @err
|
390
|
+
cfg = Yahns::Config.new
|
391
|
+
host, port = @srv.addr[3], @srv.addr[1]
|
392
|
+
cfg.instance_eval do
|
393
|
+
ru = lambda { |_| [ 200, {'Content-Length'=>'2'}, ['HI'] ] }
|
394
|
+
GTL.synchronize {
|
395
|
+
app(:rack, ru) {
|
396
|
+
listen "#{host}:#{port}"
|
397
|
+
persistent_connections false
|
398
|
+
}
|
399
|
+
}
|
400
|
+
logger(Logger.new(err.path))
|
401
|
+
end
|
402
|
+
srv = Yahns::Server.new(cfg)
|
403
|
+
pid = fork do
|
404
|
+
ENV["YAHNS_FD"] = @srv.fileno.to_s
|
405
|
+
srv.start.join
|
406
|
+
end
|
407
|
+
c = TCPSocket.new(host, port)
|
408
|
+
c.write("GET / HTTP/1.1\r\nHost: example.com\r\n\r\n")
|
409
|
+
buf = Timeout.timeout(10) { c.read }
|
410
|
+
assert_match(/Connection: close/, buf)
|
411
|
+
c.close
|
412
|
+
ensure
|
413
|
+
quit_wait(pid)
|
414
|
+
end
|
415
|
+
end
|