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
@@ -0,0 +1,24 @@
|
|
1
|
+
# -*- encoding: binary -*-
|
2
|
+
# Copyright (C) 2013, Eric Wong <normalperson@yhbt.net> et. al.
|
3
|
+
# License: GPLv3 or later (https://www.gnu.org/licenses/gpl-3.0.txt)
|
4
|
+
require_relative 'wbuf_common'
|
5
|
+
|
6
|
+
class Yahns::WbufStr # :nodoc:
|
7
|
+
include Yahns::WbufCommon
|
8
|
+
|
9
|
+
def initialize(str, next_state)
|
10
|
+
@str = str
|
11
|
+
@next = next_state # :check_client_connection, :http_100_response
|
12
|
+
end
|
13
|
+
|
14
|
+
def wbuf_flush(client)
|
15
|
+
case rv = client.kgio_trywrite(@str)
|
16
|
+
when String
|
17
|
+
@str = rv
|
18
|
+
when :wait_writable, :wait_readable
|
19
|
+
return rv
|
20
|
+
when nil
|
21
|
+
return @next
|
22
|
+
end while true
|
23
|
+
end
|
24
|
+
end
|
data/lib/yahns/worker.rb
CHANGED
@@ -23,36 +23,15 @@ class Yahns::Worker # :nodoc:
|
|
23
23
|
# This causes the worker to gracefully exit if the master
|
24
24
|
# dies unexpectedly.
|
25
25
|
def yahns_step
|
26
|
-
@to_io.kgio_tryread(11) == nil
|
27
|
-
|
26
|
+
if @to_io.kgio_tryread(11) == nil
|
27
|
+
Process.kill(:QUIT, $$)
|
28
|
+
@to_io.close
|
29
|
+
end
|
30
|
+
:ignore
|
28
31
|
end
|
29
32
|
|
30
33
|
# worker objects may be compared to just plain Integers
|
31
34
|
def ==(other_nr) # :nodoc:
|
32
35
|
@nr == other_nr
|
33
36
|
end
|
34
|
-
|
35
|
-
# Changes the worker process to the specified +user+ and +group+
|
36
|
-
# This is only intended to be called from within the worker
|
37
|
-
# process from the +after_fork+ hook. This should be called in
|
38
|
-
# the +after_fork+ hook after any privileged functions need to be
|
39
|
-
# run (e.g. to set per-worker CPU affinity, niceness, etc)
|
40
|
-
#
|
41
|
-
# Any and all errors raised within this method will be propagated
|
42
|
-
# directly back to the caller (usually the +after_fork+ hook.
|
43
|
-
# These errors commonly include ArgumentError for specifying an
|
44
|
-
# invalid user/group and Errno::EPERM for insufficient privileges
|
45
|
-
def user(user, group = nil)
|
46
|
-
# we do not protect the caller, checking Process.euid == 0 is
|
47
|
-
# insufficient because modern systems have fine-grained
|
48
|
-
# capabilities. Let the caller handle any and all errors.
|
49
|
-
uid = Etc.getpwnam(user).uid
|
50
|
-
gid = Etc.getgrnam(group).gid if group
|
51
|
-
Yahns::Log.chown_all(uid, gid)
|
52
|
-
if gid && Process.egid != gid
|
53
|
-
Process.initgroups(user, gid)
|
54
|
-
Process::GID.change_privilege(gid)
|
55
|
-
end
|
56
|
-
Process.euid != uid and Process::UID.change_privilege(uid)
|
57
|
-
end
|
58
37
|
end
|
data/test/helper.rb
CHANGED
@@ -3,6 +3,11 @@
|
|
3
3
|
$stdout.sync = $stderr.sync = Thread.abort_on_exception = true
|
4
4
|
$-w = true if RUBY_VERSION.to_f >= 2.0
|
5
5
|
require 'thread'
|
6
|
+
require 'fileutils'
|
7
|
+
|
8
|
+
def rubyv
|
9
|
+
puts RUBY_DESCRIPTION
|
10
|
+
end
|
6
11
|
|
7
12
|
# Global Test Lock, to protect:
|
8
13
|
# Process.wait*, Dir.chdir, ENV, trap, require, etc...
|
@@ -11,13 +16,14 @@ GTL = Mutex.new
|
|
11
16
|
# fork-aware coverage data gatherer, see also test/covshow.rb
|
12
17
|
if ENV["COVERAGE"]
|
13
18
|
require "coverage"
|
14
|
-
COVMATCH = %r{/lib/yahns\b.*rb\z}
|
19
|
+
COVMATCH = %r{(/lib/yahns\b|extras/).*rb\z}
|
20
|
+
COVDUMPFILE = File.expand_path("coverage.dump")
|
15
21
|
|
16
22
|
def __covmerge
|
17
23
|
res = Coverage.result
|
18
24
|
|
19
|
-
# do not create the file, Makefile does
|
20
|
-
File.open(
|
25
|
+
# do not create the file, Makefile does this before any tests run
|
26
|
+
File.open(COVDUMPFILE, IO::RDWR) do |covtmp|
|
21
27
|
covtmp.binmode
|
22
28
|
covtmp.sync = true
|
23
29
|
|
@@ -69,20 +75,20 @@ at_exit do
|
|
69
75
|
# skipping @@after_run stuff in minitest since we don't need it
|
70
76
|
case $!
|
71
77
|
when nil, SystemExit
|
72
|
-
mtobj.run(ARGV) if $$ == TSTART_PID
|
78
|
+
exit(mtobj.run(ARGV)) if $$ == TSTART_PID
|
73
79
|
end
|
74
80
|
end
|
75
81
|
|
76
82
|
require "tempfile"
|
77
83
|
require 'tmpdir'
|
78
84
|
class Dir
|
79
|
-
require 'fileutils'
|
80
85
|
def Dir.mktmpdir
|
81
86
|
begin
|
82
87
|
d = "#{Dir.tmpdir}/#$$.#{rand}"
|
83
88
|
Dir.mkdir(d)
|
84
89
|
rescue Errno::EEXIST
|
85
90
|
end while true
|
91
|
+
return d unless block_given?
|
86
92
|
begin
|
87
93
|
yield d
|
88
94
|
ensure
|
@@ -109,6 +115,10 @@ class IO
|
|
109
115
|
end
|
110
116
|
end if ! IO.method_defined?(:nread) && RUBY_PLATFORM =~ /linux/
|
111
117
|
|
118
|
+
def cloexec_pipe
|
119
|
+
IO.pipe.each { |io| io.close_on_exec = true }
|
120
|
+
end
|
121
|
+
|
112
122
|
require 'yahns'
|
113
123
|
|
114
124
|
# needed for parallel (MT) tests)
|
data/test/server_helper.rb
CHANGED
@@ -29,10 +29,16 @@ module ServerHelper
|
|
29
29
|
|
30
30
|
def quit_wait(pid)
|
31
31
|
pid or return
|
32
|
+
err = $!
|
32
33
|
Process.kill(:QUIT, pid)
|
33
34
|
_, status = Timeout.timeout(10) { Process.waitpid2(pid) }
|
34
35
|
assert status.success?, status.inspect
|
35
|
-
rescue Timeout::Error
|
36
|
+
rescue Timeout::Error => tout
|
37
|
+
err ||= tout
|
38
|
+
begin
|
39
|
+
warn "#{err.message} (#{err.class})"
|
40
|
+
err.backtrace.each { |l| warn l }
|
41
|
+
end
|
36
42
|
if RUBY_PLATFORM =~ /linux/
|
37
43
|
system("lsof -p #{pid}")
|
38
44
|
warn "#{pid} failed to die, waiting for user to inspect"
|
@@ -45,6 +51,7 @@ module ServerHelper
|
|
45
51
|
def get_tcp_client(host, port, tries = 500)
|
46
52
|
begin
|
47
53
|
c = TCPSocket.new(host, port)
|
54
|
+
c.close_on_exec = true
|
48
55
|
return c
|
49
56
|
rescue Errno::ECONNREFUSED
|
50
57
|
raise if tries < 0
|
@@ -63,4 +70,33 @@ module ServerHelper
|
|
63
70
|
@err = tmpfile(%w(srv .err))
|
64
71
|
@ru = nil
|
65
72
|
end
|
73
|
+
|
74
|
+
def mkserver(cfg)
|
75
|
+
fork do
|
76
|
+
ENV["YAHNS_FD"] = @srv.fileno.to_s
|
77
|
+
yield if block_given?
|
78
|
+
Yahns::Server.new(cfg).start.join
|
79
|
+
end
|
80
|
+
end
|
81
|
+
|
82
|
+
def wait_for_full(c)
|
83
|
+
prev = 0
|
84
|
+
prev_time = Time.now
|
85
|
+
begin
|
86
|
+
nr = c.nread
|
87
|
+
break if nr > 0 && nr == prev && (Time.now - prev_time) > 0.5
|
88
|
+
if nr != prev
|
89
|
+
prev = nr
|
90
|
+
prev_time = Time.now
|
91
|
+
end
|
92
|
+
Thread.pass
|
93
|
+
end while sleep(0.1)
|
94
|
+
end
|
95
|
+
end
|
96
|
+
|
97
|
+
module TrywriteBlocked
|
98
|
+
def kgio_trywrite(*args)
|
99
|
+
return :wait_writable if $_tw_block_on.include?($_tw_blocked += 1)
|
100
|
+
super
|
101
|
+
end
|
66
102
|
end
|
data/test/test_bin.rb
CHANGED
@@ -2,7 +2,7 @@
|
|
2
2
|
# License: GPLv3 or later (https://www.gnu.org/licenses/gpl-3.0.txt)
|
3
3
|
require_relative 'server_helper'
|
4
4
|
class TestBin < Testcase
|
5
|
-
parallelize_me!
|
5
|
+
ENV["N"].to_i > 1 and parallelize_me!
|
6
6
|
include ServerHelper
|
7
7
|
alias teardown server_helper_teardown
|
8
8
|
|
@@ -40,20 +40,32 @@ class TestBin < Testcase
|
|
40
40
|
cfg.puts " listen ENV['YAHNS_TEST_LISTEN']"
|
41
41
|
cfg.puts "end"
|
42
42
|
@cmd.concat(%W(-D -c #{cfg.path}))
|
43
|
-
addr =
|
43
|
+
addr = cloexec_pipe
|
44
44
|
pid = fork do
|
45
|
+
opts = { close_others: true }
|
46
|
+
addr[0].close
|
45
47
|
if inherit
|
46
|
-
@
|
48
|
+
opts[@srv.fileno] = @srv
|
47
49
|
ENV["YAHNS_FD"] = @srv.fileno.to_s
|
48
50
|
else
|
51
|
+
# we must create the socket inside the child and tell the parent
|
52
|
+
# about it to avoid sharing
|
49
53
|
@srv = TCPServer.new(ENV["TEST_HOST"] || "127.0.0.1", 0)
|
50
|
-
@srv.close_on_exec = true # needed for 1.9.3
|
51
54
|
end
|
55
|
+
@cmd << opts
|
52
56
|
host, port = @srv.addr[3], @srv.addr[1]
|
53
57
|
listen = ENV["YAHNS_TEST_LISTEN"] = "#{host}:#{port}"
|
54
58
|
addr[1].write(listen)
|
55
59
|
addr[1].close
|
56
|
-
|
60
|
+
|
61
|
+
# close/FD_CLOEXEC may be insufficient since the socket could be
|
62
|
+
# released asynchronously, leading to occasional test failures.
|
63
|
+
# Even with a synchronous FD_CLOEXEC, there's a chance of a race
|
64
|
+
# because the server does not bind right away.
|
65
|
+
unless inherit
|
66
|
+
@srv.shutdown
|
67
|
+
@srv.close
|
68
|
+
end
|
57
69
|
exec(*@cmd)
|
58
70
|
end
|
59
71
|
addr[1].close
|
@@ -177,9 +189,6 @@ class TestBin < Testcase
|
|
177
189
|
else
|
178
190
|
poke_until_dead newpid
|
179
191
|
end
|
180
|
-
rescue => e
|
181
|
-
Yahns::Log.exception(Logger.new($stderr), "test", e)
|
182
|
-
raise
|
183
192
|
ensure
|
184
193
|
File.unlink(exe) if exe
|
185
194
|
cfg.close! if cfg
|
@@ -0,0 +1,103 @@
|
|
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 'sleepy_penguin'
|
5
|
+
|
6
|
+
class TestBufferTmpdir < Testcase
|
7
|
+
ENV["N"].to_i > 1 and parallelize_me!
|
8
|
+
include ServerHelper
|
9
|
+
attr_reader :ino, :tmpdir
|
10
|
+
|
11
|
+
def setup
|
12
|
+
@ino = SleepyPenguin::Inotify.new(:CLOEXEC)
|
13
|
+
@tmpdir = Dir.mktmpdir
|
14
|
+
server_helper_setup
|
15
|
+
end
|
16
|
+
|
17
|
+
def teardown
|
18
|
+
server_helper_teardown
|
19
|
+
@ino.close
|
20
|
+
FileUtils.rm_rf @tmpdir
|
21
|
+
end
|
22
|
+
|
23
|
+
class GiantBody
|
24
|
+
# just spew until the client gives up
|
25
|
+
def each
|
26
|
+
nr = 16384
|
27
|
+
buf = "#{nr.to_s(16)}\r\n#{("!" * nr)}\r\n"
|
28
|
+
loop do
|
29
|
+
yield buf
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
33
|
+
|
34
|
+
def test_output_buffer_tmpdir
|
35
|
+
opts = { tmpdir: @tmpdir }
|
36
|
+
err, cfg, host, port = @err, Yahns::Config.new, @srv.addr[3], @srv.addr[1]
|
37
|
+
pid = mkserver(cfg) do
|
38
|
+
cfg.instance_eval do
|
39
|
+
ru = lambda { |e|
|
40
|
+
h = {
|
41
|
+
"Transfer-Encoding" => "chunked",
|
42
|
+
"Content-Type" => "text/plain"
|
43
|
+
}
|
44
|
+
[ 200, h, GiantBody.new ]
|
45
|
+
}
|
46
|
+
app(:rack, ru) do
|
47
|
+
listen "#{host}:#{port}"
|
48
|
+
output_buffering true, opts
|
49
|
+
end
|
50
|
+
stderr_path err.path
|
51
|
+
end
|
52
|
+
end
|
53
|
+
@ino.add_watch @tmpdir, [:CREATE, :DELETE]
|
54
|
+
c = get_tcp_client(host, port)
|
55
|
+
c.write "GET / HTTP/1.1\r\nHost: example.com\r\n\r\n"
|
56
|
+
Timeout.timeout(30) do
|
57
|
+
event = @ino.take
|
58
|
+
assert_equal [:CREATE], event.events
|
59
|
+
name = event.name
|
60
|
+
event = @ino.take
|
61
|
+
assert_equal [:DELETE], event.events
|
62
|
+
assert_equal name, event.name
|
63
|
+
refute File.exist?("#@tmpdir/#{name}")
|
64
|
+
end
|
65
|
+
ensure
|
66
|
+
c.close if c
|
67
|
+
quit_wait(pid)
|
68
|
+
end
|
69
|
+
|
70
|
+
def test_input_buffer_lazy; input_buffer(:lazy); end
|
71
|
+
def test_input_buffer_true; input_buffer(true); end
|
72
|
+
|
73
|
+
def input_buffer(btype)
|
74
|
+
opts = { tmpdir: @tmpdir }
|
75
|
+
err, cfg, host, port = @err, Yahns::Config.new, @srv.addr[3], @srv.addr[1]
|
76
|
+
pid = mkserver(cfg) do
|
77
|
+
cfg.instance_eval do
|
78
|
+
require 'rack/lobster'
|
79
|
+
app(:rack, Rack::Lobster.new) do
|
80
|
+
listen "#{host}:#{port}"
|
81
|
+
input_buffering btype, opts
|
82
|
+
end
|
83
|
+
stderr_path err.path
|
84
|
+
end
|
85
|
+
end
|
86
|
+
@ino.add_watch tmpdir, [:CREATE, :DELETE]
|
87
|
+
c = get_tcp_client(host, port)
|
88
|
+
nr = 16384 # must be > client_body_buffer_size
|
89
|
+
c.write "POST / HTTP/1.0\r\nContent-Length: #{nr}\r\n\r\n"
|
90
|
+
Timeout.timeout(30) do
|
91
|
+
event = ino.take
|
92
|
+
assert_equal [:CREATE], event.events
|
93
|
+
name = event.name
|
94
|
+
event = ino.take
|
95
|
+
assert_equal [:DELETE], event.events
|
96
|
+
assert_equal name, event.name
|
97
|
+
refute File.exist?("#{tmpdir}/#{name}")
|
98
|
+
end
|
99
|
+
ensure
|
100
|
+
c.close if c
|
101
|
+
quit_wait(pid)
|
102
|
+
end
|
103
|
+
end if SleepyPenguin.const_defined?(:Inotify)
|
data/test/test_client_expire.rb
CHANGED
@@ -3,7 +3,7 @@
|
|
3
3
|
require_relative 'server_helper'
|
4
4
|
|
5
5
|
class TestClientExpire < Testcase
|
6
|
-
parallelize_me!
|
6
|
+
ENV["N"].to_i > 1 and parallelize_me!
|
7
7
|
include ServerHelper
|
8
8
|
alias setup server_helper_setup
|
9
9
|
alias teardown server_helper_teardown
|
@@ -22,11 +22,7 @@ class TestClientExpire < Testcase
|
|
22
22
|
end
|
23
23
|
logger(Logger.new(err.path))
|
24
24
|
end
|
25
|
-
|
26
|
-
pid = fork do
|
27
|
-
ENV["YAHNS_FD"] = @srv.fileno.to_s
|
28
|
-
srv.start.join
|
29
|
-
end
|
25
|
+
pid = mkserver(cfg)
|
30
26
|
Net::HTTP.start(host, port) { |h|
|
31
27
|
res = h.get("/")
|
32
28
|
assert_empty res.body
|
@@ -51,16 +47,13 @@ class TestClientExpire < Testcase
|
|
51
47
|
end
|
52
48
|
logger(Logger.new(err.path))
|
53
49
|
end
|
54
|
-
|
55
|
-
|
56
|
-
|
57
|
-
srv.start.join
|
58
|
-
end
|
59
|
-
f = TCPSocket.new(host, port)
|
60
|
-
s = TCPSocket.new(host, port)
|
50
|
+
pid = mkserver(cfg)
|
51
|
+
f = get_tcp_client(host, port)
|
52
|
+
s = get_tcp_client(host, port)
|
61
53
|
req = "GET / HTTP/1.1\r\nHost: example.com\r\n\r\n"
|
62
54
|
s.write(req)
|
63
|
-
str =
|
55
|
+
str = ""
|
56
|
+
Timeout.timeout(20) { str << s.readpartial(666) until str =~ /\r\n\r\n/ }
|
64
57
|
assert_match(%r{keep-alive}, str)
|
65
58
|
sleep 2
|
66
59
|
abe = tmpfile(%w(abe .err))
|
@@ -75,57 +68,100 @@ class TestClientExpire < Testcase
|
|
75
68
|
end
|
76
69
|
io.close
|
77
70
|
end
|
78
|
-
rescue => e
|
79
|
-
Yahns::Log.exception(Logger.new($stderr), "test", e)
|
80
|
-
raise
|
81
71
|
ensure
|
82
72
|
quit_wait(pid)
|
83
73
|
end
|
84
74
|
|
75
|
+
# test EMFILE handling
|
85
76
|
def test_client_expire_desperate
|
86
|
-
skip "disabled since FD counts vary heavily in an MT process"
|
87
77
|
err = @err
|
88
78
|
cfg = Yahns::Config.new
|
89
79
|
host, port = @srv.addr[3], @srv.addr[1]
|
90
80
|
cfg.instance_eval do
|
91
81
|
GTL.synchronize do
|
92
82
|
h = { "Content-Length" => "0" }
|
93
|
-
|
83
|
+
queue { worker_threads 1 }
|
84
|
+
ru = lambda { |e|
|
85
|
+
sleep(0.01) unless e["PATH_INFO"] == "/_"
|
86
|
+
[ 200, h, [] ]
|
87
|
+
}
|
88
|
+
app(:rack, ru) do
|
94
89
|
listen "#{host}:#{port}", sndbuf: 2048, rcvbuf: 2048
|
95
|
-
client_timeout 1
|
90
|
+
client_timeout 1.0
|
91
|
+
# FIXME: wbuf creation does not recover from EMFILE/ENFILE
|
92
|
+
output_buffering false
|
93
|
+
check_client_connection true
|
96
94
|
end
|
97
95
|
client_expire_threshold 1.0
|
98
96
|
end
|
99
|
-
|
97
|
+
stderr_path err.path
|
100
98
|
end
|
101
|
-
|
102
|
-
|
103
|
-
|
104
|
-
|
105
|
-
|
99
|
+
pid = mkserver(cfg) do
|
100
|
+
keep = { $stderr => true, $stdout => true, $stdin => true, @srv => true }
|
101
|
+
ObjectSpace.each_object(IO) do |obj|
|
102
|
+
next if keep[obj]
|
103
|
+
begin
|
104
|
+
obj.close unless obj.closed?
|
105
|
+
rescue IOError # could be uninitialized
|
106
|
+
end
|
107
|
+
end
|
106
108
|
end
|
107
|
-
f =
|
108
|
-
|
109
|
+
f = get_tcp_client(host, port)
|
110
|
+
f.write "G"
|
111
|
+
s = get_tcp_client(host, port)
|
109
112
|
req = "GET / HTTP/1.1\r\nHost: example.com\r\n\r\n"
|
110
113
|
s.write(req)
|
111
|
-
str =
|
114
|
+
str = ""
|
115
|
+
Timeout.timeout(20) { str << s.readpartial(666) until str =~ /\r\n\r\n/ }
|
112
116
|
assert_match(%r{keep-alive}, str)
|
113
|
-
sleep
|
114
|
-
system "ab -c 1000 -n 10000 -k http://#{host}:#{port}/ 2>&1"
|
117
|
+
sleep 1
|
115
118
|
|
119
|
+
# ignore errors, just beat the crap out of the process
|
120
|
+
nr = Process.getrlimit(:NOFILE)[0] # 1024 is common
|
121
|
+
assert_operator nr, :>, 666, "increase RLIM_NOFILE (ulimit -n)"
|
122
|
+
nr -= 50
|
123
|
+
opts = { out: "/dev/null", err: "/dev/null", close_others: true }
|
124
|
+
begin
|
125
|
+
pids = 2.times.map do
|
126
|
+
fork do
|
127
|
+
exec(*%W(ab -c #{nr} -n 9999999 -v1 -k http://#{host}:#{port}/), opts)
|
128
|
+
end
|
129
|
+
end
|
130
|
+
|
131
|
+
re1 = %r{consider raising open file limits}
|
132
|
+
re2 = %r{dropping (\d+) of \d+ clients for timeout=\d+}
|
133
|
+
Timeout.timeout(30) do
|
134
|
+
n = 0
|
135
|
+
begin
|
136
|
+
buf = File.read(err.path)
|
137
|
+
if buf =~ re1 && buf =~ re2
|
138
|
+
n += $1.to_i
|
139
|
+
break if n >= 2
|
140
|
+
end
|
141
|
+
end while sleep(0.01)
|
142
|
+
end
|
143
|
+
ensure
|
144
|
+
# don't care for ab errors, they're likely
|
145
|
+
pids.each do |_pid|
|
146
|
+
Process.kill(:KILL, _pid)
|
147
|
+
Process.waitpid2(_pid)
|
148
|
+
end
|
149
|
+
end
|
116
150
|
[ f, s ].each do |io|
|
117
151
|
assert_raises(Errno::EPIPE,Errno::ECONNRESET) do
|
118
152
|
req.each_byte { |b| io.write(b.chr) }
|
119
153
|
end
|
120
154
|
io.close
|
121
155
|
end
|
156
|
+
|
157
|
+
# make sure the server still works
|
158
|
+
res = Net::HTTP.start(host, port) { |h| h.get("/_") }
|
159
|
+
assert_equal 200, res.code.to_i
|
160
|
+
|
122
161
|
errs = File.readlines(err.path).grep(/ERROR/)
|
123
162
|
File.truncate(err.path, 0) # avoid error on teardown
|
124
|
-
re = %r{consider raising open file limits}
|
163
|
+
re = %r{consider raising open file limits}
|
125
164
|
assert_equal errs.grep(re), errs
|
126
|
-
rescue => e
|
127
|
-
Yahns::Log.exception(Logger.new($stderr), "test", e)
|
128
|
-
raise
|
129
165
|
ensure
|
130
166
|
quit_wait(pid)
|
131
167
|
end
|