From fe6358be43e54d91c2beb9b52ced0437ad45f913 Mon Sep 17 00:00:00 2001 From: Eric Wong Date: Fri, 18 Oct 2013 11:21:13 +0000 Subject: test and fix client expiry This is rarely-called code, but we need to be sure we can expire clients correctly when thresholds are reached. We also correctly handle negative values of the client_expire_threshold directive. --- lib/yahns/client_expire.rb | 10 +++--- lib/yahns/config.rb | 1 - lib/yahns/fdmap.rb | 4 +-- test/test_client_expire.rb | 82 ++++++++++++++++++++++++++++++++++++++++++++++ test/test_fdmap.rb | 19 +++++++++++ 5 files changed, 108 insertions(+), 8 deletions(-) create mode 100644 test/test_client_expire.rb create mode 100644 test/test_fdmap.rb diff --git a/lib/yahns/client_expire.rb b/lib/yahns/client_expire.rb index 7da9498..c35f63f 100644 --- a/lib/yahns/client_expire.rb +++ b/lib/yahns/client_expire.rb @@ -12,18 +12,18 @@ module Yahns::ClientExpire # :nodoc: return 0 if closed? # still racy, but avoid the exception in most cases info = Raindrops::TCP_Info.new(self) - return 0 if info.tcpi_state != 1 # TCP_ESTABLISHED == 1 + return 0 if info.state != 1 # TCP_ESTABLISHED == 1 # Linux struct tcp_info timers are in milliseconds timeout *= 1000 - send_timedout = !!(info.tcpi_last_data_sent > timeout) + send_timedout = !!(info.last_data_sent > timeout) # tcpi_last_data_recv is not valid unless tcpi_ato (ACK timeout) is set - if 0 == info.tcpi_ato - sd = send_timedout && (info.tcpi_last_ack_recv > timeout) + if 0 == info.ato + sd = send_timedout && (info.last_ack_recv > timeout) else - sd = send_timedout && (info.tcpi_last_data_recv > timeout) + sd = send_timedout && (info.last_data_recv > timeout) end if sd shutdown diff --git a/lib/yahns/config.rb b/lib/yahns/config.rb index 1d4d110..d38c090 100644 --- a/lib/yahns/config.rb +++ b/lib/yahns/config.rb @@ -225,7 +225,6 @@ class Yahns::Config # :nodoc: # global def client_expire_threshold(val) var = _check_in_block(nil, :client_expire_threshold) - val > 0 or raise ArgumentError, "#{var} must be > 0" case val when Float val <= 1.0 or raise ArgumentError, "#{var} must be <= 1.0 if a ratio" diff --git a/lib/yahns/fdmap.rb b/lib/yahns/fdmap.rb index 0272421..1e1677a 100644 --- a/lib/yahns/fdmap.rb +++ b/lib/yahns/fdmap.rb @@ -12,8 +12,8 @@ class Yahns::Fdmap # :nodoc: if Float === client_expire_threshold client_expire_threshold *= Process.getrlimit(:NOFILE)[0] - elsif client_expire_treshhold < 0 - client_expire_threshold = Process.getrlimit(:NOFILE)[0] - + elsif client_expire_threshold < 0 + client_expire_threshold = Process.getrlimit(:NOFILE)[0] + client_expire_threshold end @client_expire_threshold = client_expire_threshold.to_i diff --git a/test/test_client_expire.rb b/test/test_client_expire.rb new file mode 100644 index 0000000..8bc82fa --- /dev/null +++ b/test/test_client_expire.rb @@ -0,0 +1,82 @@ +# Copyright (C) 2013, Eric Wong and all contributors +# License: GPLv3 or later (https://www.gnu.org/licenses/gpl-3.0.txt) +require_relative 'server_helper' + +class TestClientExpire < Testcase + parallelize_me! + include ServerHelper + alias setup server_helper_setup + alias teardown server_helper_teardown + + def test_client_expire_negative + err = @err + cfg = Yahns::Config.new + host, port = @srv.addr[3], @srv.addr[1] + cfg.instance_eval do + GTL.synchronize do + ru = lambda { |e| h = { "Content-Length" => "0" }; [ 200, h, [] ] } + app(:rack, ru) do + listen "#{host}:#{port}", sndbuf: 2048, rcvbuf: 2048 + end + client_expire_threshold(-10) + end + logger(Logger.new(err.path)) + end + srv = Yahns::Server.new(cfg) + pid = fork do + ENV["YAHNS_FD"] = @srv.fileno.to_s + srv.start.join + end + Net::HTTP.start(host, port) { |h| + res = h.get("/") + assert_empty res.body + } + ensure + quit_wait(pid) + end + + def test_client_expire + nr = 32 + err = @err + cfg = Yahns::Config.new + host, port = @srv.addr[3], @srv.addr[1] + cfg.instance_eval do + GTL.synchronize do + h = { "Content-Length" => "0" } + app(:rack, lambda { |e| [ 200, h, [] ]}) do + listen "#{host}:#{port}", sndbuf: 2048, rcvbuf: 2048 + client_timeout 1 + end + client_expire_threshold(32) + end + logger(Logger.new(err.path)) + end + srv = Yahns::Server.new(cfg) + pid = fork do + ENV["YAHNS_FD"] = @srv.fileno.to_s + srv.start.join + end + f = TCPSocket.new(host, port) + s = TCPSocket.new(host, port) + req = "GET / HTTP/1.1\r\nHost: example.com\r\n\r\n" + s.write(req) + str = Timeout.timeout(20) { s.readpartial(666) } + assert_match(%r{keep-alive}, str) + sleep 2 + abe = tmpfile(%w(abe .err)) + ab_res = `ab -c #{nr} -n 10000 -k http://#{host}:#{port}/ 2>#{abe.path}` + assert $?.success?, $?.inspect << abe.read + assert_match(/Complete requests:\s+10000\n/, ab_res) + + [ f, s ].each do |io| + assert_raises(Errno::EPIPE,Errno::ECONNRESET) do + req.each_byte { |b| io.write(b.chr) } + end + end + rescue => e + Yahns::Log.exception(Logger.new($stderr), "test", e) + raise + ensure + quit_wait(pid) + end +end diff --git a/test/test_fdmap.rb b/test/test_fdmap.rb new file mode 100644 index 0000000..3525dca --- /dev/null +++ b/test/test_fdmap.rb @@ -0,0 +1,19 @@ +# Copyright (C) 2013, Eric Wong and all contributors +# License: GPLv3 or later (https://www.gnu.org/licenses/gpl-3.0.txt) +require_relative 'helper' + +class TestFdmap < Testcase + def test_fdmap_negative + fdmap = Yahns::Fdmap.new(Logger.new($stderr), -5) + nr = fdmap.instance_variable_get :@client_expire_threshold + assert_operator nr, :>, 0 + assert_equal nr, Process.getrlimit(:NOFILE)[0] - 5 + end + + def test_fdmap_float + fdmap = Yahns::Fdmap.new(Logger.new($stderr), 0.5) + nr = fdmap.instance_variable_get :@client_expire_threshold + assert_operator nr, :>, 0 + assert_equal nr, Process.getrlimit(:NOFILE)[0]/2 + end +end -- cgit v1.2.3-24-ge0c7