yahns 0.0.1 → 0.0.2
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.gitignore +1 -0
- data/Documentation/.gitignore +5 -0
- data/Documentation/GNUmakefile +50 -0
- data/Documentation/yahns-rackup.txt +152 -0
- data/Documentation/yahns.txt +68 -0
- data/Documentation/yahns_config.txt +563 -0
- data/GIT-VERSION-GEN +1 -1
- data/GNUmakefile +14 -7
- data/HACKING +56 -0
- data/INSTALL +8 -0
- data/README +15 -2
- data/Rakefile +2 -2
- data/bin/yahns +1 -2
- data/bin/yahns-rackup +9 -0
- data/examples/yahns_multi.conf.rb +14 -4
- data/examples/yahns_rack_basic.conf.rb +17 -1
- data/extras/README +16 -0
- data/extras/autoindex.rb +151 -0
- data/extras/exec_cgi.rb +108 -0
- data/extras/proxy_pass.rb +210 -0
- data/extras/try_gzip_static.rb +208 -0
- data/lib/yahns.rb +5 -2
- data/lib/yahns/acceptor.rb +64 -22
- data/lib/yahns/cap_input.rb +2 -2
- data/lib/yahns/{client_expire_portable.rb → client_expire_generic.rb} +12 -11
- data/lib/yahns/{client_expire.rb → client_expire_tcpi.rb} +7 -6
- data/lib/yahns/config.rb +107 -22
- data/lib/yahns/daemon.rb +2 -0
- data/lib/yahns/fdmap.rb +28 -9
- data/lib/yahns/http_client.rb +123 -37
- data/lib/yahns/http_context.rb +21 -3
- data/lib/yahns/http_response.rb +80 -19
- data/lib/yahns/log.rb +23 -4
- data/lib/yahns/queue_epoll.rb +20 -9
- data/lib/yahns/queue_quitter.rb +16 -0
- data/lib/yahns/queue_quitter_pipe.rb +24 -0
- data/lib/yahns/rack.rb +0 -1
- data/lib/yahns/rackup_handler.rb +57 -0
- data/lib/yahns/server.rb +189 -59
- data/lib/yahns/server_mp.rb +43 -35
- data/lib/yahns/sigevent_pipe.rb +1 -0
- data/lib/yahns/socket_helper.rb +37 -11
- data/lib/yahns/stream_file.rb +14 -4
- data/lib/yahns/stream_input.rb +13 -7
- data/lib/yahns/tcp_server.rb +7 -0
- data/lib/yahns/tmpio.rb +10 -3
- data/lib/yahns/unix_server.rb +7 -0
- data/lib/yahns/wbuf.rb +19 -2
- data/lib/yahns/wbuf_common.rb +10 -3
- data/lib/yahns/wbuf_str.rb +24 -0
- data/lib/yahns/worker.rb +5 -26
- data/test/helper.rb +15 -5
- data/test/server_helper.rb +37 -1
- data/test/test_bin.rb +17 -8
- data/test/test_buffer_tmpdir.rb +103 -0
- data/test/test_client_expire.rb +71 -35
- data/test/test_client_max_body_size.rb +5 -13
- data/test/test_config.rb +1 -1
- data/test/test_expect_100.rb +176 -0
- data/test/test_extras_autoindex.rb +53 -0
- data/test/test_extras_exec_cgi.rb +81 -0
- data/test/test_extras_exec_cgi.sh +35 -0
- data/test/test_extras_try_gzip_static.rb +177 -0
- data/test/test_input.rb +128 -0
- data/test/test_mt_accept.rb +48 -0
- data/test/test_output_buffering.rb +90 -63
- data/test/test_rack.rb +1 -1
- data/test/test_rack_hijack.rb +2 -6
- data/test/test_reopen_logs.rb +2 -8
- data/test/test_serve_static.rb +104 -8
- data/test/test_server.rb +448 -73
- data/test/test_stream_file.rb +1 -1
- data/test/test_unix_socket.rb +72 -0
- data/test/test_wbuf.rb +20 -17
- data/yahns.gemspec +3 -0
- metadata +57 -5
data/test/test_reopen_logs.rb
CHANGED
@@ -4,7 +4,7 @@ require_relative 'server_helper'
|
|
4
4
|
require 'rack/commonlogger'
|
5
5
|
|
6
6
|
class TestReopenLogs < Testcase
|
7
|
-
parallelize_me!
|
7
|
+
ENV["N"].to_i > 1 and parallelize_me!
|
8
8
|
include ServerHelper
|
9
9
|
alias setup server_helper_setup
|
10
10
|
alias teardown server_helper_teardown
|
@@ -32,10 +32,7 @@ class TestReopenLogs < Testcase
|
|
32
32
|
end
|
33
33
|
worker_processes 1 if worker
|
34
34
|
end
|
35
|
-
pid =
|
36
|
-
ENV["YAHNS_FD"] = @srv.fileno.to_s
|
37
|
-
Yahns::Server.new(cfg).start.join
|
38
|
-
end
|
35
|
+
pid = mkserver(cfg)
|
39
36
|
Net::HTTP.start(host, port) do |http|
|
40
37
|
res = http.request(Net::HTTP::Get.new("/aaa"))
|
41
38
|
assert_equal 200, res.code.to_i
|
@@ -55,9 +52,6 @@ class TestReopenLogs < Testcase
|
|
55
52
|
end until File.read(opath) =~ /bbb/
|
56
53
|
end
|
57
54
|
end
|
58
|
-
rescue => e
|
59
|
-
Yahns::Log.exception(Logger.new($stderr), "test", e)
|
60
|
-
raise
|
61
55
|
ensure
|
62
56
|
quit_wait(pid)
|
63
57
|
end
|
data/test/test_serve_static.rb
CHANGED
@@ -4,12 +4,50 @@ require_relative 'server_helper'
|
|
4
4
|
require 'rack/file'
|
5
5
|
|
6
6
|
class TestServeStatic < Testcase
|
7
|
-
parallelize_me!
|
7
|
+
ENV["N"].to_i > 1 and parallelize_me!
|
8
8
|
include ServerHelper
|
9
9
|
alias setup server_helper_setup
|
10
10
|
alias teardown server_helper_teardown
|
11
11
|
|
12
12
|
def test_serve_static
|
13
|
+
tmpdir = Dir.mktmpdir
|
14
|
+
sock = "#{tmpdir}/sock"
|
15
|
+
err = @err
|
16
|
+
cfg = Yahns::Config.new
|
17
|
+
host, port = @srv.addr[3], @srv.addr[1]
|
18
|
+
cfg.instance_eval do
|
19
|
+
GTL.synchronize do
|
20
|
+
app(:rack, Rack::File.new(Dir.pwd)) {
|
21
|
+
listen sock
|
22
|
+
listen "#{host}:#{port}"
|
23
|
+
}
|
24
|
+
end
|
25
|
+
logger(Logger.new(err.path))
|
26
|
+
end
|
27
|
+
pid = mkserver(cfg)
|
28
|
+
gplv3 = File.read("COPYING")
|
29
|
+
Net::HTTP.start(host, port) do |http|
|
30
|
+
res = http.request(Net::HTTP::Get.new("/COPYING"))
|
31
|
+
assert_equal gplv3, res.body
|
32
|
+
|
33
|
+
req = Net::HTTP::Get.new("/COPYING", "Range" => "bytes=5-46")
|
34
|
+
res = http.request(req)
|
35
|
+
assert_match %r{bytes 5-46/\d+\z}, res["Content-Range"]
|
36
|
+
assert_equal gplv3[5..46], res.body
|
37
|
+
end
|
38
|
+
|
39
|
+
# ensure sendfile works on Unix sockets
|
40
|
+
s = UNIXSocket.new(sock)
|
41
|
+
s.close_on_exec = true
|
42
|
+
s.write "GET /COPYING\r\n\r\n"
|
43
|
+
assert_equal gplv3, Timeout.timeout(30) { s.read }
|
44
|
+
s.close
|
45
|
+
ensure
|
46
|
+
quit_wait(pid)
|
47
|
+
FileUtils.rm_rf tmpdir
|
48
|
+
end
|
49
|
+
|
50
|
+
def test_serve_static_blocked_header
|
13
51
|
err = @err
|
14
52
|
cfg = Yahns::Config.new
|
15
53
|
host, port = @srv.addr[3], @srv.addr[1]
|
@@ -19,10 +57,10 @@ class TestServeStatic < Testcase
|
|
19
57
|
end
|
20
58
|
logger(Logger.new(err.path))
|
21
59
|
end
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
60
|
+
pid = mkserver(cfg) do
|
61
|
+
$_tw_blocked = 0
|
62
|
+
$_tw_block_on = [1]
|
63
|
+
Yahns::HttpClient.__send__(:include, TrywriteBlocked)
|
26
64
|
end
|
27
65
|
gplv3 = File.read("COPYING")
|
28
66
|
Net::HTTP.start(host, port) do |http|
|
@@ -33,10 +71,68 @@ class TestServeStatic < Testcase
|
|
33
71
|
res = http.request(req)
|
34
72
|
assert_equal gplv3[5..46], res.body
|
35
73
|
end
|
36
|
-
rescue => e
|
37
|
-
Yahns::Log.exception(Logger.new($stderr), "test", e)
|
38
|
-
raise
|
39
74
|
ensure
|
40
75
|
quit_wait(pid)
|
41
76
|
end
|
77
|
+
|
78
|
+
def mksparse(tmpdir)
|
79
|
+
sparse = "#{tmpdir}/sparse"
|
80
|
+
off = 100 * 1024 * 1024
|
81
|
+
File.open(sparse, "w") do |fp|
|
82
|
+
fp.sysseek(off)
|
83
|
+
fp.syswrite '.'
|
84
|
+
end
|
85
|
+
[ off + 1, sparse ]
|
86
|
+
end
|
87
|
+
|
88
|
+
def test_truncated_sendfile
|
89
|
+
tmpdir = Dir.mktmpdir
|
90
|
+
size, sparse = mksparse(tmpdir)
|
91
|
+
err, cfg, host, port = @err, Yahns::Config.new, @srv.addr[3], @srv.addr[1]
|
92
|
+
pid = mkserver(cfg) do
|
93
|
+
cfg.instance_eval do
|
94
|
+
app(:rack, Rack::File.new(tmpdir)) { listen "#{host}:#{port}" }
|
95
|
+
stderr_path err.path
|
96
|
+
end
|
97
|
+
end
|
98
|
+
c = get_tcp_client(host, port)
|
99
|
+
c.write "GET /sparse HTTP/1.1\r\nHost: example.com\r\n\r\n"
|
100
|
+
wait_for_full(c)
|
101
|
+
File.truncate(sparse, 5)
|
102
|
+
buf = Timeout.timeout(60) { c.read }
|
103
|
+
c.close
|
104
|
+
assert_operator buf.size, :<, size
|
105
|
+
ensure
|
106
|
+
quit_wait(pid)
|
107
|
+
FileUtils.rm_rf(tmpdir)
|
108
|
+
end
|
109
|
+
|
110
|
+
def test_expanded_sendfile
|
111
|
+
tmpdir = Dir.mktmpdir
|
112
|
+
size, sparse = mksparse(tmpdir)
|
113
|
+
err, cfg, host, port = @err, Yahns::Config.new, @srv.addr[3], @srv.addr[1]
|
114
|
+
pid = mkserver(cfg) do
|
115
|
+
cfg.instance_eval do
|
116
|
+
app(:rack, Rack::File.new(tmpdir)) { listen "#{host}:#{port}" }
|
117
|
+
stderr_path err.path
|
118
|
+
end
|
119
|
+
end
|
120
|
+
c = get_tcp_client(host, port)
|
121
|
+
c.write "GET /sparse\r\n\r\n"
|
122
|
+
wait_for_full(c)
|
123
|
+
|
124
|
+
File.open(sparse, "w") do |fp|
|
125
|
+
fp.sysseek(size * 2)
|
126
|
+
fp.syswrite '.'
|
127
|
+
end
|
128
|
+
Timeout.timeout(60) do
|
129
|
+
bytes = IO.copy_stream(c, "/dev/null")
|
130
|
+
assert_equal bytes, size
|
131
|
+
assert_raises(EOFError) { c.readpartial 1 }
|
132
|
+
end
|
133
|
+
c.close
|
134
|
+
ensure
|
135
|
+
quit_wait(pid)
|
136
|
+
FileUtils.rm_rf(tmpdir)
|
137
|
+
end
|
42
138
|
end
|
data/test/test_server.rb
CHANGED
@@ -3,7 +3,7 @@
|
|
3
3
|
require_relative 'server_helper'
|
4
4
|
|
5
5
|
class TestServer < Testcase
|
6
|
-
parallelize_me!
|
6
|
+
ENV["N"].to_i > 1 and parallelize_me!
|
7
7
|
include ServerHelper
|
8
8
|
|
9
9
|
alias setup server_helper_setup
|
@@ -18,13 +18,9 @@ class TestServer < Testcase
|
|
18
18
|
GTL.synchronize { app(:rack, ru) { listen "#{host}:#{port}" } }
|
19
19
|
logger(Logger.new(err.path))
|
20
20
|
end
|
21
|
-
|
22
|
-
pid = fork do
|
23
|
-
ENV["YAHNS_FD"] = @srv.fileno.to_s
|
24
|
-
srv.start.join
|
25
|
-
end
|
21
|
+
pid = mkserver(cfg)
|
26
22
|
run_client(host, port) { |res| assert_equal "HI", res.body }
|
27
|
-
c =
|
23
|
+
c = get_tcp_client(host, port)
|
28
24
|
|
29
25
|
# test pipelining
|
30
26
|
r = "GET / HTTP/1.1\r\nHost: example.com\r\n\r\n"
|
@@ -60,9 +56,6 @@ class TestServer < Testcase
|
|
60
56
|
_, status = Timeout.timeout(10) { Process.waitpid2(pid) }
|
61
57
|
assert status.success?, status.inspect
|
62
58
|
c.close
|
63
|
-
rescue => e
|
64
|
-
Yahns::Log.exception(Logger.new($stderr), "test", e)
|
65
|
-
raise
|
66
59
|
end
|
67
60
|
|
68
61
|
def test_input_body_true; input_body(true); end
|
@@ -83,12 +76,8 @@ class TestServer < Testcase
|
|
83
76
|
end
|
84
77
|
logger(Logger.new(err.path))
|
85
78
|
end
|
86
|
-
|
87
|
-
|
88
|
-
ENV["YAHNS_FD"] = @srv.fileno.to_s
|
89
|
-
srv.start.join
|
90
|
-
end
|
91
|
-
c = TCPSocket.new(host, port)
|
79
|
+
pid = mkserver(cfg)
|
80
|
+
c = get_tcp_client(host, port)
|
92
81
|
buf = "PUT / HTTP/1.0\r\nContent-Length: 2\r\n\r\nHI"
|
93
82
|
c.write(buf)
|
94
83
|
IO.select([c], nil, nil, 5)
|
@@ -100,7 +89,7 @@ class TestServer < Testcase
|
|
100
89
|
|
101
90
|
# pipelined oneshot
|
102
91
|
buf = "PUT / HTTP/1.1\r\nContent-Length: 2\r\n\r\nHI"
|
103
|
-
c =
|
92
|
+
c = get_tcp_client(host, port)
|
104
93
|
c.write(buf + buf)
|
105
94
|
buf = ""
|
106
95
|
Timeout.timeout(10) do
|
@@ -132,9 +121,6 @@ class TestServer < Testcase
|
|
132
121
|
first = $1
|
133
122
|
assert rv
|
134
123
|
assert_equal first, buf
|
135
|
-
rescue => e
|
136
|
-
Yahns::Log.exception(Logger.new($stderr), "test", e)
|
137
|
-
raise
|
138
124
|
ensure
|
139
125
|
c.close if c
|
140
126
|
quit_wait(pid)
|
@@ -165,12 +151,8 @@ class TestServer < Testcase
|
|
165
151
|
end
|
166
152
|
logger(Logger.new(err.path))
|
167
153
|
end
|
168
|
-
|
169
|
-
|
170
|
-
ENV["YAHNS_FD"] = @srv.fileno.to_s
|
171
|
-
srv.start.join
|
172
|
-
end
|
173
|
-
c = TCPSocket.new(host, port)
|
154
|
+
pid = mkserver(cfg)
|
155
|
+
c = get_tcp_client(host, port)
|
174
156
|
buf = "PUT / HTTP/1.0\r\nTrailer:xbt\r\nTransfer-Encoding: chunked\r\n\r\n"
|
175
157
|
c.write(buf)
|
176
158
|
xbt = btype.to_s
|
@@ -199,7 +181,7 @@ class TestServer < Testcase
|
|
199
181
|
msgs = %w(ZZ zz)
|
200
182
|
err = @err
|
201
183
|
cfg = Yahns::Config.new
|
202
|
-
bpipe =
|
184
|
+
bpipe = cloexec_pipe
|
203
185
|
host, port = @srv.addr[3], @srv.addr[1]
|
204
186
|
cfg.instance_eval do
|
205
187
|
ru = lambda { |e|
|
@@ -237,7 +219,7 @@ class TestServer < Testcase
|
|
237
219
|
# ensure we set worker_threads correctly
|
238
220
|
eggs = srv.instance_variable_get(:@config).qeggs
|
239
221
|
assert_equal 1, eggs.size
|
240
|
-
assert_equal 1, eggs[
|
222
|
+
assert_equal 1, eggs.first[1].instance_variable_get(:@worker_threads)
|
241
223
|
|
242
224
|
pid = fork do
|
243
225
|
bpipe[1].close
|
@@ -245,8 +227,8 @@ class TestServer < Testcase
|
|
245
227
|
srv.start.join
|
246
228
|
end
|
247
229
|
bpipe[0].close
|
248
|
-
a =
|
249
|
-
b =
|
230
|
+
a = get_tcp_client(host, port)
|
231
|
+
b = get_tcp_client(host, port)
|
250
232
|
a.write("GET /sleep HTTP/1.0\r\n\r\n")
|
251
233
|
r = IO.select([a], nil, nil, 4)
|
252
234
|
assert r, "nothing ready"
|
@@ -288,37 +270,6 @@ class TestServer < Testcase
|
|
288
270
|
end
|
289
271
|
end
|
290
272
|
|
291
|
-
# Linux blocking accept() has fair behavior between multiple tasks
|
292
|
-
def test_mp_balance
|
293
|
-
skip("this fails occasionally on Linux, still...")
|
294
|
-
skip("linux-only test") unless RUBY_PLATFORM =~ /linux/
|
295
|
-
pid, host, port = new_mp_server(2)
|
296
|
-
seen = {}
|
297
|
-
|
298
|
-
# wait for both processes to spin up
|
299
|
-
Timeout.timeout(10) do
|
300
|
-
run_client(host, port) { |res| seen[res.body] = 1 } until seen.size == 2
|
301
|
-
end
|
302
|
-
|
303
|
-
prev = nil
|
304
|
-
req = Net::HTTP::Get.new("/")
|
305
|
-
# we should bounce new connections between 2 processes
|
306
|
-
4.times do
|
307
|
-
Net::HTTP.start(host, port) do |http|
|
308
|
-
res = http.request(req)
|
309
|
-
assert_equal 200, res.code.to_i
|
310
|
-
assert_equal "keep-alive", res["Connection"]
|
311
|
-
refute_equal prev, res.body, "same PID accepted twice"
|
312
|
-
prev = res.body.dup
|
313
|
-
seen[prev] += 1
|
314
|
-
666.times { Thread.pass } # have the other acceptor to wake up
|
315
|
-
end
|
316
|
-
end
|
317
|
-
assert_equal 2, seen.size
|
318
|
-
ensure
|
319
|
-
quit_wait(pid)
|
320
|
-
end
|
321
|
-
|
322
273
|
def test_mp_worker_die
|
323
274
|
pid, host, port = new_mp_server
|
324
275
|
wpid1 = wpid2 = nil
|
@@ -349,7 +300,7 @@ class TestServer < Testcase
|
|
349
300
|
end
|
350
301
|
|
351
302
|
def run_client(host, port)
|
352
|
-
c =
|
303
|
+
c = get_tcp_client(host, port)
|
353
304
|
Net::HTTP.start(host, port) do |http|
|
354
305
|
res = http.request(Net::HTTP::Get.new("/"))
|
355
306
|
assert_equal 200, res.code.to_i
|
@@ -381,11 +332,7 @@ class TestServer < Testcase
|
|
381
332
|
GTL.synchronize { app(:rack, ru.path) { listen "#{host}:#{port}" } }
|
382
333
|
logger(Logger.new(File.open(err.path, "a")))
|
383
334
|
end
|
384
|
-
|
385
|
-
pid = fork do
|
386
|
-
ENV["YAHNS_FD"] = @srv.fileno.to_s
|
387
|
-
srv.start.join
|
388
|
-
end
|
335
|
+
pid = mkserver(cfg)
|
389
336
|
[ pid, host, port ]
|
390
337
|
end
|
391
338
|
|
@@ -403,12 +350,8 @@ class TestServer < Testcase
|
|
403
350
|
}
|
404
351
|
logger(Logger.new(err.path))
|
405
352
|
end
|
406
|
-
|
407
|
-
|
408
|
-
ENV["YAHNS_FD"] = @srv.fileno.to_s
|
409
|
-
srv.start.join
|
410
|
-
end
|
411
|
-
c = TCPSocket.new(host, port)
|
353
|
+
pid = mkserver(cfg)
|
354
|
+
c = get_tcp_client(host, port)
|
412
355
|
c.write("GET / HTTP/1.1\r\nHost: example.com\r\n\r\n")
|
413
356
|
buf = Timeout.timeout(10) { c.read }
|
414
357
|
assert_match(/Connection: close/, buf)
|
@@ -416,4 +359,436 @@ class TestServer < Testcase
|
|
416
359
|
ensure
|
417
360
|
quit_wait(pid)
|
418
361
|
end
|
362
|
+
|
363
|
+
def test_ttin_ttou
|
364
|
+
err, cfg, host, port = @err, Yahns::Config.new, @srv.addr[3], @srv.addr[1]
|
365
|
+
ru = lambda { |_|
|
366
|
+
b = "#$$"
|
367
|
+
[ 200, {'Content-Length'=>b.size.to_s}, [b] ]
|
368
|
+
}
|
369
|
+
cfg.instance_eval do
|
370
|
+
GTL.synchronize { app(:rack, ru) { listen "#{host}:#{port}" } }
|
371
|
+
worker_processes 1
|
372
|
+
stderr_path err.path
|
373
|
+
end
|
374
|
+
pid = mkserver(cfg)
|
375
|
+
|
376
|
+
read_pid = lambda do
|
377
|
+
c = get_tcp_client(host, port)
|
378
|
+
c.write("GET /\r\n\r\n")
|
379
|
+
body = Timeout.timeout(10) { c.read }
|
380
|
+
c.close
|
381
|
+
assert_match(/\A\d+\z/, body)
|
382
|
+
body
|
383
|
+
end
|
384
|
+
|
385
|
+
orig_worker_pid = read_pid.call.to_i
|
386
|
+
assert_equal 1, Process.kill(0, orig_worker_pid)
|
387
|
+
|
388
|
+
Process.kill(:TTOU, pid)
|
389
|
+
poke_until_dead(orig_worker_pid)
|
390
|
+
|
391
|
+
Process.kill(:TTIN, pid)
|
392
|
+
second_worker_pid = read_pid.call.to_i
|
393
|
+
|
394
|
+
# PID recycling is rare, hope it doesn't fail here
|
395
|
+
refute_equal orig_worker_pid, second_worker_pid
|
396
|
+
ensure
|
397
|
+
quit_wait(pid)
|
398
|
+
end
|
399
|
+
|
400
|
+
def test_mp_hooks
|
401
|
+
err = @err
|
402
|
+
out = tmpfile(%w(mp_hooks .out))
|
403
|
+
cfg = Yahns::Config.new
|
404
|
+
host, port = @srv.addr[3], @srv.addr[1]
|
405
|
+
cfg.instance_eval do
|
406
|
+
ru = lambda {|_|x="#$$";[200,{'Content-Length'=>x.size.to_s },[x]]}
|
407
|
+
GTL.synchronize {
|
408
|
+
app(:rack, ru) {
|
409
|
+
listen "#{host}:#{port}"
|
410
|
+
persistent_connections false
|
411
|
+
atfork_child { warn "INFO hihi from app.atfork_child" }
|
412
|
+
}
|
413
|
+
worker_processes(1) do
|
414
|
+
atfork_child { puts "af #$$ worker is running" }
|
415
|
+
atfork_prepare { puts "af #$$ parent about to spawn" }
|
416
|
+
atfork_parent { puts "af #$$ this is probably not useful" }
|
417
|
+
end
|
418
|
+
}
|
419
|
+
stderr_path err.path
|
420
|
+
stdout_path out.path
|
421
|
+
end
|
422
|
+
pid = mkserver(cfg)
|
423
|
+
c = get_tcp_client(host, port)
|
424
|
+
c.write("GET / HTTP/1.0\r\nHost: example.com\r\n\r\n")
|
425
|
+
buf = Timeout.timeout(10) { c.read }
|
426
|
+
c.close
|
427
|
+
head, body = buf.split(/\r\n\r\n/)
|
428
|
+
assert_match(/200 OK/, head)
|
429
|
+
assert_match(/\A\d+\z/, body)
|
430
|
+
worker_pid = body.to_i
|
431
|
+
lines = out.readlines.map!(&:chomp!)
|
432
|
+
out.close!
|
433
|
+
|
434
|
+
assert_match %r{INFO hihi from app\.atfork_child}, File.read(err.path)
|
435
|
+
|
436
|
+
assert_equal 3, lines.size
|
437
|
+
assert_equal("af #{pid} parent about to spawn", lines.shift)
|
438
|
+
|
439
|
+
# child/parent ordering is not guaranteed
|
440
|
+
assert_equal 1, lines.grep(/\Aaf #{pid} this is probably not useful\z/).size
|
441
|
+
assert_equal 1, lines.grep(/\Aaf #{worker_pid} worker is running\z/).size
|
442
|
+
ensure
|
443
|
+
quit_wait(pid)
|
444
|
+
end
|
445
|
+
|
446
|
+
def test_mp_hooks_worker_nr
|
447
|
+
err = @err
|
448
|
+
out = tmpfile(%w(mp_hooks .out))
|
449
|
+
cfg = Yahns::Config.new
|
450
|
+
host, port = @srv.addr[3], @srv.addr[1]
|
451
|
+
cfg.instance_eval do
|
452
|
+
ru = lambda {|_|x="#$$";[200,{'Content-Length'=>x.size.to_s },[x]]}
|
453
|
+
GTL.synchronize {
|
454
|
+
app(:rack, ru) {
|
455
|
+
listen "#{host}:#{port}"
|
456
|
+
persistent_connections false
|
457
|
+
atfork_child { |nr| warn "INFO hihi.#{nr} from app.atfork_child" }
|
458
|
+
}
|
459
|
+
worker_processes(1) do
|
460
|
+
atfork_child { |nr| puts "af.#{nr} #$$ worker is running" }
|
461
|
+
atfork_prepare { |nr| puts "af.#{nr} #$$ parent about to spawn" }
|
462
|
+
atfork_parent { |nr| puts "af.#{nr} #$$ this is probably not useful" }
|
463
|
+
end
|
464
|
+
}
|
465
|
+
stderr_path err.path
|
466
|
+
stdout_path out.path
|
467
|
+
end
|
468
|
+
pid = mkserver(cfg)
|
469
|
+
c = get_tcp_client(host, port)
|
470
|
+
c.write("GET / HTTP/1.0\r\nHost: example.com\r\n\r\n")
|
471
|
+
buf = Timeout.timeout(10) { c.read }
|
472
|
+
c.close
|
473
|
+
head, body = buf.split(/\r\n\r\n/)
|
474
|
+
assert_match(/200 OK/, head)
|
475
|
+
assert_match(/\A\d+\z/, body)
|
476
|
+
worker_pid = body.to_i
|
477
|
+
lines = out.readlines.map!(&:chomp!)
|
478
|
+
out.close!
|
479
|
+
|
480
|
+
assert_match %r{INFO hihi\.0 from app\.atfork_child}, File.read(err.path)
|
481
|
+
assert_equal 3, lines.size
|
482
|
+
assert_equal("af.0 #{pid} parent about to spawn", lines.shift)
|
483
|
+
|
484
|
+
# child/parent ordering is not guaranteed
|
485
|
+
assert_equal 1,
|
486
|
+
lines.grep(/\Aaf\.0 #{pid} this is probably not useful\z/).size
|
487
|
+
assert_equal 1,
|
488
|
+
lines.grep(/\Aaf\.0 #{worker_pid} worker is running\z/).size
|
489
|
+
ensure
|
490
|
+
quit_wait(pid)
|
491
|
+
end
|
492
|
+
|
493
|
+
def test_pidfile_usr2
|
494
|
+
tmpdir = Dir.mktmpdir
|
495
|
+
pidf = "#{tmpdir}/pid"
|
496
|
+
old = "#{pidf}.oldbin"
|
497
|
+
err = @err
|
498
|
+
cfg = Yahns::Config.new
|
499
|
+
host, port = @srv.addr[3], @srv.addr[1]
|
500
|
+
cfg.instance_eval do
|
501
|
+
GTL.synchronize {
|
502
|
+
app(:rack, lambda { |_| [ 200, {}, [] ] }) { listen "#{host}:#{port}" }
|
503
|
+
pid pidf
|
504
|
+
}
|
505
|
+
stderr_path err.path
|
506
|
+
end
|
507
|
+
pid = mkserver(cfg) do
|
508
|
+
Yahns::START[0] = "sh"
|
509
|
+
Yahns::START[:argv] = [ '-c', "echo $$ > #{pidf}; sleep 10" ]
|
510
|
+
end
|
511
|
+
|
512
|
+
# ensure server is running
|
513
|
+
c = get_tcp_client(host, port)
|
514
|
+
c.write("GET / HTTP/1.0\r\n\r\n")
|
515
|
+
buf = Timeout.timeout(10) { c.read }
|
516
|
+
assert_match(/Connection: close/, buf)
|
517
|
+
c.close
|
518
|
+
|
519
|
+
assert_equal pid, File.read(pidf).to_i
|
520
|
+
before = File.stat(pidf)
|
521
|
+
|
522
|
+
# start the upgrade
|
523
|
+
Process.kill(:USR2, pid)
|
524
|
+
Timeout.timeout(10) { sleep(0.01) until File.exist?(old) }
|
525
|
+
after = File.stat(old)
|
526
|
+
assert_equal after.ino, before.ino
|
527
|
+
Timeout.timeout(10) { sleep(0.01) until File.exist?(pidf) }
|
528
|
+
new = File.read(pidf).to_i
|
529
|
+
refute_equal pid, new
|
530
|
+
|
531
|
+
# abort the upgrade (just wait for it to finish)
|
532
|
+
Process.kill(:TERM, new)
|
533
|
+
poke_until_dead(new)
|
534
|
+
|
535
|
+
# ensure reversion is OK
|
536
|
+
Timeout.timeout(10) { sleep(0.01) while File.exist?(old) }
|
537
|
+
after = File.stat(pidf)
|
538
|
+
assert_equal before.ino, after.ino
|
539
|
+
assert_equal before.mtime, after.mtime
|
540
|
+
assert_equal pid, File.read(pidf).to_i
|
541
|
+
|
542
|
+
lines = File.readlines(err.path).grep(/ERROR/)
|
543
|
+
assert_equal 1, lines.size
|
544
|
+
assert_match(/reaped/, lines[0], lines)
|
545
|
+
File.truncate(err.path, 0)
|
546
|
+
ensure
|
547
|
+
quit_wait(pid)
|
548
|
+
FileUtils.rm_rf(tmpdir)
|
549
|
+
end
|
550
|
+
|
551
|
+
module MockSwitchUser
|
552
|
+
def self.included(cls)
|
553
|
+
cls.__send__(:remove_method, :switch_user)
|
554
|
+
cls.__send__(:alias_method, :switch_user, :mock_switch_user)
|
555
|
+
end
|
556
|
+
|
557
|
+
def mock_switch_user(user, group = nil)
|
558
|
+
$yahns_user = [ $$, user, group ]
|
559
|
+
end
|
560
|
+
end
|
561
|
+
|
562
|
+
def test_user_no_workers
|
563
|
+
refute defined?($yahns_user), "$yahns_user global should be undefined"
|
564
|
+
err = @err
|
565
|
+
cfg = Yahns::Config.new
|
566
|
+
host, port = @srv.addr[3], @srv.addr[1]
|
567
|
+
cfg.instance_eval do
|
568
|
+
ru = lambda do |_|
|
569
|
+
b = $yahns_user.inspect
|
570
|
+
[ 200, {'Content-Length'=>b.size.to_s }, [b] ]
|
571
|
+
end
|
572
|
+
GTL.synchronize { app(:rack, ru) { listen "#{host}:#{port}" } }
|
573
|
+
user "nobody"
|
574
|
+
stderr_path err.path
|
575
|
+
end
|
576
|
+
pid = mkserver(cfg) { Yahns::Server.__send__(:include, MockSwitchUser) }
|
577
|
+
expect = [ pid, "nobody", nil ].inspect
|
578
|
+
run_client(host, port) { |res| assert_equal expect, res.body }
|
579
|
+
refute defined?($yahns_user), "$yahns_user global should be undefined"
|
580
|
+
ensure
|
581
|
+
quit_wait(pid)
|
582
|
+
end
|
583
|
+
|
584
|
+
def test_user_workers
|
585
|
+
refute defined?($yahns_user), "$yahns_user global should be undefined"
|
586
|
+
err = @err
|
587
|
+
cfg = Yahns::Config.new
|
588
|
+
host, port = @srv.addr[3], @srv.addr[1]
|
589
|
+
cfg.instance_eval do
|
590
|
+
ru = lambda do |_|
|
591
|
+
b = $yahns_user.inspect
|
592
|
+
[ 200, {'Content-Length'=>b.size.to_s, 'X-Pid' => "#$$" }, [b] ]
|
593
|
+
end
|
594
|
+
GTL.synchronize { app(:rack, ru) { listen "#{host}:#{port}" } }
|
595
|
+
user "nobody"
|
596
|
+
stderr_path err.path
|
597
|
+
worker_processes 1
|
598
|
+
end
|
599
|
+
pid = mkserver(cfg) { Yahns::Server.__send__(:include, MockSwitchUser) }
|
600
|
+
run_client(host, port) do |res|
|
601
|
+
worker_pid = res["X-Pid"].to_i
|
602
|
+
assert_operator worker_pid, :>, 0
|
603
|
+
refute_equal pid, worker_pid
|
604
|
+
refute_equal $$, worker_pid
|
605
|
+
expect = [ worker_pid, "nobody", nil ].inspect
|
606
|
+
assert_equal expect, res.body
|
607
|
+
end
|
608
|
+
refute defined?($yahns_user), "$yahns_user global should be undefined"
|
609
|
+
ensure
|
610
|
+
quit_wait(pid)
|
611
|
+
end
|
612
|
+
|
613
|
+
def test_working_directory
|
614
|
+
err, cfg, host, port = @err, Yahns::Config.new, @srv.addr[3], @srv.addr[1]
|
615
|
+
ru = lambda { |_|
|
616
|
+
[ 200, {'Content-Length'=>Dir.pwd.size.to_s }, [Dir.pwd] ]
|
617
|
+
}
|
618
|
+
Dir.mktmpdir do |tmpdir|
|
619
|
+
begin
|
620
|
+
pid = mkserver(cfg) do
|
621
|
+
$LOAD_PATH << File.expand_path("lib")
|
622
|
+
cfg.instance_eval do
|
623
|
+
working_directory tmpdir
|
624
|
+
app(:rack, ru) { listen "#{host}:#{port}" }
|
625
|
+
stderr_path err.path
|
626
|
+
end
|
627
|
+
end
|
628
|
+
refute_equal Dir.pwd, tmpdir
|
629
|
+
Net::HTTP.start(host, port) do |http|
|
630
|
+
assert_equal tmpdir, http.request(Net::HTTP::Get.new("/")).body
|
631
|
+
end
|
632
|
+
ensure
|
633
|
+
quit_wait(pid)
|
634
|
+
end
|
635
|
+
end
|
636
|
+
end
|
637
|
+
|
638
|
+
def test_errors
|
639
|
+
tmpdir = Dir.mktmpdir
|
640
|
+
sock = "#{tmpdir}/sock"
|
641
|
+
err, cfg, host, port = @err, Yahns::Config.new, @srv.addr[3], @srv.addr[1]
|
642
|
+
re = tmpfile(%w(rack .errors))
|
643
|
+
ru = lambda { |e|
|
644
|
+
e["rack.errors"].write "INFO HIHI\n"
|
645
|
+
[ 200, {'Content-Length'=>'2' }, %w(OK) ]
|
646
|
+
}
|
647
|
+
cfg.instance_eval do
|
648
|
+
GTL.synchronize {
|
649
|
+
app(:rack, ru) {
|
650
|
+
listen "#{host}:#{port}"
|
651
|
+
errors re.path
|
652
|
+
}
|
653
|
+
app(:rack, ru) { listen sock }
|
654
|
+
}
|
655
|
+
stderr_path err.path
|
656
|
+
end
|
657
|
+
pid = mkserver(cfg)
|
658
|
+
Net::HTTP.start(host, port) do |http|
|
659
|
+
assert_equal "OK", http.request(Net::HTTP::Get.new("/")).body
|
660
|
+
end
|
661
|
+
assert_equal "INFO HIHI\n", re.read
|
662
|
+
|
663
|
+
c = UNIXSocket.new(sock)
|
664
|
+
c.close_on_exec = true
|
665
|
+
c.write "GET /\r\n\r\n"
|
666
|
+
assert_equal c, c.wait(30)
|
667
|
+
assert_equal "OK", c.read
|
668
|
+
c.close
|
669
|
+
assert_match %r{INFO HIHI}, File.read(err.path)
|
670
|
+
ensure
|
671
|
+
re.close!
|
672
|
+
quit_wait(pid)
|
673
|
+
FileUtils.rm_rf(tmpdir)
|
674
|
+
end
|
675
|
+
|
676
|
+
def test_persistent_shutdown_timeout; _persistent_shutdown(nil); end
|
677
|
+
def test_persistent_shutdown_timeout_mp; _persistent_shutdown(1); end
|
678
|
+
|
679
|
+
def _persistent_shutdown(nr_workers)
|
680
|
+
err, cfg, host, port = @err, Yahns::Config.new, @srv.addr[3], @srv.addr[1]
|
681
|
+
pid = mkserver(cfg) do
|
682
|
+
ru = lambda { |e| [ 200, {'Content-Length'=>'2'}, %w(OK) ] }
|
683
|
+
cfg.instance_eval do
|
684
|
+
app(:rack, ru) { listen "#{host}:#{port}" }
|
685
|
+
stderr_path err.path
|
686
|
+
shutdown_timeout 1
|
687
|
+
worker_processes(nr_workers) if nr_workers
|
688
|
+
end
|
689
|
+
end
|
690
|
+
c = get_tcp_client(host, port)
|
691
|
+
c.write "GET / HTTP/1.1\r\nHost: example.com\r\n\r\n"
|
692
|
+
assert_equal c, c.wait(30)
|
693
|
+
buf = ""
|
694
|
+
re = /\r\n\r\nOK\z/
|
695
|
+
Timeout.timeout(30) do
|
696
|
+
begin
|
697
|
+
buf << c.readpartial(666)
|
698
|
+
end until re =~ buf
|
699
|
+
end
|
700
|
+
refute_match %r{Connection: close}, buf
|
701
|
+
assert_nil c.wait(0.001), "connection should still be alive"
|
702
|
+
Process.kill(:QUIT, pid)
|
703
|
+
_, status = Timeout.timeout(5) { Process.waitpid2(pid) }
|
704
|
+
assert status.success?, status.inspect
|
705
|
+
assert_nil c.wait(1)
|
706
|
+
assert_nil c.read(666)
|
707
|
+
end
|
708
|
+
|
709
|
+
def test_before_exec
|
710
|
+
err, cfg, host, port = @err, Yahns::Config.new, @srv.addr[3], @srv.addr[1]
|
711
|
+
ru = lambda { |e| [ 200, {'Content-Length'=>'2' }, %w(OK) ] }
|
712
|
+
tmp = tmpfile(%w(exec .pid))
|
713
|
+
x = "echo $$ >> #{tmp.path}"
|
714
|
+
pid = mkserver(cfg) do
|
715
|
+
cfg.instance_eval do
|
716
|
+
app(:rack, ru) { listen "#{host}:#{port}" }
|
717
|
+
before_exec do |exec_cmd|
|
718
|
+
exec_cmd.replace(%W(/bin/sh -c #{x}))
|
719
|
+
end
|
720
|
+
stderr_path err.path
|
721
|
+
end
|
722
|
+
end
|
723
|
+
|
724
|
+
# did we start properly?
|
725
|
+
Net::HTTP.start(host, port) do |http|
|
726
|
+
assert_equal "OK", http.request(Net::HTTP::Get.new("/")).body
|
727
|
+
end
|
728
|
+
|
729
|
+
Process.kill(:USR2, pid)
|
730
|
+
Timeout.timeout(30) { sleep(0.01) until tmp.size > 0 }
|
731
|
+
buf = tmp.read
|
732
|
+
assert_match %r{\A\d+}, buf
|
733
|
+
exec_pid = buf.to_i
|
734
|
+
poke_until_dead exec_pid
|
735
|
+
|
736
|
+
# ensure it recovered
|
737
|
+
Net::HTTP.start(host, port) do |http|
|
738
|
+
assert_equal "OK", http.request(Net::HTTP::Get.new("/")).body
|
739
|
+
end
|
740
|
+
assert_match %r{reaped}, err.read
|
741
|
+
err.truncate(0)
|
742
|
+
ensure
|
743
|
+
tmp.close!
|
744
|
+
quit_wait(pid)
|
745
|
+
end
|
746
|
+
|
747
|
+
def test_app_controls_close
|
748
|
+
err, cfg, host, port = @err, Yahns::Config.new, @srv.addr[3], @srv.addr[1]
|
749
|
+
pid = mkserver(cfg) do
|
750
|
+
cfg.instance_eval do
|
751
|
+
ru = lambda { |env|
|
752
|
+
h = { 'Content-Length' => '2' }
|
753
|
+
if env["PATH_INFO"] =~ %r{\A/(.+)}
|
754
|
+
h["Connection"] = $1
|
755
|
+
end
|
756
|
+
[ 200, h, ['HI'] ]
|
757
|
+
}
|
758
|
+
app(:rack, ru) { listen "#{host}:#{port}" }
|
759
|
+
stderr_path err.path
|
760
|
+
end
|
761
|
+
end
|
762
|
+
c = get_tcp_client(host, port)
|
763
|
+
|
764
|
+
# normal response
|
765
|
+
c.write "GET /keep-alive HTTP/1.1\r\nHost: example.com\r\n\r\n"
|
766
|
+
buf = ""
|
767
|
+
Timeout.timeout(30) do
|
768
|
+
buf << c.readpartial(4096) until buf =~ /HI\z/
|
769
|
+
end
|
770
|
+
assert_match %r{^Connection: keep-alive}, buf
|
771
|
+
assert_raises(Errno::EAGAIN,IO::WaitReadable) { c.read_nonblock(666) }
|
772
|
+
|
773
|
+
# we allow whatever in the response, but don't send it
|
774
|
+
c.write "GET /whatever HTTP/1.1\r\nHost: example.com\r\n\r\n"
|
775
|
+
buf = ""
|
776
|
+
Timeout.timeout(30) do
|
777
|
+
buf << c.readpartial(4096) until buf =~ /HI\z/
|
778
|
+
end
|
779
|
+
assert_match %r{^Connection: keep-alive}, buf
|
780
|
+
assert_raises(Errno::EAGAIN,IO::WaitReadable) { c.read_nonblock(666) }
|
781
|
+
|
782
|
+
c.write "GET /close HTTP/1.1\r\nHost: example.com\r\n\r\n"
|
783
|
+
buf = ""
|
784
|
+
Timeout.timeout(30) do
|
785
|
+
buf << c.readpartial(4096) until buf =~ /HI\z/
|
786
|
+
end
|
787
|
+
assert_match %r{^Connection: close}, buf
|
788
|
+
assert_equal c, IO.select([c], nil, nil, 30)[0][0]
|
789
|
+
assert_raises(EOFError) { c.readpartial(666) }
|
790
|
+
c.close
|
791
|
+
ensure
|
792
|
+
quit_wait(pid)
|
793
|
+
end
|
419
794
|
end
|