about summary refs log tree commit homepage
diff options
context:
space:
mode:
authorEric Wong <normalperson@yhbt.net>2013-10-18 11:21:13 +0000
committerEric Wong <normalperson@yhbt.net>2013-10-18 11:35:43 +0000
commitfe6358be43e54d91c2beb9b52ced0437ad45f913 (patch)
treeb5ab71ae89d1d7b60037bf43950f0910384d2517
parent94acc35b6bc42ca02033d111534b6f8135a724eb (diff)
downloadyahns-fe6358be43e54d91c2beb9b52ced0437ad45f913.tar.gz
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.
-rw-r--r--lib/yahns/client_expire.rb10
-rw-r--r--lib/yahns/config.rb1
-rw-r--r--lib/yahns/fdmap.rb4
-rw-r--r--test/test_client_expire.rb82
-rw-r--r--test/test_fdmap.rb19
5 files changed, 108 insertions, 8 deletions
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 <normalperson@yhbt.net> 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 <normalperson@yhbt.net> 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