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
|
@@ -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
|