yahns 0.0.1 → 0.0.2
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 +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
|